// Copyright (c) 2023 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#include "VNSceneGen.hpp"

using namespace Dawn;

void VNSceneGen::test(
  std::string eventName,
  struct VNSceneEvent *event,
  int32_t *eventIndex,
  std::vector<std::string> *eventInit,
  std::vector<std::string> *eventChain,
  std::vector<std::string> *includes
) {
  std::string initType = "";
  std::string toInclude = "";
  std::string initArgs = "";

  std::vector<std::string> eventInitAfter;

  switch(event->type) {
    case VN_SCENE_EVENT_TYPE_TEXT:
      initType = "VNTextEvent";
      toInclude = "games/vn/events/VNTextEvent.hpp";
      line(&eventInitAfter, eventName + "->" + "text = " + event->text.texts.begin()->text + ";", "");
      break;

    case VN_SCENE_EVENT_TYPE_POSITION:
      initType = "VNPositionEvent";
      toInclude = "games/vn/events/VNPositionEvent.hpp";
      line(&eventInitAfter, eventName + "->item = " + event->position.item + ";", "");
      if(event->position.x != "") line(&eventInitAfter, eventName + "->" + "to.x = " + event->position.x + ";", "");
      if(event->position.y != "") line(&eventInitAfter, eventName + "->" + "to.y = " + event->position.y + ";", "");
      if(event->position.z != "") line(&eventInitAfter, eventName + "->" + "to.z = " + event->position.z + ";", "");
      break;

    case VN_SCENE_EVENT_TYPE_SET:
      initType = "VNSetEvent<" + event->set.type + ">";
      toInclude = "games/vn/events/VNSetEvent.hpp";
      line(&eventInitAfter, eventName + "->modifies = &" + event->set.property + ";", "");
      line(&eventInitAfter, eventName + "->value = " + event->set.to + ";", "");
      break;

    case VN_SCENE_EVENT_TYPE_WAIT:
      initType = "VNWaitEvent";
      toInclude = "games/vn/events/VNWaitEvent.hpp";
      line(&eventInitAfter, eventName + "->duration = " + event->wait.duration + ";", "");
      break;

    case VN_SCENE_EVENT_TYPE_PARALLEL: {
      initType = "VNParallelEvent";
      toInclude = "games/vn/events/VNParallelEvent.hpp";

      auto itParallel = event->parallel.events.events.begin();
      while(itParallel != event->parallel.events.events.end()) {
        std::string pEventName = "pEvent" + std::to_string((*eventIndex)++);
        VNSceneGen::test(
          pEventName,
          &(*itParallel),
          eventIndex,
          eventInit,
          eventChain,
          includes
        );
        line(&eventInitAfter, eventName + "->events.push_back(" + pEventName + ");", "");
        line(&eventInitAfter, "", "");
        ++itParallel;
      }
      break;
    }

    case VN_SCENE_EVENT_TYPE_MARKER:
      initType = "VNDummyEvent";
      toInclude = "games/vn/events/VNDummyEvent.hpp";
      line(&eventInitAfter, "auto marker_" + event->marker.name + " = " + eventName + ";", "");
      break;

    case VN_SCENE_EVENT_TYPE_GOTO_MARKER:
      initType = "VNDummyEvent";
      toInclude = "games/vn/events/VNDummyEvent.hpp";
      line(eventChain, eventName + "->then(marker_" + event->gotoMarker.name + ");", "");
      break;

    case VN_SCENE_EVENT_TYPE_CHOICES: {
      initType = "VNChoiceEvent";
      toInclude = "games/vn/events/VNChoiceEvent.hpp";

      line(&eventInitAfter, eventName + "->key = \"" + event->choices.key+ "\";", "");
      line(&eventInitAfter, eventName + "->text = " + event->choices.titles.begin()->text + ";", "");
      auto itChoices = event->choices.choices.begin();
      while(itChoices != event->choices.choices.end()) {
        line(&eventInitAfter, eventName + "->choices[\"" + itChoices->value + "\"] = " + itChoices->texts.begin()->text + ";", "");
        ++itChoices;
      }
      break;
    }

    case VN_SCENE_EVENT_TYPE_CHOICE_SET:
      initType = "VNChoiceSetEvent";
      toInclude = "games/vn/events/VNChoiceSetEvent.hpp";
      line(&eventInitAfter, eventName + "->key = \"" + event->choiceSet.key + "\";", "");
      line(&eventInitAfter, eventName + "->value = \"" + event->choiceSet.value + "\";", "");
      break;

    case VN_SCENE_EVENT_TYPE_IF: {
      initType = "VNIfEvent";
      toInclude = "games/vn/events/VNIfEvent.hpp";
      line(&eventInitAfter, eventName + "->key = \"" + event->ifEvent.key + "\";", "");
      line(&eventInitAfter, eventName + "->value = \"" + event->ifEvent.value + "\";", "");

      std::string ifPrevious = "";
      std::string ifFirst = "";
      auto itIf = event->ifEvent.events.events.begin();
      while(itIf != event->ifEvent.events.events.end()) {
        std::string ifEventName = "ifEvent" + std::to_string((*eventIndex)++);
        VNSceneGen::test(
          ifEventName,
          &(*itIf),
          eventIndex,
          eventInit,
          eventChain,
          includes
        );
        if(!ifPrevious.empty()) line(eventChain, ifPrevious + "->then(" + ifEventName + ");", "");
        ifPrevious = ifEventName;
        if(ifFirst == "") ifFirst = ifEventName;
        ++itIf;
      }

      if(ifFirst == "" || ifPrevious == "") {
        std::cout << "If event must have at least one event" << std::endl;
        assertUnreachable("VNSCeneGen::test: If event must have at least one event");
      }

      line(eventChain, eventName + "->ifTrue = " + ifFirst + ";", "");
      line(eventChain, eventName + "->ifEnd = " + ifPrevious + ";", "");
      break;
    }

    case VN_SCENE_EVENT_TYPE_SCENE_CHANGE: {
      initType = "VNSceneChangeEvent<" + event->sceneChange.scene + ">";
      toInclude = "games/vn/events/VNSceneChangeEvent.hpp";
      includes->push_back(event->sceneChange.include);
      break;
    }

    case VN_SCENE_EVENT_SET_DEFAULT_FONT: {
      initType = "VNSetDefaultFontEvent";
      toInclude = "games/vn/events/VNSetDefaultFontEvent.hpp";
      std::string strFont = "<font ";
      auto sdf = event->setDefaultFont;
      if(!sdf.font.empty()) strFont += "font=" + stringParser(sdf.font, NULL);
      if(!sdf.fontSize.empty()) strFont += " size=\"" + floatParser(sdf.fontSize, NULL) + "\"";
      if(!sdf.color.empty()) strFont += " color=\"" + colorParser(sdf.color, NULL) + "\"";
      if(!sdf.style.empty()) strFont += " style=" + stringParser(sdf.style, NULL);
      strFont += ">{{ text }}</font>";
      line(&eventInitAfter, eventName + "->font = " + stringParser(strFont, NULL) + ";", "");
      break;
    }

    default:
      std::cout << "Unknown event type: " << event->type << std::endl;
      assertUnreachable("VNSceneGen::test: Unknown event type");
  }

  if(!toInclude.empty()) includes->push_back(toInclude);

  line(eventInit, "auto " + eventName + " = vnManager->createEvent<" + initType + ">(" + initArgs + ");", "");
  lines(eventInit, eventInitAfter, "");
}

