
#include <iostream>

#include <tsystem.h>
#include <tconvert.h>
#include <tiio.h>

#include <tmetaimage.h>
#include <toonz/txshsimplelevel.h>

#include "tiio_tzm.h"



//===========================================================================

namespace {
  class TLevelWriterTzm;

  class TImageWriterTzm final : public TImageWriter {
  private:
    TLevelWriterTzm &m_writer;
    TFrameId m_frameId;
  public:
    TImageWriterTzm(const TFilePath &path, TLevelWriterTzm &writer, const TFrameId &frameId):
      TImageWriter(path), m_writer(writer), m_frameId(frameId) { }
    void save(const TImageP &image) override;
  };

  class TLevelWriterTzm final : public TLevelWriter {
  private:
    TVariant m_data;
    Tofstream *m_stream;

  public:
    TLevelWriterTzm(const TFilePath &path, TPropertyGroup *winfo):
      TLevelWriter(path, winfo)
    {
      // lock file for whole time of saving process to avoid collisions
      try {
        m_stream = new Tofstream(path);
      } catch (const std::exception &e) {
        throw TImageException(path, e.what());
      } catch (const TException &e) {
        throw TImageException(path, to_string(e.getMessage()));
      }

      m_data["type"].setString("tzm");
      m_data["version"].setDouble( 0.0 );
    }

    TImageWriterP getFrameWriter(TFrameId frameId) override
      { return TImageWriterP(new TImageWriterTzm(m_path, *this, frameId)); }

    void saveFrame(const TFrameId &frameId, const TImageP &image) {
      if (const TMetaImage *metaImage = dynamic_cast<const TMetaImage*>(image.getPointer())) {
        TMetaImage::Reader reader(*metaImage);
        TVariant &frameData = m_data["frames"][frameId.expand()];
        TVariant &objectsData = frameData["objects"];
        objectsData.setType(TVariant::List);
        for(TMetaObjectListCW::iterator i = reader->begin(); i != reader->end(); ++i) {
          if (*i) {
            TVariant &objectData = objectsData[ objectsData.size() ];
            objectData["type"].setString( (*i)->getTypeName() );
            objectData["data"] = (*i)->data();
          }
        }
      }
    }

    ~TLevelWriterTzm() {
      try {
        m_data["creator"].setString( m_creator.toStdString() );
        m_data.toStream(*m_stream, true);
        delete m_stream;
      } catch (const std::exception &e) {
        throw TImageException(m_path, e.what());
      } catch (const TException &e) {
        throw TImageException(m_path, to_string(e.getMessage()));
      }
    }
  };

  void TImageWriterTzm::save(const TImageP &image)
    { m_writer.saveFrame(m_frameId, image); }
} // end of anonymous namespace for TLevelWriterTzm

//===========================================================================

namespace {
  class TLevelReaderTzm;

  class TImageReaderTzm final : public TImageReader {
  private:
    TLevelReaderTzm &m_reader;
    TFrameId m_frameId;
  public:
    TImageReaderTzm(const TFilePath &path, TLevelReaderTzm &reader, const TFrameId &frameId):
      TImageReader(path), m_reader(reader), m_frameId(frameId) { }
    TImageP load() override;
  };


  class TLevelReaderTzm final : public TLevelReader {
  private:
    TVariant m_data;
    TLevelP m_level;

    void warning(const std::string &msg)
      { std::cerr << to_string(m_path.getWideString()) << ": " << msg << std::endl; }

  public:
    TLevelReaderTzm(const TFilePath &path):
      TLevelReader(path) { }

    TImageReaderP getFrameReader(TFrameId frameId) override
      { return TImageReaderP(new TImageReaderTzm(m_path, *this, frameId)); }

    TLevelP loadInfo() override {
      try {
        Tifstream stream(m_path);
        m_data.fromStream(stream);
      } catch (const std::exception &e) {
        throw TImageException(m_path, e.what());
      } catch (const TException &e) {
        throw TImageException(m_path, to_string(e.getMessage()));
      }

      if (m_data["type"].getString() != "tzm")
        warning("seems it's not TZM");
      if (m_data["version"].getDouble() > 0.0 + TConsts::epsilon)
        warning( "version ("
               + std::to_string(m_data["version"].getDouble())
               + ") is higher than supported (0.0)");

      const TVariantMap &map = m_data["frames"].getMap();
      for(TVariantMap::const_iterator i = map.begin(); i != map.end(); ++i) {
        TFrameId frameId(i->first.str());
        if (frameId.getNumber() < 0)
          warning("wrong frame number: " + i->first.str());
        else
        if (m_level->getTable()->count(frameId))
          warning(frameId.expand());
        else
          m_level->setFrame(frameId, TImageP());
      }

      return m_level;
    }

    QString getCreator() override
      { return QString::fromStdString( m_data["creator"].getString() ); }

    TImageP loadFrame(const TFrameId &frameId) {
      const TVariantMap &map = m_data["frames"].getMap();
      for(TVariantMap::const_iterator i = map.begin(); i != map.end(); ++i) {
        if (TFrameId(i->first.str()) == frameId) {
          TMetaImage *image = new TMetaImage();
          TMetaImage::Writer writer(*image);
          const TVariant &objectsData = i->second["objects"];
          if (objectsData.getType() == TVariant::List) {
            for(int j = 0; j < objectsData.size(); ++j) {
              const TVariant &objectData = objectsData[j];
              if (!objectData["type"].getString().empty()) {
                TMetaObjectP obj( new TMetaObject(objectData["type"].getString()) );
                obj->data() = objectData["data"];
                writer->push_back(obj);
              }
            }
          }
          return image;
        }
      }
      return TImageP();
    }
  };

  TImageP TImageReaderTzm::load()
    { return m_reader.loadFrame(m_frameId); }
} // end of anonymous namespace for TLevelReaderTzm

//===========================================================================

namespace tzm {
  TLevelWriter* createWriter(const TFilePath &path, TPropertyGroup *winfo)
    { return new TLevelWriterTzm(path, winfo); }
  TLevelReader* createReader(const TFilePath &path)
    { return new TLevelReaderTzm(path); }
}

//=============================================================================
