#pragma once

#ifndef TCLI_INCLUDED
#define TCLI_INCLUDED

#include <memory>

//#include "tcommon.h"   contenuto in tconvert.h
#include "tconvert.h"

#include "tfilepath.h"

#undef DVAPI
#undef DVVAR
#ifdef TAPPTOOLS_EXPORTS
#define DVAPI DV_EXPORT_API
#define DVVAR DV_EXPORT_VAR
#else
#define DVAPI DV_IMPORT_API
#define DVVAR DV_IMPORT_VAR
#endif

//=========================================================

// forward declaration
class TFilePath;
//=========================================================

namespace TCli {

//=========================================================

inline bool fromStr(int &value, std::string s) {
  if (isInt(s)) {
    value = std::stoi(s);
    return true;
  } else
    return false;
}
inline bool fromStr(double &value, std::string s) {
  if (isDouble(s)) {
    value = std::stod(s);
    return true;
  } else
    return false;
}
inline bool fromStr(std::string &value, std::string s) {
  value = s;
  return true;
}
inline bool fromStr(TFilePath &value, std::string s) {
  value = TFilePath(s);
  return true;
}

//=========================================================

class UsageError {
  std::string m_msg;

public:
  UsageError(std::string msg) : m_msg(msg){};
  ~UsageError(){};
  std::string getError() const { return m_msg; };
};

//=========================================================

class DVAPI UsageElement {
protected:
  std::string m_name, m_help;
  bool m_selected;

public:
  UsageElement(std::string name, std::string help);
  virtual ~UsageElement(){};
  std::string getName() const { return m_name; };
  bool isSelected() const { return m_selected; };
  void select() { m_selected = true; };

  virtual bool isHidden() const { return false; };
  virtual bool isSwitcher() const { return false; };
  virtual bool isArgument() const { return false; };
  virtual bool isMultiArgument() const { return false; };
  void setHelp(std::string help) { m_help = help; };

  virtual void print(std::ostream &out) const;
  virtual void printHelpLine(std::ostream &out) const;
  virtual void dumpValue(std::ostream &out) const = 0;
  virtual void resetValue()                       = 0;

private:
  // not implemented
  UsageElement(const UsageElement &);
  UsageElement &operator=(const UsageElement &);
};

//=========================================================

class DVAPI Qualifier : public UsageElement {
protected:
  bool m_switcher;

public:
  Qualifier(std::string name, std::string help)
      : UsageElement(name, help), m_switcher(false){};
  ~Qualifier(){};

  bool isSwitcher() const override { return m_switcher; };

  bool isHidden() const override { return m_help == ""; };

  operator bool() const { return isSelected(); };
  virtual void fetch(int index, int &argc, char *argv[]) = 0;
  void print(std::ostream &out) const override;
};

//---------------------------------------------------------

class DVAPI SimpleQualifier : public Qualifier {
public:
  SimpleQualifier(std::string name, std::string help) : Qualifier(name, help){};
  ~SimpleQualifier(){};
  void fetch(int index, int &argc, char *argv[]) override;
  void dumpValue(std::ostream &out) const override;
  void resetValue() override;
};

//---------------------------------------------------------

class DVAPI Switcher final : public SimpleQualifier {
public:
  Switcher(std::string name, std::string help) : SimpleQualifier(name, help) {
    m_switcher = true;
  };
  ~Switcher(){};
};

//---------------------------------------------------------

template <class T>
class QualifierT final : public Qualifier {
  T m_value;

public:
  QualifierT<T>(std::string name, std::string help)
      : Qualifier(name, help), m_value(){};
  ~QualifierT<T>(){};

  T getValue() const { return m_value; };

  void fetch(int index, int &argc, char *argv[]) override {
    if (index + 1 >= argc) throw UsageError("missing argument");
    if (!fromStr(m_value, argv[index + 1]))
      throw UsageError(m_name + ": bad argument type /" +
                       std::string(argv[index + 1]) + "/");
    for (int i = index; i < argc - 1; i++) argv[i] = argv[i + 2];
    argc -= 2;
  };

  void dumpValue(std::ostream &out) const override {
    out << m_name << " = " << (isSelected() ? "on" : "off") << " : " << m_value
        << "\n";
  };

  void resetValue() override {
    m_value    = T();
    m_selected = false;
  };
};

//=========================================================

class DVAPI Argument : public UsageElement {
public:
  Argument(std::string name, std::string help) : UsageElement(name, help){};
  ~Argument(){};
  virtual void fetch(int index, int &argc, char *argv[]);
  virtual bool assign(char *) = 0;
  bool isArgument() const override { return true; };
};

//---------------------------------------------------------

template <class T>
class ArgumentT final : public Argument {
  T m_value;

public:
  ArgumentT<T>(std::string name, std::string help) : Argument(name, help){};
  ~ArgumentT<T>(){};
  operator T() const { return m_value; };
  T getValue() const { return m_value; };

