#include "PGNGameBase.hpp"
#include <wx/stdpaths.h>

PGNGameBase::PGNGameBase(std::string pgn_file) : pgn(new pgnp::PGN()) {
  file = pgn_file;
  pgn->FromFile(pgn_file);
}

PGNGameBase::~PGNGameBase() {
  delete pgn; // Should never fail since pgn is never NULL
}

bool PGNGameBase::NextGame() {
  bool game_found = false;
  try {
    pgn->ParseNextGame();
    game_found = true;
  } catch (...) {
    game_found = false;
  }
  return (game_found);
}

std::string PGNGameBase::GetTag(std::string tag) {
  if (pgn->HasTag(tag)) {
    return (pgn->GetTagValue(tag));
  }
  return ("");
}

void PGNGameBase::CreateDatabaseFile(std::string path){
  wxFile empty_pgn(path, wxFile::write);
  empty_pgn.Close();
}

std::shared_ptr<Game> PGNGameBase::GetCurrentGame() {
  pgnp::HalfMove *pgnp_moves = new pgnp::HalfMove();
  pgn->GetMoves(pgnp_moves);
  std::string fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  if (pgn->HasTag("FEN")) {
    fen = pgn->GetTagValue("FEN");
  }

  // Init game object
  Game *g;
  if(pgnp_moves->count == -1){ // Check if PGN contains at least a move
    g=new Game(fen);
  }
  else {
    HalfMove *m = new HalfMove(pgnp_moves);
    m->SetFen(fen);
    g=new Game(m, fen);
  }
  // Setup game object
  for (std::string &s : pgn->GetTagList()) {
    g->SetTag(s, pgn->GetTagValue(s));
  }
  g->SetResult(pgn->GetResult());
  // Finish
  return (std::shared_ptr<Game>(g));
}

void PGNGameBase::Reset() {
  delete pgn;
  pgn = new pgnp::PGN();
  pgn->FromFile(file);
}

std::shared_ptr<Game> PGNGameBase::GetGame(std::uint32_t id) {
  Reset();
  std::uint32_t curid = 0;
  while (NextGame()) {
    if (id == curid) {
      return (GetCurrentGame());
    }
    curid++;
  }
  return nullptr;
}

void PGNGameBase::Save(std::vector<std::uint32_t> to_delete,
                       std::vector<std::string> databases_to_import,
                       std::vector<std::shared_ptr<Game>> games_to_import) {
  wxStandardPaths stdPaths = wxStandardPaths::Get();
  wxString tmp = stdPaths.GetTempDir() + "/save_pgn_tmp.pgn";
  wxFile new_pgn(tmp, wxFile::write);

  Reset();
  std::uint32_t id = 0;
  bool several = false;
  while (NextGame()) {
    if (std::find(to_delete.begin(), to_delete.end(), id) == to_delete.end()) {
      if (several) {
        new_pgn.Write("\n\n");
      } else {
        several = true;
      }
      std::shared_ptr<Game> g = GetCurrentGame();
      new_pgn.Write(GetPGN(g));
    }
    id++;
  }

  // Now add new games
  for(auto dbpath: databases_to_import){
    std::shared_ptr<GameBase> current=OpenDatabase(dbpath);
    while (current->NextGame()) {
      if (several) {
        new_pgn.Write("\n\n");
      } else {
        several = true;
      }
      std::shared_ptr<Game> g = current->GetCurrentGame();
      new_pgn.Write(GetPGN(g));
    }
  }

  for (std::shared_ptr<Game> g : games_to_import) {
    if (several) {
      new_pgn.Write("\n\n");
    } else {
      several = true;
    }
    new_pgn.Write(GetPGN(g));
  }

  new_pgn.Close();
  wxCopyFile(tmp, file);
  wxRemoveFile(tmp);
}

void PGNGameBase::Export(std::shared_ptr<GameBase> base) {
  wxFile new_pgn(file, wxFile::write);

  base->Reset();
  bool several = false;
  while (base->NextGame()) {
    if (several) {
      new_pgn.Write("\n\n");
    } else {
      several = true;
    }
    std::shared_ptr<Game> g = base->GetCurrentGame();
    new_pgn.Write(GetPGN(g));
  }

  new_pgn.Close();
}

std::string PGNGameBase::GetPGN(std::shared_ptr<Game> g) {
  std::string pgn;
  HalfMove *m=g->GetMoves();

  for (auto const &element : g->ListTags()) {
    pgn += '[' + element + " \"" + g->GetTag(element) + "\"]\n";
  }

  if(m !=nullptr){
    pgn += GetMovesPGN(m,m->IsABlackMove());
    pgn += " ";
  }

  pgn += g->GetResult();
  return (pgn);
}

std::string PGNGameBase::GetMovesPGN(HalfMove *m, bool needDots) {
  std::string part;
  bool newNeedDots = false;

  if (!m->IsABlackMove() || needDots) {
    part += std::to_string(m->Number) + ".";
    if (needDots) {
      part += "..";
    }
  }
  part += m->move;

  if (m->comment.size() > 0) {
    part += " {";
    part += m->comment;
    part += "}";
    newNeedDots = true;
  }

  if (m->GetVariations().size() > 0) {
    newNeedDots = true;
    for (HalfMove *v : m->GetVariations()) {
      part += " (";
      part += GetMovesPGN(v, m->IsABlackMove());
      part += ")";
    }
  }

  if (m->GetMainline() != nullptr) {
    part += " " + GetMovesPGN(m->GetMainline(), !m->IsABlackMove() && newNeedDots);
  }

  return (part);
}