#include "stdafx.h"

#include "xtr/io/RSMLegacyLoader.h"

#include "xtr/io/TRSLegacyLoader.h"

using ccl::rslang::Typification;
using ccl::object::StructuredData;
using ccl::object::Factory;
using ccl::object::SDCompact;
using ccl::semantic::TextInterpretation;
using ccl::semantic::ConceptRecord;
using ccl::semantic::CstType;
using ccl::semantic::EvalStatus;

namespace xtr::io {

namespace {

class LegacyUnpacker {
public:
  explicit LegacyUnpacker(const RSMLegacyLoader::LegacyData& data) noexcept
    : data{ data } {}

private:
  const RSMLegacyLoader::LegacyData& data;

  uint32_t pos_x{ 0 };
  uint32_t pos_y{ 0 };

public:
  std::optional<StructuredData> UnpackFor(const Typification& type) {
    if (size(data) > pos_x && size(data.at(pos_x)) > pos_y) {
      switch (type.Structure()) {
      case ccl::rslang::StructureType::basic:
        return UnpackBasic();
      case ccl::rslang::StructureType::collection:
        return UnpackBool(type);
      case ccl::rslang::StructureType::tuple:
        return UnpackTuple(type);
      }
    }
    return std::nullopt;
  }

protected:
  [[nodiscard]]  StructuredData UnpackBasic() {
    auto result = Factory::Val(data.at(pos_x).at(pos_y));
    ++pos_y;
    return result;
  }

  [[nodiscard]] std::optional<StructuredData> UnpackBool(const Typification& type) {
    auto result = Factory::EmptySet();
    const auto bool_y = pos_y;
    const auto element_y = bool_y + 1;
    const auto base_x = pos_x;
    while (size(data) > pos_x) {
      if (size(data.at(pos_x)) < element_y ||
          FirstElementsAreNotEqual(base_x, pos_x, element_y)) {
        --pos_x;
        return result;
      } else {
        pos_y = element_y;

        if (auto element = UnpackFor(type.B().Base()); !element.has_value()) {
          return result;
        }  else if (!result.ModifyB().AddElement(element.value())) {
          --pos_x;
          return result;
        } else {
          ++pos_x;
        }
      }
    }
    return result;
  }

  [[nodiscard]] std::optional<StructuredData> UnpackTuple(const Typification& type) {
    std::vector<StructuredData> factorsData{};
    const auto arity = type.T().Arity();
    for (ccl::rslang::Index i = 0; i < arity; ++i) {
      const auto& factor = type.T().Component(i + 1);
      auto factorVal = UnpackFor(factor);
      if (!factorVal.has_value()) {
        return std::nullopt;
      }
      factorsData.emplace_back(factorVal.value());
    }
    return Factory::Tuple(factorsData);
  }

  [[nodiscard]] bool FirstElementsAreNotEqual(const uint32_t row1, const uint32_t row2, const uint32_t ymax) const {
    for (auto y = 0U; y < ymax; ++y) {
      if (data.at(row1).at(y) != data.at(row2).at(y)) {
        return true;
      }
    }
    return false;
  }
};

} // namespace

void RSMLegacyLoader::LoadVersion() {
  const auto intro = ar.Load<CString>();
  if (intro != LR"(__@VERSION@__)") {
    version = RSMVersion::old;
  } else {
    version = static_cast<RSMVersion::VersionID>(ar.Load<uint16_t>());
    ar.Skip<CString>();
  }
}

void RSMLegacyLoader::DoLoad() {
  const auto bFirstGen = version.v < RSMVersion::r1800;
  LoadModelProperties(bFirstGen);
  if (bFirstGen) {
    LoadFirstGen();
  } else {
    LoadSecondGen();
  }
  model.Calculations().LoadCalulated(calculated);
}

void RSMLegacyLoader::LoadFirstGen() {
  auto countCst = ar.Load<uint16_t>();
  ar.Skip<int32_t>();
  while (countCst--) {
    LoadCst();
  }

  model.UpdateState();
  auto countData = ar.Load<int32_t>();
  while (countData--) {
    LoadCstData();
  }
}

void RSMLegacyLoader::LoadModelProperties(const BOOL loadModelName) {
  model.title = ar.Load<std::string>();
  model.alias = ar.Load<std::string>();
  if (loadModelName) {
    const auto modelName = ar.Load<std::string>();
    if (empty(model.title)) {
      model.title = modelName;
    }
  }
  model.comment = ar.Load<std::string>();
}

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

