#include "stdafx.h"

#include "xtr/io/RSExcelIOManager.h"

#include "resource.h"

#include "xtr/Exteor.h"
#include "xtr/cclDescriptor.h"

#include "xtr/dialog/ImportWSDialog.h"

namespace xtr::io {

using ccl::semantic::TextInterpretation;
using ccl::object::SDCompact;
using ccl::rslang::Typification;
using MSO::Excel::EXLWorksheet;
using MSO::SizeT;

static constexpr MSO::SizeT HEADER_ROWS{ 1 };
static constexpr auto EXCEL_DATA_START = MSO::FIRST_INDEX + HEADER_ROWS;
static constexpr MSO::SizeT TEXT_COLUMNS{ 2 };

RSExcelIOManager::~RSExcelIOManager() {
  try {
    workingWb.ReleaseDispatch();
    workingWs.ReleaseDispatch();
    xlWrapper.ReleaseDocument();
  } catch (...) {
    ; // TODO: manage exceptions correctly
  }
}

BOOL RSExcelIOManager::ExportModel() {
  if (!PrepareNewWorkbook()) {
    return FALSE;
  }
  auto rsFormSheet = InitModelWorksheet(mfc::ToMFC(model.alias));
  auto outputRange = rsFormSheet.get_Cells();

  auto nRow = MSO::FIRST_INDEX + HEADER_ROWS;
  for (const auto uid : model.List()) {
    const auto& cst = model.GetRS(uid);
    ExportCstProperties(uid, rsFormSheet, nRow);
    if (ccl::semantic::IsRSObject(cst.type)) {
      if (!ExportCstDataInternal(uid)) {
        return FALSE;
      }
    }
    ++nRow;
  }
  outputRange.get_Columns().AutoFit();
  rsFormSheet.Activate();
  return TRUE;
}

BOOL RSExcelIOManager::ExportDataFor(const EntityUID target) {
  if (!PrepareNewWorkbook()) {
    return FALSE;
  } else {
    return ExportCstDataInternal(target);
  }
}


std::optional<TextInterpretation> RSExcelIOManager::ImportText() {
  if (PrepareInputWorksheet()) {
    auto result = ImportTextInternal();
    xlWrapper.ReleaseDocument();
    return result;
  } else {
    return std::nullopt;
  }
}

std::optional<SDCompact> RSExcelIOManager::ImportStructured(const Typification& structure) {
  if (PrepareInputWorksheet()) {
    auto result = ImportStructuredInternal(structure);
    xlWrapper.ReleaseDocument();
    return result;
  } else {
    return std::nullopt;
  }
}

BOOL RSExcelIOManager::PrepareNewWorkbook() {
  const auto pathToTemplate = g_Exteor.GetModuleFolder() + mfc::LoadSID(IXTRS_HEADER_CONSTITUENTA);
  if (workingWb = xlWrapper.NewDocument(pathToTemplate); workingWb == nullptr) {
    ::AfxMessageBox(IXTRE_EXCEL_OPENWB, MB_ICONERROR);
    return FALSE;
  } else {
    return true;
  }
}

EXLWorksheet RSExcelIOManager::InitModelWorksheet(const CString label) {
  auto rsFormSheet = workingWb.get_Worksheets()[1];
  rsFormSheet.put_Name(label);

  auto rsRange = rsFormSheet.get_Cells();
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::id), T2VT(mfc::LoadSID(IXTRS_HEADER_ID)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::constituenta), T2VT(mfc::LoadSID(IXTRS_HEADER_CONSTITUENTA)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::expression), T2VT(mfc::LoadSID(IXTRS_HEADER_EXPRESSION)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::type), T2VT(mfc::LoadSID(IXTRS_HEADER_TYPE)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::term), T2VT(mfc::LoadSID(IXTRS_HEADER_TERM)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::interpretation), T2VT(mfc::LoadSID(IXTRS_HEADER_INTERPRETATION)));
  rsRange.put_Item(MSO::FIRST_INDEX, static_cast<long>(OutputColumn::convention), T2VT(mfc::LoadSID(IXTRS_HEADER_CONVENTION)));

  return rsFormSheet;
}

