#include "TransitionMachine.hpp"
#include "File.hpp"
#include "util.hpp"
#include <cstring>

TransitionMachine::TransitionMachine(bool trainMode)
{
  std::string filename = ProgramParameters::tmFilename;

  auto badFormatAndAbort = [&filename](const std::string & errInfo)
  {
    fprintf(stderr, "ERROR (%s) : file %s bad format. Aborting.\n", errInfo.c_str(), filename.c_str());

    exit(1);
  };

  this->trainMode = trainMode;

  File file(filename, "r");
  FILE * fd = file.getDescriptor();

  char buffer[1024];
  char buffer2[1024];
  char buffer3[1024];

  // Reading the name
  if(fscanf(fd, "Name : %[^\n]\n", buffer) != 1)
    badFormatAndAbort(ERRINFO);

  name = buffer;

  // Reading dicts
  if(fscanf(fd, "Dicts : %[^\n]\n", buffer) != 1)
    badFormatAndAbort(ERRINFO);

  if (ProgramParameters::dicts.empty())
    ProgramParameters::dicts = ProgramParameters::expPath + buffer;

  Dict::readDicts(ProgramParameters::expPath, ProgramParameters::dicts, trainMode);

  // Reading %CLASSIFIERS
  if(fscanf(fd, "%%%s\n", buffer) != 1 || buffer != std::string("CLASSIFIERS"))
    badFormatAndAbort(ERRINFO);

  while(fscanf(fd, "%%%s\n", buffer) != 1)
  {
    // Reading a classifier
    if(fscanf(fd, "%s %s\n", buffer, buffer2) != 2)
      badFormatAndAbort(ERRINFO);

    str2classifier.emplace(buffer, std::unique_ptr<Classifier>(new Classifier(buffer2, trainMode)));
  }

  // Reading %STATES
  if(buffer != std::string("STATES"))
    badFormatAndAbort(ERRINFO);

  currentState = "";
  initialState = "";

  while(fscanf(fd, "%%%s\n", buffer) != 1)
  {
    // Reading a state
    if(fscanf(fd, "%s %s\n", buffer, buffer2) != 2)
      badFormatAndAbort(ERRINFO);

    if(str2classifier.count(buffer2) == 0)
      badFormatAndAbort(ERRINFO + std::string(" unknown classifier \'") + buffer2 + std::string("\'"));

    str2state.emplace(buffer, std::unique_ptr<State>(new State(buffer, buffer2)));

    if(currentState.empty()) // Initial state = first state in the file
    {
      currentState = buffer;
      initialState = currentState;
    }
  }

  // Reading %TRANSITIONS
  if(buffer != std::string("TRANSITIONS"))
    badFormatAndAbort(ERRINFO);

  while(fscanf(fd, "%s %s %[^\n]\n", buffer, buffer2, buffer3) == 3)
  {
    std::string src(buffer);
    std::string dest(buffer2);
    std::string prefix(buffer3);

    if(str2state.count(src) == 0)
      badFormatAndAbort(ERRINFO + std::string(" unknown state \'") + src + std::string("\'"));

    if(str2state.count(dest) == 0)
      badFormatAndAbort(ERRINFO + std::string(" unknown state \'") + dest + std::string("\'"));

    State * srcState = str2state[src].get();

    srcState->transitions.emplace_back(dest, prefix);
  }
}

TransitionMachine::State::State(const std::string & name, const std::string & classifier)
{
  this->name = name;
  this->classifier = classifier;
}

TransitionMachine::Transition::Transition(const std::string & dest, const std::string & prefix)
{
  this->dest = dest;
  this->actionPrefix = prefix;
}

std::string & TransitionMachine::getCurrentState()
{
  return currentState;
}

TransitionMachine::Transition * TransitionMachine::getTransition(const std::string & action)
{
  int longestPrefix = -1;
  State * currentStatePtr = str2state[currentState].get();

  for (unsigned int i = 0; i < currentStatePtr->transitions.size(); i++)
  {
    auto & transition = currentStatePtr->transitions[i];
    unsigned int currentMaxLength = longestPrefix >= 0 ? currentStatePtr->transitions[longestPrefix].actionPrefix.size() : 0;

    if(transition.actionPrefix == "*" || !strncmp(action.c_str(), transition.actionPrefix.c_str(), transition.actionPrefix.size()))
      if (transition.actionPrefix.size() > currentMaxLength)
        longestPrefix = i;
  }

  if (longestPrefix != -1)
    return &currentStatePtr->transitions[longestPrefix];

  fprintf(stderr, "ERROR (%s) : no corresponding transition for action \'%s\' and state \'%s\'. Aborting.\n", ERRINFO, action.c_str(), currentStatePtr->name.c_str());

  exit(1);

  return nullptr;
}

void TransitionMachine::takeTransition(Transition * transition)
{
  currentState = transition->dest;
}

std::vector<Classifier*> TransitionMachine::getClassifiers()
{
  std::vector<Classifier*> classifiers;

  for (auto & it : str2classifier)
    classifiers.emplace_back(it.second.get());

  return classifiers;
}

void TransitionMachine::reset()
{
  currentState = initialState;
}

Classifier * TransitionMachine::getCurrentClassifier()
{
  return str2classifier[str2state[currentState]->classifier].get();
}