  const auto bActive = ar.Load<uint16_t>();
  ar.Skip<uint16_t>();

  cstLoader.LoadID();

  ConceptRecord loadedCst = cstLoader.GetResult();
  const auto newCst = model.Load(std::move(loadedCst));
  if (bActive) {
    selection.emplace(newCst);
  }
}

void RSMLegacyLoader::LoadCstData() {
  const auto name = ar.Load<std::string>();
  const auto status = static_cast<EvalStatus>(ar.Load<uint32_t>());
  const auto skipData = 
       status == EvalStatus::UNKNOWN
    || status == EvalStatus::NEVER_CALCULATED
    || status == EvalStatus::INCALCULABLE;

  const auto uid = model.Core().FindAlias(name).value();
  if (status != EvalStatus::UNKNOWN && status != EvalStatus::NEVER_CALCULATED) {
    calculated.insert(uid);
  }

  const auto type = model.GetRS(uid).type;
  if (ccl::semantic::IsCallable(type)) {
    return;
  }

  if (ccl::semantic::IsRSObject(type)) {
    const auto structuredData = LoadRSITable();
    const auto textData = std::empty(structuredData) ? TextInterpretation{} : LoadTextData(FALSE);
    if (!skipData) {
      if (ccl::semantic::IsBaseSet(model.GetRS(uid).type)) {
        model.Values().LoadData(uid, textData);
      } else if (const auto* typification = model.GetParse(uid).Typification(); typification != nullptr) {
        LegacyUnpacker unpacker(structuredData);
        if (const auto val = unpacker.UnpackFor(*typification); val.has_value()) {
          model.Values().LoadData(uid, val.value());
        }
      }
    }
  } else {
    const auto value = static_cast<bool>(ar.Load<uint16_t>());
    if (!skipData) {
      model.Values().LoadData(uid, value);
    }
  }
}

SDCompact::Data RSMLegacyLoader::LoadRSITable() {
  auto nRows = ar.Load<uint32_t>();
  SDCompact::Data data(nRows);
  for (size_t i = 0; nRows--; ++i) {
    auto nCols = ar.Load<uint32_t>();
    while (nCols--) {
      data.at(i).emplace_back(ar.Load<int32_t>());
    }
  }
  return data;
}

TextInterpretation RSMLegacyLoader::LoadTextData(const BOOL skipIndex) {
  TextInterpretation data{};
  auto namesCount = ar.Load<uint32_t>();
  while (namesCount--) {
    if (skipIndex) {
      ar.Skip<int32_t>();
    }
    data.PushBack(ar.Load<std::string>());
  }
  return data;
}

void RSMLegacyLoader::LoadSecondGen() {
  ar.SkipN<uint32_t>(3);
  LegacyCoreLoader{ ar }.DoLoad(model.LoadCore());
  model.FinalizeLoadingCore();

  ar.Skip<uint32_t>();
  const auto modelName = ar.Load<std::string>();
  if (empty(model.title)) {
    model.title = modelName;
  }

  auto calculatedCount = ar.Load<uint32_t>();
  while (calculatedCount--) {
    const auto uid = EntityUID{ ar.Load<uint32_t>() };
    if (ar.Load<bool>()) {
      calculated.insert(uid);
    }
    LoadDataFor(uid);
  }

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

void RSMLegacyLoader::LoadDataFor(const EntityUID uid) {
  const auto type = model.GetRS(uid).type;
  if (ccl::semantic::IsCallable(type)) {
    return;
  }
  if (ccl::semantic::IsRSObject(type)) {
    if (ar.Load<bool>()) {
      const auto compactedData = LoadRSITable();
      const auto* typification = model.GetParse(uid).Typification();
      if (typification != nullptr) {
        const auto val = ccl::object::SDCompact::Unpack(compactedData, *typification);
        if (val.has_value()) {
          model.Values().LoadData(uid, val.value());
        }
      }
    }
    if (ar.Load<bool>()) {
      model.Values().LoadData(uid, LoadTextData(TRUE));
    }
  } else {
    if (ar.Load<bool>()) {
      model.Values().LoadData(uid, ar.Load<bool>());
    }
  }
}

} // namespace xtr::io