  bool assign(char *src) override { return fromStr(m_value, src); };
  void dumpValue(std::ostream &out) const override {
    out << m_name << " = " << m_value << "\n";
  };
  void resetValue() override {
    m_value    = T();
    m_selected = false;
  };
};

//=========================================================

class DVAPI MultiArgument : public Argument {
protected:
  int m_count, m_index;

public:
  MultiArgument(std::string name, std::string help)
      : Argument(name, help), m_count(0), m_index(0){};
  ~MultiArgument(){};
  int getCount() const { return m_count; };

  void fetch(int index, int &argc, char *argv[]) override;
  bool isMultiArgument() const override { return true; };
  virtual void allocate(int count) = 0;
};

//---------------------------------------------------------

template <class T>
class MultiArgumentT final : public MultiArgument {
  std::unique_ptr<T[]> m_values;

public:
  MultiArgumentT(std::string name, std::string help)
      : MultiArgument(name, help) {}
  T operator[](int index) {
    assert(0 <= index && index < m_count);
    return m_values[index];
  };

  bool assign(char *src) override {
    assert(0 <= m_index && m_index < m_count);
    return fromStr(m_values[m_index], src);
  };

  void dumpValue(std::ostream &out) const override {
    out << m_name << " = {";
    for (int i = 0; i < m_count; i++) out << " " << m_values[i];
    out << "}" << std::endl;
  };

  void resetValue() override {
    m_values.reset();
    m_count = m_index = 0;
  };

  void allocate(int count) override {
    m_values.reset((count > 0) ? new T[count] : nullptr);
    m_count = count;
    m_index = 0;
  };
};

//=========================================================

typedef UsageElement *UsageElementPtr;

//---------------------------------------------------------

class DVAPI UsageLine {
protected:
  std::unique_ptr<UsageElementPtr[]> m_elements;
  int m_count;

public:
  UsageLine();
  virtual ~UsageLine();
  UsageLine(const UsageLine &ul);
  UsageLine &operator=(const UsageLine &ul);

  UsageLine(int count);
  UsageLine(const UsageLine &, UsageElement &elem);
  UsageLine(UsageElement &elem);
  UsageLine(UsageElement &a, UsageElement &b);

  UsageLine operator+(UsageElement &);

  int getCount() const { return m_count; };
  UsageElementPtr &operator[](int index) { return m_elements[index]; };
  const UsageElementPtr &operator[](int index) const {
    return m_elements[index];
  };
};

//---------------------------------------------------------

DVAPI UsageLine operator+(UsageElement &a, UsageElement &b);

//---------------------------------------------------------

class DVAPI Optional final : public UsageLine {
public:
  Optional(const UsageLine &ul);
  ~Optional(){};
};

//---------------------------------------------------------

DVAPI UsageLine operator+(const UsageLine &a, const Optional &b);

//=========================================================

class UsageImp;

class DVAPI Usage {
  std::unique_ptr<UsageImp> m_imp;

public:
  Usage(std::string progName);
  ~Usage();
  void add(const UsageLine &);

  void print(std::ostream &out) const;
  void dumpValues(std::ostream &out) const;  // per debug

  bool parse(int argc, char *argv[], std::ostream &err = std::cerr);
  bool parse(const char *argvString, std::ostream &err = std::cerr);
  void clear();  // per debug

private:
  // not implemented
  Usage(const Usage &);
  Usage &operator=(const Usage &);
};

//=========================================================

typedef QualifierT<int> IntQualifier;
typedef QualifierT<double> DoubleQualifier;
typedef QualifierT<std::string> StringQualifier;

typedef ArgumentT<int> IntArgument;
typedef ArgumentT<double> DoubleArgument;
typedef ArgumentT<std::string> StringArgument;
typedef ArgumentT<TFilePath> FilePathArgument;

typedef MultiArgumentT<int> IntMultiArgument;
typedef MultiArgumentT<double> DoubleMultiArgument;
typedef MultiArgumentT<std::string> StringMultiArgument;
typedef MultiArgumentT<TFilePath> FilePathMultiArgument;

//=========================================================

class DVAPI RangeQualifier final : public Qualifier {
  int m_from, m_to;

public:
  RangeQualifier();
  ~RangeQualifier(){};

  int getFrom() const { return m_from; };
  int getTo() const { return m_to; };
  bool contains(int frame) const { return m_from <= frame && frame <= m_to; };
  void fetch(int index, int &argc, char *argv[]) override;
  void dumpValue(std::ostream &out) const override;
  void resetValue() override;
};

//=========================================================

}  // namespace TCli

#endif  // TCLI_INCLUDED
