// Copyright (c) 2022 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#include "SaveManager.hpp"
#include "game/DawnGame.hpp"

using namespace Dawn;

SaveManager::SaveManager(DawnGame *game) {
  this->game = game;
}

void SaveManager::saveFile() {
  savedata_t value;

  assertTrue(this->currentSaveSlot >= 0);
  
  // Update metadata
  auto timestamp = this->game->timeManager.getTimestamp();
  value.i64 = timestamp;
  this->currentSave.set(SAVE_KEY_TIMESTAMP, value);
  value.i16 = this->currentSaveSlot;
  this->currentSave.set(SAVE_KEY_SLOT, value);

  // Now open output buffer.
  char filename[SAVE_MANAGER_FILENAME_LENGTH];
  sprintf(filename, "savefile_%u.save", this->currentSaveSlot);
  FILE *fptr = fopen(filename, "wb");
  if(fptr == NULL) {
    printf("Error opening %s\n", filename);
    assertUnreachable();
    return;
  }

  // Now begin buffering data. First we start with the common phrase, the engine
  // version, the timestamp. In future we will store the MD5 here too.
  char buffer[SAVE_MANAGER_BUFFER_LENGTH];
  auto len = sprintf(buffer, "DE_SAVE|%s|%lld|", "1.00", timestamp);
  fwrite(buffer, sizeof(char), len, fptr);

  // Now buffer out the data, this is almost certain to change over time.
  auto it = this->currentSave.values.begin();
  while(it != this->currentSave.values.end()) {
    uint8_t *buffer = (uint8_t *)&it->second;
    char delim = '|';
    fwrite(it->first.c_str(), sizeof(char), strlen(it->first.c_str()), fptr);
    fwrite(&delim, sizeof(char), 1, fptr);
    fwrite(buffer, sizeof(uint8_t), sizeof(savedata_t) / sizeof(uint8_t), fptr);
    fwrite(&delim, sizeof(char), 1, fptr);
    ++it;
  }

  // Buffering done, close the file buffer.
  fclose(fptr);
  this->currentSave.hasChanges = false;
}

enum SaveLoadResult SaveManager::loadFile() {
  this->currentSave.reset();

  assertTrue(this->currentSaveSlot >= 0);

  // Load file
  struct SaveFile temporaryFile;

  char filename[SAVE_MANAGER_FILENAME_LENGTH];
  sprintf(filename, "savefile_%u.save", this->currentSaveSlot);
  FILE *fptr = fopen(filename, "rb");
  if(fptr == NULL) {
    return SAVE_LOAD_RESULT_FILE_NOT_PRESENT;
  }

  // Let's read how long the file is first.
  fseek(fptr, 0, SEEK_END);
  auto len = ftell(fptr);

  // Now let's just ensure the file isn't too small, this starts by us making
  // sure we have at least "DE_SAVE|%s|%lld|", the string is unknown length, 
  // but we can be 100% sure of the rest of the file size. So we take
  // 7+1+1+1+19+1=30
  if(len < 30) {
    fclose(fptr);
    return SAVE_LOAD_RESULT_TOO_SMALL;
  }
  
  // Rewind.
  fseek(fptr, 0, SEEK_SET);

  // Ok let's buffer the first 8 characters to validate "DE_SAVE|"
  char buffer[SAVE_MANAGER_BUFFER_LENGTH];
  auto read = fread(buffer, sizeof(char), SAVE_MANAGER_BUFFER_LENGTH, fptr);
  if(
    read < 8 ||
    buffer[0] != 'D' ||
    buffer[1] != 'E' ||
    buffer[2] != '_' ||
    buffer[3] != 'S' ||
    buffer[4] != 'A' ||
    buffer[5] != 'V' ||
    buffer[6] != 'E' ||
    buffer[7] != '|'
  ) {
    fclose(fptr);
    return SAVE_LOAD_RESULT_CORRUPTED_DE_SAVE;
  }

  // Now read ahead to the next vertical bar, that will give us the engine
  // version
  char *bufferCurrent = buffer + 8;
  char *p = stringFindNext(bufferCurrent, '|', 10);
  if(p == NULL) {
    fclose(fptr);
    return SAVE_LOAD_RESULT_CORRUPTED_VERSION;
  }
  *p = '\0';
  char *version = bufferCurrent;
  bufferCurrent = p + 1;

  // Now read the timestamp string.
  p = stringFindNext(bufferCurrent, '|', 64);
  if(p == NULL) {
    fclose(fptr);
    return SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP;
  }
  *p = '\0';
  char *strTimestamp = bufferCurrent;
  
  // Parse timestamp
  int64_t timestamp = strtoll(strTimestamp, NULL, 10);
  if(timestamp == 0) {
    fclose(fptr);
    return SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP;
  }
  
  // Now begin parsing the data in the save file. We start here, at the end of
  // the previous string, so it will INCLUDE "|" at the first char.
  read = p - buffer;
  while(read < (len-1)) {
    // Rewind and then buffer the next set of bytes
    fseek(fptr, (long)read, SEEK_SET);
    if(fread(buffer, sizeof(char), SAVE_MANAGER_BUFFER_LENGTH, fptr) <= 1) {
      break;
    }

    // Because we finish the last string INCLUDING "|", then we skip it here.
    bufferCurrent = buffer + 1;

    // Now, read the key
    p = stringFindNext(bufferCurrent, '|', SAVE_MANAGER_BUFFER_LENGTH);
    if(p == NULL) {
      fclose(fptr);
      return SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY;
    }

    *p = '\0';
    char *key = bufferCurrent;

    // Validate the string lengths
    if(strlen(key) <= 0) {
      fclose(fptr);
      return SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY;
    }

    // Now read the value
    bufferCurrent = p + 1;
    savedata_t destination;
    uint8_t *ptrValue = (uint8_t*)bufferCurrent;
    memoryCopy(ptrValue, &destination, sizeof(savedata_t));

    // Now advance the pointer...
    p = bufferCurrent + sizeof(savedata_t);
    if(*p != '|') {
      fclose(fptr);
      return SAVE_LOAD_RESULT_CORRUPTED_DATA_VALUE;
    }

    // Set the value.
    temporaryFile.set(std::string(key), destination);
    read += (p - buffer);
  }

  // Close file
  fclose(fptr);

  // OK Let's validate that everything was read OK
  if(temporaryFile.get(SAVE_KEY_TIMESTAMP).i64 != timestamp) {
    return SAVE_LOAD_RESULT_MISMATCH_TIMESTAMP;
  }

  // Now pass off to the loader
  if(this->validateSave(temporaryFile) != false) {
    return SAVE_LOAD_RESULT_ERROR;
  }

  // Unmark changes. In future I may need to do more complex testing, e.g. if
  // the game has a newer version that updates save files some how.
  this->currentSave.hasChanges = false;
  return SAVE_LOAD_SUCCESS;
}

void SaveManager::deleteFile(int16_t slot) {
  assertTrue(slot >= 0);
}

void SaveManager::useSlot(int16_t slot) {
  assertTrue(slot >= 0);
  this->currentSaveSlot = slot;
  this->currentSave.reset();
}

std::vector<int16_t> SaveManager::getUsedSlots() {
  std::vector<int16_t> slots;

  return slots;
}