#pragma once

#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/binary_reader.h>
#include <frostbite2D/resource/asset.h>
#include <optional>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <memory>
#include <zlib.h>

namespace frostbite2D {

struct ImageFrame {
  int32 type = 0;
  int32 compressionType = 0;
  int32 width = 0;
  int32 height = 0;
  int32 xPos = 0;
  int32 yPos = 0;
  int32 frameXPos = 0;
  int32 frameYPos = 0;
  uint32 offset = 0;
  int32 size = 0;
  std::vector<uint8> data;
};

struct ImgRef {
  std::string path;
  std::string npkFile;
  size_t frameCount = 0;
  uint32 offset = 0;
  uint32 size = 0;
  bool loaded = false;
};

struct CachedImageData {
  std::vector<ImageFrame> frames;
  uint64 lastUseTime = 0;
  size_t memoryUsage = 0;
};

class NpkArchive {
public:
  static NpkArchive& get();

  NpkArchive(const NpkArchive&) = delete;
  NpkArchive& operator=(const NpkArchive&) = delete;
  NpkArchive(NpkArchive&&) = delete;
  NpkArchive& operator=(NpkArchive&&) = delete;

  void setImagePackDirectory(const std::string& dir);
  const std::string& getImagePackDirectory() const;

  void init();
  void close();
  bool isOpen() const;

  bool hasImg(const std::string& path) const;
  std::optional<ImgRef> getImg(const std::string& path);
  std::vector<std::string> listImgs() const;

  std::optional<ImageFrame> getImageFrame(const ImgRef& img, size_t index);
  size_t getFrameCount(const ImgRef& img) const;

  void setCacheSize(size_t maxBytes);
  void clearCache();
  size_t getCacheUsage() const;

  void setDefaultImg(const std::string& imgPath, size_t frameIndex = 0);
  const std::string& getDefaultImgPath() const;
  size_t getDefaultImgFrame() const;

private:
  NpkArchive() = default;
  ~NpkArchive() = default;

  std::string normalizePath(const std::string& path) const;
  void scanNpkFiles();
  bool parseNpkFile(const std::string& npkPath);
  bool loadImgData(ImgRef& img);
  void parseColor(const uint8* tab, int type, uint8* saveByte, int offset);
  void evictCacheIfNeeded(size_t requiredSize);
  void updateCacheUsage(const std::string& imgPath);
  std::string readNpkInfoString(BinaryReader& reader);

  static const uint8 NPK_KEY[256];

  std::string imagePackDirectory_ = "ImagePacks2";
  bool initialized_ = false;
  std::map<std::string, ImgRef> imgIndex_;
  std::map<std::string, CachedImageData> imageCache_;
  std::list<std::string> lruList_;
  size_t maxCacheSize_ = 512 * 1024 * 1024;
  size_t currentCacheSize_ = 0;
  std::string defaultImgPath_;
  size_t defaultImgFrame_ = 0;
};

}