void RSExcelIOManager::ExportCstProperties(const EntityUID target,
                                           EXLWorksheet rsFormSheet, const MSO::SizeT nRow) {
  const auto& rsCst = model.GetRS(target);
  const auto& textCst = model.GetText(target);
  auto range = rsFormSheet.get_Cells();
  range.put_Item(nRow, static_cast<long>(OutputColumn::id), T2VT(static_cast<long>(target)));
  range.put_Item(nRow, static_cast<long>(OutputColumn::constituenta), T2VT(mfc::ToMFC(rsCst.alias)));
  range.put_Item(nRow, static_cast<long>(OutputColumn::expression), T2VT(mfc::ToMFC(rsCst.definition)));
  range.put_Item(nRow, static_cast<long>(OutputColumn::type), T2VT(info::Label(model.GetParse(target).TypePtr())));
  range.put_Item(nRow, static_cast<long>(OutputColumn::term), T2VT(mfc::ToMFC(textCst.term.Nominal())));
  range.put_Item(nRow, static_cast<long>(OutputColumn::interpretation), T2VT(mfc::ToMFC(textCst.definition.Str())));
  range.put_Item(nRow, static_cast<long>(OutputColumn::convention), T2VT(mfc::ToMFC(rsCst.convention)));
}

BOOL RSExcelIOManager::ExportCstDataInternal(const EntityUID target) {
  const auto& rsCst = model.GetRS(target);
  if (workingWs = CreateOutputSheet(mfc::ToMFC(rsCst.alias)); workingWs == nullptr) {
    return FALSE;
  } else if (!ccl::semantic::IsBaseSet(rsCst.type)) {
    return ExportStructuredData(target);
  } else {
    ExportNamedData(target);
    return TRUE;
  }
}

EXLWorksheet RSExcelIOManager::CreateOutputSheet(const CString label) {
  auto worksheets = workingWb.get_Worksheets();
  auto lastWorksheet = worksheets[worksheets.get_Count()];
  auto result = worksheets.Add({}, lastWorksheet, vtMissing, vtMissing);
  if (result != nullptr) {
    result.put_Name(label);
  }
  return result;
}

void RSExcelIOManager::ExportNamedData(const EntityUID target) {
  workingWs.get_Cells().put_Item(1, 1, T2VT(mfc::ToMFC(model.GetRS(target).alias)));
  workingWs.get_Cells().put_Item(1, 2, T2VT(mfc::LoadSID(IXTRS_HEADER_INTERPRETATION)));

  const auto& interp = *model.Values().TextFor(target);
  auto nRow = EXCEL_DATA_START;
  for (const auto& [id, text] : interp) {
    const auto interpretant = mfc::ToMFC(text);
    CString index{};
    index.AppendFormat(LR"(%ld)", id);
    workingWs.get_Cells().put_Item(nRow, 1, T2VT(index));
    workingWs.get_Cells().put_Item(nRow, 2, T2VT(interpretant));
    ++nRow;
  }
}

BOOL RSExcelIOManager::ExportStructuredData(const EntityUID target) {
  const auto* typif = model.GetParse(target).Typification();
  if (typif == nullptr) {
    ::AfxMessageBox(IXTRE_EXPORT_TYPIF, MB_ICONERROR);
    return FALSE;
  }
  if (auto structured = model.Values().SDataFor(target); structured.has_value()) {
    const auto compact = SDCompact::FromSData(structured.value(), *typif);
    InitCstDataHeader(compact.header);
    for (auto nRow = 0; nRow < ssize(compact.data); ++nRow) {
      for (auto nCol = 1; nCol < ssize(compact.data.at(nRow)); ++nCol) {
        const auto dataIndex = compact.data.at(nRow).at(nCol);
        workingWs.get_Cells().put_Item(nRow + EXCEL_DATA_START, nCol - 1 + MSO::FIRST_INDEX, T2VT(dataIndex));
      }
    }
  }
  return TRUE;
}

void RSExcelIOManager::InitCstDataHeader(const std::vector<std::string>& header) {
  static constexpr auto skipFirst = 1;
  for (auto i = skipFirst; i < ssize(header); ++i) {
    workingWs.get_Cells().put_Item(1, i - 1 + MSO::FIRST_INDEX, T2VT(mfc::ToMFC(header.at(i))));
  }
}

BOOL RSExcelIOManager::PrepareInputWorksheet() {
  if (auto path = MSO::Excel::ExcelWrapper::PromptUserForPath(mfc::LoadSID(IXTRS_FILTER_EXCEL)); path.IsEmpty()) {
    return FALSE;
  } else if (workingWb = xlWrapper.OpenDocument(path, TRUE, TRUE, TRUE, FALSE); workingWb == nullptr) {
    ::AfxMessageBox(mfc::FormatSID(IXTRE_IMPORT_DOC_FAIL, path.GetString()), MB_ICONERROR);
    return FALSE;
  } else {
    return DetermineWorksheet();
  }
}

