#pragma once

#include <nall/file.hpp>
#include <nall/filemap.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>

namespace nall {

struct bpsmetadata {
  inline auto load(const string& filename) -> bool;
  inline auto save(const string& filename, const string& metadata) -> bool;
  inline auto metadata() const -> string;

protected:
  file sourceFile;
  string metadataString;
};

auto bpsmetadata::load(const string& filename) -> bool {
  if(sourceFile.open(filename, file::mode::read) == false) return false;

  auto read = [&]() -> uint8_t {
    return sourceFile.read();
  };

  auto decode = [&]() -> uint64_t {
    uint64_t data = 0, shift = 1;
    while(true) {
      uint8_t x = read();
      data += (x & 0x7f) * shift;
      if(x & 0x80) break;
      shift <<= 7;
      data += shift;
    }
    return data;
  };

  if(read() != 'B') return false;
  if(read() != 'P') return false;
  if(read() != 'S') return false;
  if(read() != '1') return false;
  decode();
  decode();
  uint metadataSize = decode();
  char data[metadataSize + 1];
  for(uint n = 0; n < metadataSize; n++) data[n] = read();
  data[metadataSize] = 0;
  metadataString = (const char*)data;

  return true;
}

auto bpsmetadata::save(const string& filename, const string& metadata) -> bool {
  file targetFile;
  if(targetFile.open(filename, file::mode::write) == false) return false;
  if(sourceFile.open() == false) return false;
  sourceFile.seek(0);

  auto read = [&]() -> uint8_t {
    return sourceFile.read();
  };

  auto decode = [&]() -> uint64_t {
    uint64_t data = 0, shift = 1;
    while(true) {
      uint8_t x = read();
      data += (x & 0x7f) * shift;
      if(x & 0x80) break;
      shift <<= 7;
      data += shift;
    }
    return data;
  };

  Hash::CRC32 checksum;

  auto write = [&](uint8_t data) {
    targetFile.write(data);
    checksum.input(data);
  };

  auto encode = [&](uint64_t data) {
    while(true) {
      uint64_t x = data & 0x7f;
      data >>= 7;
      if(data == 0) {
        write(0x80 | x);
        break;
      }
      write(x);
      data--;
    }
  };

  for(uint n = 0; n < 4; n++) write(read());
  encode(decode());
  encode(decode());
  uint sourceLength = decode();
  uint targetLength = metadata.length();
  encode(targetLength);
  sourceFile.seek(sourceLength, file::index::relative);
  for(uint n = 0; n < targetLength; n++) write(metadata[n]);
  uint length = sourceFile.size() - sourceFile.offset() - 4;
  for(uint n = 0; n < length; n++) write(read());
  uint32_t outputChecksum = checksum.digest().hex();
  for(uint n = 0; n < 32; n += 8) write(outputChecksum >> n);

  targetFile.close();
  return true;
}

auto bpsmetadata::metadata() const -> string {
  return metadataString;
}

}
