#include "licGenELC3.h"

namespace Generator
{
  Licence3::Licence3()
  {
  }

  Licence3::~Licence3() {}

  Licence3::Licence3(string uid, pugi::xml_document *xmlDoc)
  {
    this->uid = uid;

    this->xmlDoc = xmlDoc;
    if (processInputConfiguration() == false)
      throw LicenceException((int)GeneralError::LicenceReadError, "Chyba při čtení licence");
  }

  void Licence3::getHeader()
  {
    PublicHeader publicHeader;
    publicHeader.version = getVersion(lIdentification.revision);
    publicHeader.projectDescription = projectDescription;
    publicHeader.date = getDate();
    publicHeader.licenceType = lIdentification.licTypeName;
    publicHeader.licenceType += to_string(lIdentification.licenceIndex);

    licBody.publicHeader = "";

    licBody.publicHeader.append("{\"version\":");
    licBody.publicHeader.append(publicHeader.version);
    licBody.publicHeader.append("\",");

    licBody.publicHeader.append("\"project\":\"");
    licBody.publicHeader.append(publicHeader.projectDescription);
    licBody.publicHeader.append("\",");

    licBody.publicHeader.append("\"date\":\"");
    licBody.publicHeader.append(publicHeader.date);
    licBody.publicHeader.append("\",");

    licBody.publicHeader.append("\"note\":\"poznámka\",");
    licBody.publicHeader.append("\"licenceType\":\"");
    licBody.publicHeader.append(publicHeader.licenceType);
    licBody.publicHeader.append("\",");

    licBody.publicHeader.append("\"items\":[");

    lIdentification.licItemsCount = std::distance(xmlDoc->child("licence").child("items").begin(), xmlDoc->child("licence").child("items").end());

    int nodeIndex = 0;
    int itemIndex = 0;

    for (pugi::xml_node licItemNode : xmlDoc->child("licence").child("items"))
    {
      licBody.publicHeader.append("{");
      nodeIndex = 0;
      itemIndex++;
      for (pugi::xml_node child : licItemNode.children())
      {
        nodeIndex++;
        if (nodeIndex == 1)
        {
          licBody.publicHeader.append("\"name\":\"");
          licBody.publicHeader.append(child.child_value());
          licBody.publicHeader.append("\",");
          continue;
        }
        if (nodeIndex == 3)
        {
          licBody.publicHeader.append("\"dataPointsCount\":\"");
          licBody.publicHeader.append(child.child_value());
          licBody.publicHeader.append("\"");
        }
      }
      if (itemIndex != lIdentification.licItemsCount)
      {
        licBody.publicHeader.append("},");
      }
      else
      {
        licBody.publicHeader.append("}");
      }
    }

    licBody.publicHeader.append("]");
    licBody.publicHeader.append("}");
  }

  string Licence3::getVersion(int middleVersion)
  {
    string result = "";
    result.append(to_string(generatorVersion));
    result.append(".");
    result.append(to_string(middleVersion));
    result.append(".");
    string tempLicenceCount = "1";
    result.append(tempLicenceCount);
    return result;
  }

  bool Licence3::processInputConfiguration()
  {
    const char *dataRootName = "licence";

    string licType = xmlDoc->child(dataRootName).child("licenceType").child_value();
    if (!licType.empty())
    {
      this->lIdentification.licLicenceType = mapping.licMapTypes[licType]; // LicenceType::EOS_EOV;
      this->lIdentification.licTypeName = licType;
    }
    else
    {
      errorMessage.code = (int)GeneralError::LicenceMismatch;
      errorMessage.message = "ERROR MISSING licenceType";
      return false;
    }

    this->lIdentification.licenceVersion = atoi(&xmlDoc->child(dataRootName).child("licenceType").attribute("licenceVersion").value()[0]);
    this->lIdentification.revision = atoi(&xmlDoc->child(dataRootName).attribute("revision").value()[0]);
    this->lIdentification.licenceIndex = atoi(&xmlDoc->child(dataRootName).child("licenceType").attribute("licenceIndex").value()[0]);
    this->lIdentification.licElcType = (ELCType)atoi(&xmlDoc->child(dataRootName).attribute("elc").value()[0]);

    this->lIdentification.licCompatibility = atoi(&xmlDoc->child(dataRootName).attribute("compatibility").value()[0]);

    string plcType = xmlDoc->child(dataRootName).child("plcType").child_value();
    if (!plcType.empty())
    {
      this->lIdentification.licPlcType = mapping.licMapPlcType[plcType];
    }

    this->projectDescription = &xmlDoc->child(dataRootName).child("project").child_value()[0];

    licBody.licenceIdentHeader.compatibilityVersion = this->lIdentification.licCompatibility;
    licBody.licenceIdentHeader.licenceIndex = this->lIdentification.licenceIndex;
    licBody.licenceIdentHeader.licenceType = (BYTE)this->lIdentification.licLicenceType;
    licBody.licenceIdentHeader.licenceTypeVersion = this->lIdentification.licenceVersion;
    return true;
  }

  void Licence3::getLicenceItems()
  {
    int nodeIndex = 0;
    int itemIndex = 0;

    for (pugi::xml_node licItemNode : xmlDoc->child("licence").child("items"))
    {
      licDataItem item;

      item.protoId = atoi(licItemNode.child("protoId").child_value());
      item.licCount = atoi(licItemNode.child("dataPointsCount").child_value());

      this->licBody.privateContent.dataItems.push_back(item);

      nodeIndex = 0;
      itemIndex++;
      for (pugi::xml_node child : licItemNode.children())
      {
        nodeIndex++;
        if (nodeIndex == 1)
        {
          licBody.publicHeader.append("\"name\":\"");
          licBody.publicHeader.append(child.child_value());
          licBody.publicHeader.append("\",");
          continue;
        }
        if (nodeIndex == 3)
        {
          licBody.publicHeader.append("\"dataPointsCount\":\"");
          licBody.publicHeader.append(child.child_value());
          licBody.publicHeader.append("\"");
        }
      }
      if (itemIndex != lIdentification.licItemsCount)
      {
        licBody.publicHeader.append("},");
      }
      else
      {
        licBody.publicHeader.append("}");
      }
    }
  }