BOOL RSExcelIOManager::DetermineWorksheet() {
  auto worksheets = workingWb.get_Worksheets();
  std::vector<CString> labels{};
  labels.reserve(worksheets.get_Count());
  for (auto i = 0; i < worksheets.get_Count(); ++i) {
    labels.emplace_back(worksheets[MSO::FIRST_INDEX + i].get_Name());
  }
  auto selectedIndex = 0;
  if (ssize(labels) > 1) {
    dialog::ImportWSDialog wsDialog(labels);
    if (wsDialog.DoModal() != IDOK) {
      return FALSE;
    }
    selectedIndex = wsDialog.selection;
  }
  workingWs = worksheets[MSO::FIRST_INDEX + selectedIndex];
  return TRUE;
}

std::optional<TextInterpretation> RSExcelIOManager::ImportTextInternal() {
  auto rawData = ExtractRawData(TEXT_COLUMNS);
  auto result = RSExcelIOManager::DataToTextInterpretation(rawData);
  if (!result.has_value()) {
    ::AfxMessageBox(IXTRE_DATA2EXCEL_FORMAT, MB_ICONERROR);
  }
  return result;
}

std::optional<SDCompact> RSExcelIOManager::ImportStructuredInternal(const Typification& structure) {
  if (auto headers = SDCompact::CreateHeader(structure); !CheckHeaders(headers)) {
    return std::nullopt;
  } else {
    auto rawData = ExtractRawData(static_cast<MSO::SizeT>(ssize(headers)));
    auto data = RSExcelIOManager::DataToRSI(rawData);
    if (!data.has_value()) {
      ::AfxMessageBox(IXTRE_IMPORT_EXCEL_INVALID_DATA, MB_ICONERROR);
      return std::nullopt;
    } else {
      return SDCompact{ headers, data.value() };
    }
  }
}

BOOL RSExcelIOManager::CheckHeaders(const std::vector<std::string>& headers) {
  if (std::empty(headers)) {
    return FALSE;
  } else {
    for (MSO::SizeT nCol = 1; nCol < ssize(headers); ++nCol) {
      CString cellHead = workingWs.get_Cells()(MSO::FIRST_INDEX, nCol + MSO::FIRST_INDEX - 1).get_Value();
      if (cellHead != mfc::ToMFC(headers.at(nCol))) {
        ::AfxMessageBox(IXTRE_IMPORT_TYPIF, MB_ICONEXCLAMATION);
        return FALSE;
      } else {
        ++nCol;
      }
    }
    return TRUE;
  }
}

RSExcelIOManager::RawStringData RSExcelIOManager::ExtractRawData(const MSO::SizeT columnCount) {
  auto range = workingWs.get_UsedRange();
  const auto rowCount = range.get_Rows().get_Count();

  RawStringData strData{};
  strData.reserve(static_cast<size_t>(rowCount) - HEADER_ROWS);
  for (auto nRow = HEADER_ROWS; nRow < rowCount; ++nRow) {
    for (auto nCol = 0; nCol < columnCount; ++nCol) {
      auto cell = range.get_Cells()(nRow + MSO::FIRST_INDEX, nCol + MSO::FIRST_INDEX);
      const CString text{ cell.get_Value() };
      if (nCol == 0) {
        if (text.IsEmpty()) {
          return strData;
        } else {
          strData.push_back({});
        }
      }
      strData.at(static_cast<size_t>(nRow) - HEADER_ROWS).emplace_back(text);
    }
  }
  return strData;
}

std::optional<TextInterpretation> RSExcelIOManager::DataToTextInterpretation(const RawStringData& data) {
  TextInterpretation result{};
  for (const auto& aRow : data) {
    const auto& index = aRow.at(0);
    const auto& text = aRow.at(1);
    if (index.IsEmpty() || index.SpanIncluding(L"0123456789") != index) {
      return std::nullopt;
    }
    result.SetInterpretantFor(_wtoi(index), mfc::ToSTL(text));
  }
  return result;
}

std::optional<SDCompact::Data> RSExcelIOManager::DataToRSI(const RawStringData& data) {
  SDCompact::Data result{ std::size(data) };
  auto resultRow = std::begin(result);
  for (const auto& aRow : data) {
    resultRow->emplace_back(SDCompact::unknownCount);
    for (const auto& text : aRow) {
      if (text.SpanIncluding(L"0123456789") != text) {
        return std::nullopt;
      } else if (!text.IsEmpty()) {
        resultRow->emplace_back(_wtoi(text));
      }
    }
    ++resultRow;
  }
  return result;
}

} // namespace xtr::io