// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// 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 <mobius/imagefile/ewf/reader_impl.h>
#include <mobius/imagefile/ewf/segment_decoder.h>
#include <mobius/imagefile/ewf/imagefile_impl.h>
#include <mobius/exception.inc>
#include <mobius/zlib_functions.h>

namespace mobius
{
namespace imagefile
{
namespace ewf
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \param imagefile_impl imagefile implementation
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
reader_impl::reader_impl (const imagefile_impl& imagefile_impl)
  : size_ (imagefile_impl.get_size ()),
    segments_ (imagefile_impl.get_url ())
{
  segments_.scan ();
  size_type next_offset = 0;
  size_type chunk_count = 0;

  // walk through segment files
  for (auto segment : segments_)
    {
      auto reader = segment.new_reader ();
      segment_decoder decoder (reader);
  
      // walk through sections, retrieving imagefile metadata
      for (auto section : decoder)
        {
          if (section.get_name () == "volume" || section.get_name () == "disk")
            {
              auto volume_section = decoder.decode_volume_section (section);
              chunk_size_ = volume_section.get_chunk_sectors () * volume_section.get_sector_size ();
            }
        
          else if (section.get_name () == "table")
            {
              auto table_section = decoder.decode_table_section (section);
              chunk_count += table_section.get_chunk_count ();
              size_type size = table_section.get_chunk_count () * chunk_size_;

              segment_info info;
              info.segment_idx = segment.get_idx ();
              info.start_offset = next_offset;
              info.end_offset = info.start_offset + size - 1;
              info.base_offset = table_section.get_base_offset ();
              info.offset_table = table_section.get_offset_list ();
              segment_info_.push_back (info);
              
              next_offset += size;
            }
        }
    }

  segment_idx_ = segments_.get_size ();
  chunk_idx_ = chunk_count;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief set read position
//! \param offset offset in bytes
//! \param w either beginning, current or end
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
reader_impl::seek (offset_type offset, whence_type w)
{
  // calculate offset from the beginning of data
  offset_type abs_offset;

  if (w == whence_type::beginning)
    abs_offset = offset;

  else if (w == whence_type::current)
    abs_offset = pos_ + offset;

  else if (w == whence_type::end)
    abs_offset = size_ - 1 + offset;

  else
    throw std::invalid_argument (MOBIUS_EXCEPTION_MSG ("invalid whence_type"));

  // update current pos, if possible
  if (abs_offset >= 0 && size_type (abs_offset) < size_)
    pos_ = abs_offset;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief read bytes from reader
//! \param size size in bytes
//! \return bytearray containing data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const mobius::bytearray
reader_impl::read (size_type size)
{
  size = std::min (size_ - pos_, size);
  mobius::bytearray data;
 
  while (size > 0)
    {
      _retrieve_current_chunk ();

      size_type slice_start = pos_ % chunk_size_;
      size_type slice_end = std::min (slice_start + size - 1, chunk_data_.size () - 1);
      mobius::bytearray tmp = chunk_data_.slice (slice_start, slice_end);
      data += tmp;
      pos_ += tmp.size ();
      size -= tmp.size ();
    }

  return data;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief retrieve current data chunk
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
reader_impl::_retrieve_current_chunk ()
{
  size_type chunk_idx = pos_ / chunk_size_;
  
  // if chunk is loaded, return
  if (chunk_idx == chunk_idx_)
    return;
  
  // find segment info for pos_ offset (binary search)
  size_type hi = segment_info_.size ();
  size_type lo = 0;
  size_type mid;
  bool found = false;
  
  while (lo < hi && !found)
    {
      mid = (lo + hi) / 2;
      
      if (segment_info_[mid].start_offset > pos_)
        hi = mid;
      
      else if (segment_info_[mid].end_offset < pos_)
        lo = mid;
      
      else
        found = true;
    }

  // if segment info not found, return
  if (!found)
    return;

  // set stream
  auto segment_idx = segment_info_[mid].segment_idx;

  if (segment_idx != segment_idx_)
    {
      stream_ = segments_[segment_idx].new_reader ();
      segment_idx_ = segment_idx;
    }

  // get chunk data offset
  const auto& info = segment_info_[mid];
  size_type table_idx = (pos_ - info.start_offset) / chunk_size_;
  auto offset = info.offset_table[table_idx];
  bool compressed = offset & 0x80000000;
  offset = offset & 0x7fffffff;
      
  // get data size
  size_type data_size;
     
  if (table_idx + 1 < info.offset_table.size ())
    data_size = (info.offset_table[table_idx+1] & 0x7fffffff) - offset;
  else
    data_size = info.end_offset - offset;

  // read chunk data
  stream_.seek (info.base_offset + offset);

  if (compressed)
    chunk_data_ = zlib_decompress (stream_.read (data_size));

  else
    {
      chunk_data_ = stream_.read (data_size - 4);
      stream_.skip (4);         // skip CRC of uncompressed data
    }

  // set new current chunk index
  chunk_idx_ = chunk_idx;      
}

} // namespace ewf
} // namespace imagefile
} // namespace mobius
