#ifndef CONFIG__H
#define CONFIG__H

#include <memory>
#include <string>
#include <vector>
#include <boost/circular_buffer.hpp>
#include "util.hpp"
#include "Dict.hpp"
#include "Strategy.hpp"

class Transition;

class Config
{
  public :

  static constexpr const char * EOSColName = "EOS";
  static constexpr const char * EOSSymbol1 = "true";
  static constexpr const char * EOSSymbol0 = "false";
  static constexpr const char * headColName = "HEAD";
  static constexpr const char * deprelColName = "DEPREL";
  static constexpr const char * idColName = "ID";
  static constexpr const char * sentIdColName = "SENTID";
  static constexpr const char * isMultiColName = "MULTI";
  static constexpr const char * childsColName = "CHILDS";
  static constexpr const char * commentsColName = "COMMENTS";
  static constexpr const char * rawRangeStartColName = "RAWSTART";
  static constexpr const char * rawRangeEndColName = "RAWEND";
  static constexpr int nbHypothesesMax = 1;
  static constexpr int maxNbAppliableSplitTransitions = 8;

  enum class Object
  {
    Buffer,
    Stack
  };

  public :

  using Utf8String = util::utf8string;
  using ValueIterator = std::vector<util::String>::iterator;
  using ConstValueIterator = std::vector<util::String>::const_iterator;

  private :

  std::vector<util::String> lines;

  protected :

  Utf8String rawInput;
  bool rawInputIsComplete{true};
  std::size_t wordIndex{0};
  std::size_t characterIndex{0};
  std::size_t currentSentenceStartRawInput{0};
  util::String state{"NONE"};
  boost::circular_buffer<util::String> history{10};
  std::map<std::string, boost::circular_buffer<util::String>> stateHistory;
  boost::circular_buffer<std::size_t> stack{50};
  float chosenActionScore{0.0};
  std::vector<std::string> extraColumns{commentsColName, rawRangeStartColName, rawRangeEndColName, isMultiColName, childsColName, sentIdColName, EOSColName};
  std::set<std::string> predicted;
  int lastPoppedStack{-1};
  int lastAttached{-1};
  int currentWordId{0};
  std::vector<Transition *> appliableSplitTransitions;
  std::vector<int> appliableTransitions;
  std::shared_ptr<Strategy> strategy;
  std::string mcd;

  protected :

  Config() = default;
  Config & operator=(const Config & other) = default;
  Config(const Config & other);
  virtual ~Config() = default;

  public :

  static Object str2object(const std::string & s);

  virtual std::size_t getNbColumns() const = 0;
  virtual std::size_t getColIndex(const std::string & colName) const = 0;
  virtual bool hasColIndex(const std::string & colName) const = 0;
  virtual std::size_t getFirstLineIndex() const = 0;
  virtual const std::string & getColName(int colIndex) const = 0;

  std::size_t getIndexOfLine(int lineIndex) const;
  std::size_t getIndexOfCol(int colIndex) const;
  std::size_t getNbLines() const;
  void addLines(unsigned int nbLines);
  void resizeLines(unsigned int nbLines);
  bool has(int colIndex, int lineIndex, int hypothesisIndex) const;
  util::String & get(int colIndex, int lineIndex, int hypothesisIndex);
  const util::String & getConst(int colIndex, int lineIndex, int hypothesisIndex) const;
  util::String & getLastNotEmpty(int colIndex, int lineIndex);
  util::String & getLastNotEmptyHyp(int colIndex, int lineIndex);
  const util::String & getLastNotEmptyHypConst(int colIndex, int lineIndex) const;
  const util::String & getLastNotEmptyConst(int colIndex, int lineIndex) const;
  const util::String & getAsFeature(int colIndex, int lineIndex) const;
  ValueIterator getIterator(int colIndex, int lineIndex, int hypothesisIndex);
  ConstValueIterator getConstIterator(int colIndex, int lineIndex, int hypothesisIndex) const;
  std::size_t & getStackRef(int relativeIndex);

