#include "object.h"

#include <iostream>

#include "othertypes.h"
#include "util.h"

namespace si {

Object::Object()
{
  type_ = MxOb::Null;
  id_ = 0;
  last_chunk_split_ = false;
}

bool Object::ReplaceWithFile(const char *f)
{
  std::ifstream is(f);
  if (!is.is_open() || !is.good()) {
    return false;
  }
  return ReplaceWithFile(is);
}

bool Object::ExtractToFile(const char *f) const
{
  std::ofstream os(f);
  if (!os.is_open() || !os.good()) {
    return false;
  }
  return ExtractToFile(os);
}

bool Object::ReplaceWithFile(std::istream &is)
{
  data_.clear();

  switch (this->filetype()) {
  case MxOb::WAV:
  {
    if (ReadU32(is) != RIFF::RIFF_) {
      return false;
    }

    // Skip total size
    ReadU32(is);

    if (ReadU32(is) != RIFF::WAVE) {
      return false;
    }

    bytearray fmt;
    bytearray data;

    while (is.good()) {
      uint32_t id = ReadU32(is);
      uint32_t sz = ReadU32(is);
      if (id == RIFF::fmt_) {
        fmt.resize(sz);
        is.read(fmt.data(), fmt.size());
      } else if (id == RIFF::data) {
        data.resize(sz);
        is.read(data.data(), data.size());
      } else {
        is.seekg(sz, std::ios::cur);
      }
    }

    if (fmt.empty() || data.empty()) {
      return false;
    }

    data_.push_back(fmt);
    WAVFmt *fmt_info = fmt.cast<WAVFmt>();
    size_t second_in_bytes = fmt_info->Channels * fmt_info->SampleRate * (fmt_info->BitsPerSample/8);
    size_t max;
    for (size_t i=0; i<data.size(); i+=max) {
      max = std::min(data.size() - i, second_in_bytes);
      data_.push_back(bytearray(data.data() + i, max));
    }

    return true;
  }
  case MxOb::SMK:
  {
    // Read header
    bytearray hdr(sizeof(SMK2));
    is.read(hdr.data(), hdr.size());

    // Read frame sizes
    SMK2 smk = *hdr.cast<SMK2>();
    bytearray frame_sizes(smk.Frames * sizeof(uint32_t));
    is.read(frame_sizes.data(), frame_sizes.size());
    hdr.append(frame_sizes);

    // Read frame types
    bytearray frame_types(smk.Frames);
    is.read(frame_types.data(), frame_types.size());
    hdr.append(frame_types);

    // Read Huffman trees
    bytearray huffman(smk.TreesSize);
    is.read(huffman.data(), huffman.size());
    hdr.append(huffman);

    // Place header into data vector
    data_.resize(smk.Frames + 1);
    data_[0] = hdr;

    uint32_t *real_sizes = frame_sizes.cast<uint32_t>();
    for (uint32_t i=0; i<smk.Frames; i++) {
      uint32_t sz = real_sizes[i];
      if (sz > 0) {
        bytearray &d = data_[i+1];
        d.resize(sz);
        is.read(d.data(), d.size());
      }
    }
    return true;
  }
  default:
    LogWarning() << "Don't yet know how to chunk type " << RIFF::PrintU32AsString(this->filetype()) << std::endl;
    break;
  }

  return false;
}

bool Object::ExtractToFile(std::ostream &os) const
{
  switch (this->filetype()) {
  case MxOb::WAV:
  {
    // Write RIFF header
    RIFF::Chk riff = RIFF::BeginChunk(os, RIFF::RIFF_);

    WriteU32(os, RIFF::WAVE);

    {
      RIFF::Chk fmt = RIFF::BeginChunk(os, RIFF::fmt_);

      WriteBytes(os, data_.at(0));

      RIFF::EndChunk(os, fmt);
    }

    {
      RIFF::Chk data = RIFF::BeginChunk(os, RIFF::data);
      // Merge all chunks after the first one
      for (size_t i=1; i<data_.size(); i++) {
        WriteBytes(os, data_.at(i));
      }
      RIFF::EndChunk(os, data);
    }

    RIFF::EndChunk(os, riff);
    break;
  }
  case MxOb::STL:
  {
    static const uint32_t BMP_HDR_SZ = 14;

    // Write BMP header
    WriteU16(os, 0x4D42);

    // Write placeholder for size
    std::ios::pos_type sz_loc = os.tellp();
    WriteU32(os, 0);

    // Write "reserved" bytes
    WriteU32(os, 0);

    // Write data offset
    WriteU32(os, data_.at(0).size() + BMP_HDR_SZ);

    for (size_t i=0; i<data_.size(); i++) {
      WriteBytes(os, data_.at(i));
    }

    std::ios::pos_type len = os.tellp();
    os.seekp(sz_loc);
    WriteU32(os, len);
    break;
  }
  case MxOb::FLC:
  {
    // First chunk is a complete FLIC header, so add it as-is
    WriteBytes(os, data_.at(0));

    // Subsequent chunks are FLIC frames with an additional 20 byte header that needs to be stripped
    const int CUSTOM_HEADER_SZ = 20;
    for (size_t i=1; i<data_.size(); i++) {
      os.write(data_.at(i).data() + CUSTOM_HEADER_SZ, data_.at(i).size() - CUSTOM_HEADER_SZ);
    }
    break;
  }
  default:
    LogWarning() << "Didn't know how to extract type '" << RIFF::PrintU32AsString(filetype()) << "', merging..." << std::endl;
    /* fall-through */
  case MxOb::SMK:
  case MxOb::OBJ:
    // Simply merge
    for (size_t i=0; i<data_.size(); i++) {
      WriteBytes(os, data_.at(i));
    }
    break;
  }

  return true;
}

bytearray Object::ExtractToMemory() const
{
  memorybuf buf;
  std::ostream os(&buf);

  ExtractToFile(os);

  return buf.data();
}

const bytearray &Object::GetFileHeader() const
{
  return data_.at(0);
}

bytearray Object::GetFileBody() const
{
  bytearray b;

  for (size_t i=1; i<data_.size(); i++) {
    b.append(data_.at(i));
  }

  return b;
}

size_t Object::GetFileBodySize() const
{
  size_t s = 0;

  for (size_t i=1; i<data_.size(); i++) {
    s += data_.at(i).size();
  }

  return s;
}

Object *Object::FindSubObjectWithID(uint32_t id)
{
  if (this->id() == id) {
    return this;
  }

  for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
    if (Object *o = static_cast<Object*>(*it)->FindSubObjectWithID(id)) {
      return o;
    }
  }

  return NULL;
}

}