void VNSceneGen::generate(
  std::vector<std::string> *out,
  struct VNScene *scene,
  std::string tabs
) {
  struct ClassGenInfo classInfo;
  struct MethodGenInfo methodAssets;
  struct MethodGenInfo methodInit;

  // Load Scene
  SceneGenerator::generate(
    &scene->scene,
    classInfo,
    methodAssets,
    methodInit
  );


  // Events
  classInfo.includes.push_back("games/vn/events/VNDummyEvent.hpp");
  line(&methodInit.body, "assertNotNull(vnManager, \"VNSceneGenInit - VN Manager is null?\");", "");
  line(&methodInit.body, "VNEvent *previous = vnManager->createEvent<VNDummyEvent>();", "");
  line(&methodInit.body, "auto eventStart = previous;", "");

  int32_t eventIndex = 0;
  auto itEvents = scene->events.events.begin();
  std::string previous = "eventStart";

  std::vector<std::string> eventInit;
  std::vector<std::string> eventChain;

  while(itEvents != scene->events.events.end()) {
    line(&eventInit, "", "");
    std::string eventName = "event" + std::to_string(eventIndex++);
    VNSceneGen::test(
      eventName,
      &(*itEvents),
      &eventIndex,
      &eventInit,
      &eventChain,
      &classInfo.includes
    );
    if(!previous.empty()) line(&eventChain, previous + "->then(" + eventName + ");", "");
    previous = eventName;
    if(itEvents->type == VN_SCENE_EVENT_TYPE_GOTO_MARKER) previous = "";
    ++itEvents;
  }

  lines(&methodInit.body, eventInit, "");
  lines(&methodInit.body, eventChain, "");
  line(&methodInit.body, "vnManager->setEvent(eventStart);", "");

  // Add in methods
  CodeGen::methodGen(&classInfo.publicCode, methodAssets);
  line(&classInfo.publicCode, "", "");
  CodeGen::methodGen(&classInfo.publicCode, methodInit);

  CodeGen::classGen(out, classInfo);
}