#pragma once

#include <QJsonObject>
#include <QObject>
#include <QString>

#include <functional>
#include <memory>

#include "geometry/snake.h"

#include "AreaData.h"

namespace routing {

class GeneratorBase : public QObject {
  Q_OBJECT
public:
  using Data = AreaData *;
  using Generator = std::function<bool(snake::Transects &)>;

  explicit GeneratorBase(QObject *parent = nullptr);
  explicit GeneratorBase(Data d, QObject *parent = nullptr);
  ~GeneratorBase();

  Q_PROPERTY(QString editorQml READ editorQml CONSTANT)
  Q_PROPERTY(QString mapVisualQml READ mapVisualQml CONSTANT)
  Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

  virtual QString editorQml() const = 0;
  virtual QString mapVisualQml() const = 0;

  virtual bool save(QJsonObject &obj) const; // must be called if overridden
  virtual bool load(const QJsonObject &obj,
                    QString &errorString); // must be called if overridden

  QString name() const;
  void setName(const QString &name);
  virtual QString abbreviation() const = 0;
  virtual QString type() const = 0;

  virtual bool get(Generator &generator) = 0;

  Data data() const;
  void setData(Data d);

  static const char *typeKey;
  static const char *nameKey;

signals:
  void generatorChanged();
  void dataChanged();
  void nameChanged();

protected:
  Data _d;
  QString _name;

private:
};

class GeneratorFactory {
  GeneratorFactory();
  GeneratorFactory(GeneratorFactory &other) = delete;
  static GeneratorFactory *createInstance();

public:
  typedef std::function<GeneratorBase *(QObject *)> Creator;
  ~GeneratorFactory();
  static GeneratorFactory *instance();

  bool registerGenerator(const QString &type,
                         Creator creator); // use REGISTER_GENERATOR to register
                                           // a generator befor main()
  bool registered(const QString &type);
  void unregisterGenerator(const QString &type);

  GeneratorBase *create(const QString &type, QObject *parent = nullptr);
  GeneratorBase *create(const QJsonObject &jsonGenerator, QString &errorMessage,
                        QObject *parent = nullptr);

private:
  std::map<QString, Creator> _creatorMap;
};

#define pGeneratorFactory ::routing::GeneratorFactory::instance()
#define REGISTER_GENERATOR(type, creator)                                      \
  namespace {                                                                  \
  auto registered = pGeneratorFactory -> registerGenerator(type, creator);     \
  }
} // namespace routing
