// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017 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>

namespace mobius
{
namespace filesystem
{
namespace tsk
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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)
{
  int type = 0;

  // read name structure
  if (fs_file_->name)
    {
      type = fs_file_->name->type;
      set_inode (fs_file_->name->meta_addr);
      is_deleted (fs_file_->name->flags & TSK_FS_NAME_FLAG_UNALLOC);

      if (fs_file_->name->name)
        set_name (fs_file_->name->name);

      if (fs_file_->name->shrt_name)
        set_short_name (fs_file_->name->shrt_name);
    }

  // 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;
    }

  // read meta structure
  if (fs_file_->meta)
    {
      type = fs_file_->meta->type;
      set_inode (fs_file_->meta->addr);
      is_deleted (fs_file_->meta->flags & TSK_FS_NAME_FLAG_UNALLOC);
      set_size (fs_file_->meta->size);
      set_mode (fs_file_->meta->mode);
      set_uid (fs_file_->meta->uid);
      set_gid (fs_file_->meta->gid);

      // fill timestamps
      if (fs_file_->meta->atime)
        set_last_access_time (mobius::datetime::datetime_from_unix_timestamp (fs_file_->meta->atime));

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

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

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

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

  // set is_reallocated flag
  is_reallocated (is_deleted () &&
                  fs_file_->meta &&
                  (fs_file_->meta->flags & TSK_FS_NAME_FLAG_ALLOC));

  // set type
  switch (type)
    {
    case TSK_FS_META_TYPE_REG:
      set_type (entry_type::file);
      break;

    case TSK_FS_META_TYPE_DIR:
      set_type (entry_type::folder);
      break;

    case TSK_FS_META_TYPE_LNK:
      set_type (entry_type::link);
      break;
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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 ());
  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 () + '/' + child->get_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
      parent = std::make_shared <entry_impl> (file_p);
      parent->set_path (dirname (get_path ()));
      parent->set_name (basename (parent->get_path ()));
    }

  return parent;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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;
}

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