  bool Licence3::createLicence()
  {
    getLicenceItems();
    getHeader();

    this->licBody.licenceIdentHeader.licItemCount = this->licBody.privateContent.dataItems.size();
    this->licBody.licenceIdentHeader.publicHeaderLength = this->licBody.publicHeader.length();

    vector<unsigned char> publicContent{};
    vector<unsigned char> privateContent{};
    vector<unsigned char> privateContentEncrypted{};

    publicContent.push_back(this->licBody.licId.licIdent[0]);
    publicContent.push_back(this->licBody.licId.licIdent[1]);
    publicContent.push_back(this->licBody.licId.licIdent[2]);
    publicContent.push_back(((char)48 + (int)this->lIdentification.licElcType));
    publicContent.push_back(this->licBody.licId.licIdent[4]);

    publicContent.push_back(this->licBody.licenceIdentHeader.licenceType);
    publicContent.push_back(this->licBody.licenceIdentHeader.licenceTypeVersion);
    publicContent.push_back(this->licBody.licenceIdentHeader.licenceIndex);
    publicContent.push_back(this->licBody.licenceIdentHeader.compatibilityVersion);
    publicContent.push_back(this->licBody.privateContent.dataItems.size());
    publicContent.push_back(this->licBody.licenceIdentHeader.publicHeaderLength & 0xFF);
    publicContent.push_back((this->licBody.licenceIdentHeader.publicHeaderLength >> 8) & 0xFF);
    
    appendStringToVector(this->licBody.publicHeader, publicContent);

#ifdef EZ_LIC_DEBUG_SHOW_DETAILS
    cout << "CRC public size: " << publicContent.size() << "\n";
    cout << "CRC gen public: " << calculateCRC16(publicContent) << "\n";
#endif

    privateContent.push_back(this->licBody.licenceIdentHeader.licenceType);
    privateContent.push_back(this->licBody.licenceIdentHeader.licenceTypeVersion);
    privateContent.push_back(this->licBody.licenceIdentHeader.licenceIndex);
    privateContent.push_back(this->licBody.licenceIdentHeader.compatibilityVersion);
    privateContent.push_back(this->licBody.privateContent.dataItems.size());
    privateContent.push_back(this->licBody.licenceIdentHeader.publicHeaderLength & 0xFF);
    privateContent.push_back((this->licBody.licenceIdentHeader.publicHeaderLength >> 8) & 0xFF);
    
    privateContent.push_back(1);
    privateContent.push_back(1);
    privateContent.push_back(1);

    for (licDataItem dataItem : this->licBody.privateContent.dataItems)
    {
      privateContent.push_back(dataItem.protoId & 0xFF);
      privateContent.push_back((dataItem.protoId >> 8) & 0xFF);
      privateContent.push_back((dataItem.protoId >> 16) & 0xFF);
      privateContent.push_back((dataItem.protoId >> 24) & 0xFF);
      privateContent.push_back(dataItem.licCount & 0xFF);
      privateContent.push_back((dataItem.licCount >> 8) & 0xFF);
      privateContent.push_back((dataItem.licCount >> 16) & 0xFF);
      privateContent.push_back((dataItem.licCount >> 24) & 0xFF);
    }

#ifdef EZ_LIC_DEBUG_SHOW_DETAILS
    cout << "CRC private size: " << privateContent.size() << "\n";
    cout << "CRC gen private: " << calculateCRC16(privateContent) << "\n";
#endif

    vector<unsigned char> completeVector = joinVectors(publicContent, privateContent);
    uint16_t crcComplete = calculateCRC16(completeVector);

#ifdef EZ_LIC_DEBUG_SHOW_DETAILS
    cout << "CRC complete size: " << completeVector.size() << "\n";
    cout << "CRC gen complete: " << crcComplete << "\n";
#endif

    privateContent.push_back(crcComplete & 0xFF);
    privateContent.push_back((crcComplete >> 8) & 0xFF);

    // cout << "crc complete: " << crcComplete << "\n";

    // cout << "privateContent length: " << privateContent.size() << "\n";
    //  for (auto x : privateContent) cout << (int)x << "-";
    // cout << "\n";

    privateContentEncrypted = cryptPrivateContent(privateContent);

    // cout << "privateContentEncrypted length: " << privateContentEncrypted.size() << "\n";
    //  for (auto x : privateContentEncrypted) cout << (int)x << "-";
    // cout << "\n";

    string licfileName = getLicenceName();
    std::ofstream outputFile(licfileName, std::ios::out | std::ios::binary);

    // Check if the file is open
    if (!outputFile.is_open())
    {
      throw LicenceException((int)GeneralError::FileOpenError, "Chyba při zakládání souboru licence: " + uid_path);
    }

    std::copy(publicContent.cbegin(), publicContent.cend(), std::ostream_iterator<unsigned char>(outputFile));
    std::copy(privateContentEncrypted.cbegin(), privateContentEncrypted.cend(), std::ostream_iterator<unsigned char>(outputFile));

    outputFile.close();

    cout << licfileName;
    return true;
  }
}
