// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "entry_impl.h"
#include "stream_impl.h"
#include "exception.h"
#include <mobius/datetime/conv_unix_timestamp.h>
#include <mobius/string_functions.h>

namespace mobius
{
namespace filesystem
{
namespace tsk
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get path dirname
//! \param path
//! \return dirname
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static const std::string
dirname (const std::string& path)
{
  std::string dname;

  auto pos = path.rfind ('/');

  if (pos != std::string::npos)
    dname = path.substr (0, pos);

  return dname;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get path basename
//! \param path
//! \return basename
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static const std::string
basename (const std::string& path)
{
  std::string bname;

  auto pos = path.rfind ('/');

  if (pos == std::string::npos)
    bname = path;
  else
    bname = path.substr (pos+1);

  return bname;
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \param fs_file libtsk file structure pointer
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
entry_impl::entry_impl (TSK_FS_FILE *fs_file)
  : fs_file_ (fs_file, tsk_fs_file_close),
    fs_dir_ (nullptr, tsk_fs_dir_close)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get entry's children
//! \return vector of children
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const std::vector <std::shared_ptr <entry_impl_base>>
    entry_impl::get_children () const
{
  std::vector <std::shared_ptr <entry_impl_base>> children;

  // if entry is not folder, returns a null vector
  if (!is_folder ())
    return children;

  // search children
  _load_fs_dir ();

  std::uint32_t count = tsk_fs_dir_getsize (fs_dir_.get ());
  if (count == 0)
    return children;

  children.reserve (count);

  for (std::uint32_t i = 0; i < count; i++)
    {
      TSK_FS_FILE *file_p = tsk_fs_dir_get (fs_dir_.get (), i);
      if (!file_p)
        throw MOBIUS_TSK_EXCEPTION;

      const std::string name = (file_p->name && file_p->name->name) ? file_p->name->name : std::string ();

      if (file_p->name->meta_addr != get_inode () && name != "." && name != "..")
        {
          auto child = std::make_shared <entry_impl> (file_p);
          child->set_path (get_path () + '/' + name);
          children.push_back (child);
        }
    }

  return children;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get parent
//! \return parent entry, if found
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::shared_ptr <entry_impl_base>
entry_impl::get_parent () const
{
  std::shared_ptr <entry_impl_base> parent;

  if (fs_file_->name && fs_file_->name->meta_addr != fs_file_->name->par_addr)
    {
      TSK_FS_FILE *file_p = tsk_fs_file_open_meta (
                              fs_file_->fs_info,
                              nullptr,
                              fs_file_->name->par_addr
                            );

      if (!file_p)
        throw MOBIUS_TSK_EXCEPTION;

      // build parent object
      auto p = std::make_shared <entry_impl> (file_p);
      parent = p;
      parent->set_path (dirname (get_path ()));
      p->name_ = basename (parent->get_path ());
    }

  return parent;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get child by name
//! \param name name
//! \param case_sensitive if name comparision is case sensitive or not
//! \return child, if found
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::shared_ptr <entry_impl_base>
entry_impl::get_child_by_name (const std::string& name, bool case_sensitive) const
{
  std::shared_ptr <entry_impl_base> child;
  const std::string lname = mobius::string::tolower (name);

  // open directory
  _load_fs_dir ();

  // search child
  std::uint32_t count = tsk_fs_dir_getsize (fs_dir_.get ());
  std::uint32_t i = 0;
  bool found = false;

  while (i < count && !found)
    {
      const TSK_FS_NAME *fs_name = tsk_fs_dir_get_name (fs_dir_.get (), i);
      if (!fs_name)
        throw MOBIUS_TSK_EXCEPTION;

      if ((name == fs_name->name) || (!case_sensitive && (mobius::string::tolower (fs_name->name) == lname)))
        {
          TSK_FS_FILE *file_p = tsk_fs_dir_get (fs_dir_.get (), i);
          if (!file_p)
            throw MOBIUS_TSK_EXCEPTION;

          child = std::make_shared <entry_impl> (file_p);

          if (!child->is_deleted ())
            found = true;
        }

      i++;
    }

  if (child)
    child->set_path (get_path () + '/' + child->get_name ());

  return child;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief load dir structure pointer
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
entry_impl::_load_fs_dir () const
{
  if (!fs_dir_)
    {
      TSK_FS_DIR *dir_p = tsk_fs_dir_open_meta (fs_file_->fs_info, get_inode ());
      if (!dir_p)
        throw MOBIUS_TSK_EXCEPTION;

      fs_dir_ = fs_dir_type (dir_p, tsk_fs_dir_close);
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get entry's streams
//! \return vector of streams
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const std::vector <std::shared_ptr <stream_impl_base>>
    entry_impl::get_streams () const
{
  std::vector <std::shared_ptr <stream_impl_base>> streams;
  std::size_t count = tsk_fs_file_attr_getsize (fs_file_.get ());

  for (std::size_t i = 0; i < count; i++)
    {
      const TSK_FS_ATTR *fs_attr_p = tsk_fs_file_attr_get_idx (fs_file_.get (), i);

      if (fs_attr_p)
        {
          auto stream = std::make_shared <stream_impl> (fs_attr_p);
          streams.push_back (stream);
        }
    }

  return streams;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new reader for the default stream
//! \return reader
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const  mobius::io::reader
entry_impl::new_reader () const
{
  for (const auto& stream : get_streams ())
    {
      if (stream->get_type () == TSK_FS_ATTR_TYPE_NTFS_DATA ||
          stream->get_type () == TSK_FS_ATTR_TYPE_DEFAULT ||
          stream->get_type () == TSK_FS_ATTR_TYPE_HFS_DATA)
        return stream->new_reader ();
    }

  return mobius::io::reader ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief load fs_name on demand
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
entry_impl::_load_fs_name () const
{
  if (!fs_name_loaded_ && fs_file_->name)
    {
      inode_ = fs_file_->name->meta_addr;
      is_deleted_ = bool (fs_file_->name->flags & TSK_FS_NAME_FLAG_UNALLOC);

      if (fs_file_->name->name)
        name_ = fs_file_->name->name;

      if (fs_file_->name->shrt_name)
        short_name_ = fs_file_->name->shrt_name;

      switch (fs_file_->name->type)
        {
        case TSK_FS_NAME_TYPE_REG:
          type_ = entry_type::file;
          break;

        case TSK_FS_NAME_TYPE_DIR:
        case TSK_FS_NAME_TYPE_VIRT_DIR:
          type_ = entry_type::folder;
          break;

        case TSK_FS_NAME_TYPE_LNK:
          type_ = entry_type::link;
          break;

        default:
          type_ = entry_type::unknown;
          break;
        }

      fs_name_loaded_ = true;
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief load fs_meta on demand
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
entry_impl::_load_fs_meta () const
{
  if (fs_meta_loaded_)
    return;

  // retrieve meta structure if needed
  if (!fs_file_->meta && get_inode ())
    {
      int rc = fs_file_->fs_info->file_add_meta (fs_file_->fs_info, fs_file_.get (), get_inode ());
      if (rc)
        throw MOBIUS_TSK_EXCEPTION;
    }

  // set metadata
  if (fs_file_->meta)
    {
      size_ = fs_file_->meta->size;
      mode_ = fs_file_->meta->mode;
      uid_ = fs_file_->meta->uid;
      gid_ = fs_file_->meta->gid;
      is_reallocated_ = is_deleted () && (fs_file_->meta->flags & TSK_FS_META_FLAG_ALLOC);

      // set timestamps
      if (fs_file_->meta->atime)
        last_access_time_ = mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->atime);

      if (fs_file_->meta->mtime)
        last_modification_time_ = mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->mtime);

      if (fs_file_->meta->ctime)
        last_metadata_time_ = mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->ctime);

      if (fs_file_->meta->crtime)
        creation_time_ = mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->crtime);

      if (fs_file_->fs_info->ftype & TSK_FS_TYPE_EXT_DETECT)
        deletion_time_ = mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->time2.ext2.dtime);

      // set i-node and type, if not set
      if (!fs_file_->name)
        {
          inode_ = fs_file_->meta->addr;

          switch (fs_file_->meta->type)
            {
            case TSK_FS_META_TYPE_REG:
              type_ = entry_type::file;
              break;

            case TSK_FS_META_TYPE_DIR:
            case TSK_FS_META_TYPE_VIRT_DIR:
              type_ = entry_type::folder;
              break;

            case TSK_FS_META_TYPE_LNK:
              type_ = entry_type::link;
              break;

            default:
              type_ = entry_type::unknown;
              break;
            }
        }
    }

  fs_meta_loaded_ = true;
}

} // namespace tsk
} // namespace filesystem
} // namespace mobius
