#include "athena/SpriteFileReader.hpp"
#include "athena/SpriteFile.hpp"
#include "athena/Sprite.hpp"
#include "athena/SpritePart.hpp"
#include "athena/SpriteFrame.hpp"
#include "athena/Utility.hpp"

namespace athena::io {
SpriteFileReader::SpriteFileReader(uint8_t* data, uint64_t length) : MemoryCopyReader(data, length) {}

SpriteFileReader::SpriteFileReader(const std::string& filepath) : MemoryCopyReader(filepath) {}

Sakura::SpriteFile* SpriteFileReader::readFile() {
  Sakura::SpriteFile* ret = NULL;

  uint32_t magic = readUint32();

  if (magic != Sakura::SpriteFile::Magic) {
    atError("Not a valid Sakura Sprite container");
    return nullptr;
  }

  uint32_t version = readUint32();

  // TODO: Make this more verbose
  if (version != Sakura::SpriteFile::Version) {
    atError("Unsupported version");
    return nullptr;
  }

  // After reading in the magic and version we need to load some
  // metadata about the file.
  // Such as the texture count, it's dimensions, and it's origin.
  // After that we have the number of sprites contained in this
  // sprite container.
  uint16_t textureCount =
      readUint16(); // Having it as a Uint16 gives us the ability to have up to 65536 different states
  // This is probably overkill, but it's better safe than sorry.
  uint32_t width = readUint32();
  uint32_t height = readUint32();
  float originX = readFloat();
  float originY = readFloat();
  uint16_t spriteCount = readUint16();

  // Lets go ahead and create or new container.
  ret = new Sakura::SpriteFile(width, height, originX, originY);

  // The next four bytes are reserved to keep the header 32 byte aligned.
  // This isn't necessary for most systems, but it's eventually planned
  // to migrate this code to Big Endian based systems, such as the wii
  // which require data to be 32 byte aligned, or it causes some issues.
  // It's also convenient to have this, for later expansion.
  uint32_t reserved = readUint32();

  // Next we have to load the textures
  // If we tried to add them one at a time to the sprite container
  // it will be slow as hell, so we store them in a vector locally
  // then give that vector the the container, this bypasses the de-reference
  // for each texture
#ifndef ATHENA_USE_QT
  std::vector<Sakura::STexture*> textures;
#else
  QList<Sakura::STexture*> textures;
#endif

  for (uint16_t i = 0; i < textureCount; i++) {
    Sakura::STexture* texture = new Sakura::STexture;
    texture->Filepath = readString();
    texture->Preload = readBool();
    textures.push_back(texture);
  }

  ret->setTextures(textures);

  // Now for the sprites
  // The sprites are a bit more difficult, they are stored in an unordered_map
  // with it's name as the key, this means we can't have two sprites with the same name
  // Normally this isn't a problem, but someone may decide to copy and paste a sprite
  // and forget to change the name, that needs to be handled, but it's outside the scope
  // of this reader.
#ifndef ATHENA_USE_QT
  std::unordered_map<std::string, Sakura::Sprite*> sprites;
#else
  QMap<QString, Sakura::Sprite*> sprites;
#endif

  for (uint16_t i = 0; i < spriteCount; i++) {
    Sakura::Sprite* sprite = new Sakura::Sprite(ret);
#ifndef ATHENA_USE_QT
    std::string name = readString();
#else
    QString name = QString::fromStdString(readString());
#endif
    sprite->setName(name);
    uint16_t frameCount = readUint16();
    uint16_t stateCount = readUint16();

    // Each state id corresponds to a texture held in the parent class
    std::vector<int> stateIds;

    for (int j = 0; j < stateCount; j++)
      stateIds.push_back(readUint16());

    sprite->setStateIds(stateIds);

    // Now to read the sprite parts.
    // The parts allow us to build retro style sprites very easily
    // making it possible to use one texture atlas for all possible
    // frame combinations, this reduces the amount of memory overhead
    // and the storage footprint, while Sakura supports packs and zips
    // it's still a bad idea to have a metric ton of texture resources
    // littering the place
#ifndef ATHENA_USE_QT
    std::vector<Sakura::SpriteFrame*> frames;
#else
    QList<Sakura::SpriteFrame*> frames;
#endif

    for (uint32_t k = 0; k < frameCount; k++) {
      Sakura::SpriteFrame* frame = new Sakura::SpriteFrame(sprite);
      frame->setFrameTime(readFloat());
      uint16_t partCount = readUint16();

#ifndef ATHENA_USE_QT
      std::vector<Sakura::SpritePart*> parts;
#else
      QList<Sakura::SpritePart*> parts;
#endif

      for (uint8_t j = 0; j < partCount; j++) {
        Sakura::SpritePart* part = new Sakura::SpritePart(frame);
#ifndef ATHENA_USE_QT
        std::string name = readString();
#else
        QString name = QString::fromStdString(readString());
#endif
        part->setName(name);
        part->setCollision(readBool());

        float xOff = readFloat();
        float yOff = readFloat();
        part->setOffset(xOff, yOff);
        float texXOff = readFloat();
        float texYOff = readFloat();
        part->setTextureOffset(texXOff, texYOff);
        uint32_t width = readUint32();
        uint32_t height = readUint32();
        part->setSize(width, height);
        bool flippedH = readBool();
        part->setFlippedHorizontally(flippedH);
        bool flippedV = readBool();
        part->setFlippedVertically(flippedV);

        parts.push_back(part);
      }

      frame->setParts(parts);
      frames.push_back(frame);
    }

    sprite->setFrames(frames);
#ifndef ATHENA_USE_QT

    if (sprite->name() != std::string()) {
      std::string nameLow(sprite->name());
      athena::utility::tolower(nameLow);
      sprites[nameLow] = sprite;
    }

#else

    if (!sprite->name().isEmpty())
      sprites[sprite->name().toLower()] = sprite;

#endif
    else {
      atError("Sprite names cannot be empty");
      return nullptr;
    }
  }

  ret->setSprites(sprites);

  return ret;
}
} // namespace athena::io
