// MIT License
// Copyright (c) 2026 by Peter Siegmund (mars3142)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include "cartridge/cartridge.h"
#include "cartridge/binary_reader.h"
#include "cartridge/lat_lng.h"
#include "cartridge/media.h"

namespace cartridge {

Cartridge::Cartridge(std::string cartridgeGuid, double altitude,
                     std::string author, std::string cartridgeDesc,
                     std::string cartridgeName, std::string company,
                     std::string completionCode, LatLng latLng,
                     std::string playerName, std::string recommendDevice,
                     int smallIconId, int splashScreenId,
                     std::string startLocationDesc, std::string typeOfCartridge,
                     std::string version, std::map<int, int> references,
                     std::vector<uint8_t> bytes)
    : m_cartridgeGuid(std::move(cartridgeGuid)), m_altitude(altitude),
      m_author(std::move(author)), m_cartridgeDesc(std::move(cartridgeDesc)),
      m_cartridgeName(std::move(cartridgeName)), m_company(std::move(company)),
      m_completionCode(std::move(completionCode)), m_latLng(latLng),
      m_playerName(std::move(playerName)),
      m_recommendDevice(std::move(recommendDevice)), m_smallIconId(smallIconId),
      m_splashScreenId(splashScreenId),
      m_startLocationDesc(std::move(startLocationDesc)),
      m_typeOfCartridge(std::move(typeOfCartridge)),
      m_version(std::move(version)), m_references(std::move(references)),
      m_bytes(std::move(bytes)) {}

std::unique_ptr<Cartridge> Cartridge::create(BinaryReader &reader) {
  try {
    const uint16_t count = reader.getUShort();
    std::map<int, int> references;
    for (uint16_t index = 0; index < count; ++index) {
      const int objectId = reader.getShort();
      const int address = reader.getLong();
      references.emplace(objectId, address);
    }

    reader.getLong(); // header length
    const double latitude = reader.getDouble();
    const double longitude = reader.getDouble();
    const double altitude = reader.getDouble();
    reader.getLong(); // unknown 0
    reader.getLong(); // unknown 1
    const int splashScreenId = reader.getShort();
    const int smallIconId = reader.getShort();
    const std::string typeOfCartridge = reader.getASCIIZ();
    const std::string playerName = reader.getASCIIZ();
    reader.getLong(); // unknown 2
    reader.getLong(); // unknown 3
    const std::string cartridgeName = reader.getASCIIZ();
    const std::string cartridgeGuid = reader.getASCIIZ();
    const std::string cartridgeDesc = reader.getASCIIZ();
    const std::string startLocationDesc = reader.getASCIIZ();
    const std::string version = reader.getASCIIZ();
    const std::string author = reader.getASCIIZ();
    const std::string company = reader.getASCIIZ();
    const std::string recommendedDevice = reader.getASCIIZ();
    reader.getLong(); // unknown 4
    const std::string completionCode = reader.getASCIIZ();

    // Rohdaten aus dem Reader extrahieren
    const auto bytes_span = reader.data();
    const std::vector<uint8_t> bytes(bytes_span.begin(), bytes_span.end());

    return std::make_unique<Cartridge>(
        cartridgeGuid, altitude, author, cartridgeDesc, cartridgeName, company,
        completionCode, LatLng(latitude, longitude), playerName,
        recommendedDevice, smallIconId, splashScreenId, startLocationDesc,
        typeOfCartridge, version, references, bytes);
  } catch (const std::exception &) {
    return nullptr;
  }
}

double Cartridge::altitude() const { return m_altitude; }
const std::string &Cartridge::author() const { return m_author; }
const LatLng &Cartridge::latLng() const { return m_latLng; }
const std::string &Cartridge::cartridgeName() const { return m_cartridgeName; }
const std::string &Cartridge::cartridgeDesc() const { return m_cartridgeDesc; }
const std::string &Cartridge::cartridgeGuid() const { return m_cartridgeGuid; }
const std::string &Cartridge::typeOfCartridge() const {
  return m_typeOfCartridge;
}
const std::string &Cartridge::playerName() const { return m_playerName; }
const std::string &Cartridge::startLocationDesc() const {
  return m_startLocationDesc;
}
const std::string &Cartridge::version() const { return m_version; }
const std::string &Cartridge::company() const { return m_company; }
const std::string &Cartridge::recommendDevice() const {
  return m_recommendDevice;
}
const std::string &Cartridge::completionCode() const { return m_completionCode; }

std::unique_ptr<Media> Cartridge::getMedia(const int objectId) {
  if (m_lastObject == objectId && m_lastMedia) {
    return std::make_unique<Media>(*m_lastMedia);
  }
  if (auto it = m_references.find(objectId); it != m_references.end()) {
    const int address = it->second;
    BinaryReader reader(m_bytes, Endian::Little);
    auto media = Media::create(reader, objectId, address);
    if (media && media->getData().size() < 128000) {
      m_lastObject = objectId;
      m_lastMedia = std::make_unique<Media>(*media);
    }
    return media;
  }
  return nullptr;
}

int Cartridge::mediaCount() const {
  return static_cast<int>(m_references.size());
}

std::unique_ptr<Media> Cartridge::splashScreen() {
  return getMedia(m_splashScreenId);
}
std::unique_ptr<Media> Cartridge::smallIcon() { return getMedia(m_smallIconId); }
std::unique_ptr<Media> Cartridge::luac() { return getMedia(0); }

bool Cartridge::operator==(const Cartridge &other) const {
  return m_cartridgeGuid == other.m_cartridgeGuid;
}
bool Cartridge::operator!=(const Cartridge &other) const {
  return !(*this == other);
}

} // namespace cartridge