#include "stdafx.h"

#include "xtr/io/TRSLegacyLoader.h"

#include "xtr/core/Encoder.h"

#include <string>
#include <string_view>
#include <regex>
#include <algorithm>

using ccl::semantic::ConceptRecord;
using ccl::semantic::CstType;
using ccl::lang::ManagedText;
using ccl::lang::LexicalTerm;

namespace xtr::io {

namespace details {
  static void FixOldReferences(std::string& target) {
    static const std::regex referencePattern(R"(\@\{([A-Z]\d+)\|([A-Z]+)\|([A-Z]+)\|\d+\})");
    std::vector<std::pair<std::string, std::string>> replacements{};
    std::sregex_iterator iter(target.begin(), target.end(), referencePattern);
    std::sregex_iterator end{};
    while (iter != end) {
      std::smatch match = *iter;

      std::string part1 = match[1].str();  // e.g., "D4"
      std::string part2 = match[2].str();  // e.g., "ABLT"
      std::string part3 = match[3].str();  // e.g., "PLUR"

      std::transform(part2.begin(), part2.end(), part2.begin(),
        [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
      std::transform(part3.begin(), part3.end(), part3.begin(),
        [](unsigned char c) { return static_cast<char>(std::tolower(c)); });

      std::string newReference = "@{" + part1 + "|" + part2 + "," + part3 + "}";

      replacements.emplace_back(match.str(), newReference);

      iter++;
    }
    for (const auto& [oldRef, newRef] : replacements) {
      size_t pos = 0;
      while ((pos = target.find(oldRef, pos)) != std::string::npos) {
        target.replace(pos, oldRef.length(), newRef);
        pos += newRef.length();
      }
    }
  }
}

void LegacyCstLoader::LoadBasicProperties(BOOL oldType) {
  const auto sName = ar.Load<CString>();
  auto sComment = ar.Load<CString>();
  const auto type = ar.Load<uint16_t>();
  auto sExpression = ar.Load<CString>();

  index::FixLegacy(sExpression);
  index::FixLegacy(sComment);
  if (sComment == sExpression) {
    sComment.Empty();
  }
  if (needEncoding) {
    encode::EncodeToUnicode(sExpression);
    encode::EncodeToUnicode(sComment);
  }

  cst = std::make_unique<ConceptRecord>();
  cst->type = oldType ? LoadV0CstType(type) : static_cast<CstType>(type);
  cst->alias = mfc::ToSTL(sName);
  cst->rs = mfc::ToSTL(sExpression);
  cst->convention = mfc::ToSTL(sComment);
}

CstType LegacyCstLoader::LoadV0CstType(const int32_t code) noexcept {
  switch (code) {
  case 0x01: return CstType::base; // NOLINT
  case 0x02: return CstType::constant; // NOLINT
  case 0x03: return CstType::term; // NOLINT
  case 0x04: return CstType::structured; // NOLINT
  case 0x05: return CstType::axiom; // NOLINT
  case 0x06: return CstType::term; // NOLINT
  case 0x07: return CstType::axiom; // NOLINT
  case 0x08: return CstType::term; // NOLINT
  case 0x09: return CstType::term; // NOLINT
  case 0x0A: return CstType::theorem; // NOLINT
  case 0x0B: return CstType::function; // NOLINT
  }
  return CstType::base;
}

void LegacyCstLoader::LoadTerms(const BOOL hasTerms, const BOOL multipleTerms) {
  using ccl::lang::TERM_FORMS;
  const int32_t terminCount = multipleTerms ? ar.Load<int32_t>() : 1;
  std::vector<CString> terms(terminCount, CString{});
  std::vector<CString> termRefs(terminCount, CString{});
  std::vector<std::vector<CString>> termForms(terminCount);
  for (auto i = 0; i < terminCount; ++i) {
    termForms.at(i).resize(size(TERM_FORMS), CString{});
  }
  if (multipleTerms) {
    for (auto i = 0; i < terminCount; ++i) {
      terms.at(i) = ar.Load<CString>();
      termRefs.at(i) = ar.Load<CString>();
      for (auto j = 0; j < ssize(TERM_FORMS); ++j) {
        termForms.at(i).at(j) = ar.Load<CString>();
      }
    }
  } else if (hasTerms) {
    terms.at(0) = ar.Load<CString>();;
    termRefs.at(0) = terms.at(0);
  }

  auto termRaw = mfc::ToSTL(termRefs.at(0));
  details::FixOldReferences(termRaw);
  cst->term = LexicalTerm{ termRaw, mfc::ToSTL(terms.at(0)) };
  for (uint8_t j = 0; j < size(TERM_FORMS); ++j) {
    auto manualForm = mfc::ToSTL(termForms.at(0).at(j));
    if (!manualForm.empty()) {
      details::FixOldReferences(manualForm);
      cst->term.SetForm(TERM_FORMS.at(j), manualForm);
    }
  }
}

void LegacyCstLoader::LoadInterpretation(const BOOL loadWithRef) {
  auto sInterpretation = ar.Load<CString>();
  CString sInterpretationRef = sInterpretation;
  if (loadWithRef) {
    sInterpretationRef = ar.Load<CString>();
  }
  if (needEncoding) {
    encode::EncodeToUnicode(sInterpretation);
  }
  auto interpretationRaw = mfc::ToSTL(sInterpretationRef);
  details::FixOldReferences(interpretationRaw);
  cst->definition = ManagedText{ interpretationRaw, mfc::ToSTL(sInterpretation) };
}

void LegacyCstLoader::LoadID() {
  cst->uid = ar.Load<uint32_t>();
}

void LegacyCoreLoader::DoLoad(ccl::semantic::RSCore& core) {
  auto cstCount = ar.Load<uint32_t>();
  while (cstCount--) {
    ccl::semantic::ConceptRecord cst{};
    cst.uid = ar.Load<uint32_t>();
    cst.alias = ar.Load<std::string>();
    cst.type = static_cast<ccl::semantic::CstType>(ar.Load<uint32_t>());
    cst.convention = ar.Load<std::string>();
    cst.rs = ar.Load<std::string>();
    cst.definition = LoadText();

    auto count = ar.Load<int32_t>() - 1;
    cst.term = LoadTerm();
    while (count--) {
      [[maybe_unused]] const auto discardTerm = LoadTerm();
    }
    core.Load(std::move(cst));
  }
  core.UpdateState();
}

ccl::lang::ManagedText LegacyCoreLoader::LoadText() {
  auto rawText = ar.Load<std::string>();
  details::FixOldReferences(rawText);
  const auto cache = ar.Load<std::string>();
  return ccl::lang::ManagedText{ rawText, cache };
}

ccl::lang::LexicalTerm LegacyCoreLoader::LoadTerm() {
  using ccl::lang::TERM_FORMS;
  ccl::lang::LexicalTerm term{ LoadText() };
  for (uint8_t j = 0; j < size(TERM_FORMS); ++j) {
    auto manualForm = ar.Load<std::string>();
    if (!std::empty(manualForm)) {
      details::FixOldReferences(manualForm);
      term.SetForm(TERM_FORMS.at(j), manualForm);
    }
  }
  return term;
}

void TRSLegacyLoader::LoadVersion() {
  const auto versionIntro = ar.Load<CString>();
  if (versionIntro != LR"(__@VERSION@__)") {
    schema.title = mfc::ToSTL(versionIntro);
    version = TRSVersion::old;
  } else {
    auto versionID = ar.Load<uint16_t>();
    versionID = versionID < TRSVersion::old ? versionID + TRSVersion::old : versionID;
    version = static_cast<TRSVersion::VersionID>(versionID);
    ar.Skip<CString>();
  }
}

void TRSLegacyLoader::DoLoad() {
  LoadSchemaProperties(version.v != TRSVersion::old);
  if (version.v < xtr::TRSVersion::r1800) {
    LoadFirstGen();
  } else {
    LoadSecondGen();
  }
  schema.UpdateState();
}

void TRSLegacyLoader::LoadSchemaProperties(const BOOL loadFullName) {
  if (loadFullName) {
    schema.title = ar.Load<std::string>();
  }
  schema.alias = ar.Load<std::string>();
  schema.comment = ar.Load<std::string>();
}

void TRSLegacyLoader::LoadFirstGen() {
  static constexpr auto CCONSTITUENTA_CLASS_PREFIX_LEN = 19;
  auto countCst = ar.Load<uint16_t>();
  if (version.v != TRSVersion::old) {
    ar.Skip<int32_t>();
  } else if (countCst > 0) {
    ar.SkipN<uint8_t>(CCONSTITUENTA_CLASS_PREFIX_LEN);
  }
  while (countCst--) {
    LoadCst();
  }
}

void TRSLegacyLoader::LoadCst() {
  LegacyCstLoader cstLoader{ ar };
  if (version.v < TRSVersion::r1092) {
    cstLoader.InitOldEncoding();
  }
  cstLoader.LoadBasicProperties(version.v == TRSVersion::old);
  cstLoader.LoadTerms(version.v >= TRSVersion::r1092, version.v >= TRSVersion::r1438);
  cstLoader.LoadInterpretation(version.v >= TRSVersion::r1438);

  const auto bActive = ar.Load<uint16_t>();
  uint16_t inheritStats{ 0 };
  if (version.v != TRSVersion::old) {
    inheritStats = ar.Load<uint16_t>();
    cstLoader.LoadID();
  } else {
    ar.SkipN<uint8_t>(2);
    try {  
      ar.SkipN<uint8_t>(2); // in some older versions delimiter is not useCount for last element
    } catch (...) {}
  }

  ConceptRecord loadedCst = cstLoader.GetResult();
  const auto newCst = schema.Load(std::move(loadedCst));
  schema.MoveBefore(newCst, std::end(schema.List()));

  if (bActive) {
    selection.emplace(newCst);
  }
  if (version.v >= TRSVersion::r1092) {
    LoadInheritanceDataFromOldFlag(newCst, inheritStats);
  }
}

void TRSLegacyLoader::LoadInheritanceDataFromOldFlag(const EntityUID uid, const uint16_t loadedStatus) {
  static constexpr int8_t CSO_INHERITED = 0x01;
  static constexpr int8_t CSO_UNCHECKED = 0x02;
  static constexpr int8_t CSO_TEXT_OWN = 0x08;
  if ((loadedStatus & CSO_INHERITED) != 0) {
    ccl::semantic::TrackingFlags mode{};
    if (((loadedStatus & CSO_UNCHECKED) != 0) || (loadedStatus & CSO_TEXT_OWN) != 0) {
      mode.allowEdit = true;
      mode.term = true;
      mode.definition = true;
      mode.convention = true;
    }
    schema.Mods().Track(uid, mode);
  }
}

void TRSLegacyLoader::LoadSecondGen() {
  ar.SkipN<uint32_t>(3);
  LegacyCoreLoader{ ar }.DoLoad(schema.LoadCore());
  LoadInheritance();

  isClaimed = ar.Load<bool>();
  auto count = ar.Load<uint32_t>();
  while (count--) {
    selection.emplace(ar.Load<EntityUID>());
  }
}

void TRSLegacyLoader::LoadInheritance() {
  auto modCount = ar.Load<uint32_t>();
  while (modCount--) {
    const auto uid = ar.Load<uint32_t>();
    ccl::semantic::TrackingFlags props{};
    props.allowEdit = ar.Load<bool>();
    props.term = ar.Load<bool>();
    props.definition = ar.Load<bool>();
    props.convention = ar.Load<bool>();
    schema.Mods().Track(EntityUID{ uid }, props);
  }
}

} // namespace xtr::io