  long getRelativeWordIndex(int relativeIndex) const;
  long getRelativeDistance(int fromIndex, int toIndex) const;

  public :

  void print(FILE * dest, bool printHeader = true) const;
  void printForDebug(FILE * dest) const;
  bool has(const std::string & colName, int lineIndex, int hypothesisIndex) const;
  util::String & get(const std::string & colName, int lineIndex, int hypothesisIndex);
  const util::String & getConst(const std::string & colName, int lineIndex, int hypothesisIndex) const;
  util::String & getLastNotEmpty(const std::string & colName, int lineIndex);
  const util::String & getLastNotEmptyConst(const std::string & colName, int lineIndex) const;
  util::String & getLastNotEmptyHyp(const std::string & colName, int lineIndex);
  const util::String & getLastNotEmptyHypConst(const std::string & colName, int lineIndex) const;
  const util::String & getAsFeature(const std::string & colName, int lineIndex) const;
  util::String & getFirstEmpty(int colIndex, int lineIndex);
  util::String & getFirstEmpty(const std::string & colName, int lineIndex);
  bool hasCharacter(int letterIndex) const;
  const util::utf8char & getLetter(int letterIndex) const;
  bool getRawInputStatus() const;
  void setRawInputStatus(bool status);
  void rawInputPop();
  void rawInputAdd(util::utf8char letter);
  void addToHistory(const std::string & transition);
  void addToStack(std::size_t index);
  void popStack();
  void swapStack(int relIndex1, int relIndex2);
  bool isMultiword(std::size_t lineIndex) const;
  bool isMultiwordPredicted(std::size_t lineIndex) const;
  int getMultiwordSize(std::size_t lineIndex) const;
  int getMultiwordSizePredicted(std::size_t lineIndex) const;
  bool isEmptyNode(std::size_t lineIndex) const;
  bool isEmptyNodePredicted(std::size_t lineIndex) const;
  bool isToken(std::size_t lineIndex) const;
  bool isTokenPredicted(std::size_t lineIndex) const;
  bool moveWordIndex(int relativeMovement);
  bool canMoveWordIndex(int relativeMovement) const;
  void moveWordIndexRelaxed(int relativeMovement);
  bool moveCharacterIndex(int relativeMovement);
  bool canMoveCharacterIndex(int relativeMovement) const;
  bool rawInputOnlySeparatorsLeft() const;
  std::size_t getWordIndex() const;
  std::size_t getCharacterIndex() const;
  long getRelativeWordIndex(Object object, int relativeIndex) const;
  bool hasRelativeWordIndex(Object object, int relativeIndex) const;
  const util::String & getHistory(int relativeIndex) const;
  const util::String & getHistoryState(int relativeIndex) const;
  std::size_t getStack(int relativeIndex) const;
  std::size_t getStackSize() const;
  bool hasHistory(int relativeIndex) const;
  bool hasHistoryState(int relativeIndex) const;
  bool hasStack(int relativeIndex) const;
  util::String getState() const;
  void setState(const std::string state);
  bool stateIsDone() const;
  void addPredicted(const std::set<std::string> & predicted);
  bool isPredicted(const std::string & colName) const;
  int getLastPoppedStack() const;
  int getLastAttached() const;
  void setLastAttached(int lastAttached);
  int getCurrentWordId() const;
  void setCurrentWordId(int currentWordId);
  void addMissingColumns();
  void setAppliableSplitTransitions(const std::vector<Transition *> & appliableSplitTransitions);
  void setAppliableTransitions(const std::vector<int> & appliableTransitions);
  const std::vector<Transition *> & getAppliableSplitTransitions() const;
  const std::vector<int> & getAppliableTransitions() const;
  bool isExtraColumn(const std::string & colName) const;
  void setStrategy(const std::vector<std::string> & strategyDefinition);
  Strategy & getStrategy();
  void setChosenActionScore(float chosenActionScore);
  float getChosenActionScore() const;
};

#endif