#pragma once
#include "IOptimizer.h"
#include "exprtk.hpp"
#include <thread>
#include <sqlite3.h> 
#include <mutex>

namespace mdd {
	////////////////////////////////////////////////////////////
	//
	//Optimization is always minimization!
	//
	////////////////////////////////////////////////////////////
	class OptimizerBase 
		: public IOptimizer 
	{
	protected:
		std::mutex _mutex;
		json _base_config;
		std::string key;
		const std::string type = "optimizer";
		std::shared_ptr<IModule> _module;
		std::vector<std::shared_ptr<IInput>> _inputs;
		std::vector<std::shared_ptr<IOutput>> _outputs;
		std::vector<double> _output_vals;
		exprtk::expression<double> _func_expr;

		std::function<json(const json&)> _callback;

		std::shared_ptr<std::thread> _thread_ptr;

		int step = -1;

		struct opt_state {
			state module_state = state::STATE_ERROR;
			double opt_value = 0;
		};
		opt_state updateOutputs();
		OptimizerBase(const std::string& base_config = "{}");

		bool configureChild(const json& config) override;
	public:
		struct Permutation {
		public:
			std::vector<std::vector<double>> dna;
			double fitness = 0;
			double time = 0;
			state status = state::UNCHANGED;

			bool operator== (const Permutation& ind);
			static bool same(Permutation const& a, Permutation const& b);
			static bool smaller(Permutation const& a, Permutation const& b);
		};

		bool connect(std::shared_ptr<IModule> module) override;
		void updateLayout();
		bool setEvaluation(std::string func) override;

		bool configure(const json& config) override;
		const json& getConfiguration() override;
		//virtual std::string getGeneratorID() = 0;
		void load(const json& j);
		json dump();
		json getIdentifier() override;

		void attachCallback(std::function<json(const json&)> callback) override;
		//state update() override;
		void callback_add_step(const std::vector<Permutation>& pers);
		void callback_evaluate_opt(const Permutation& per);
		void callback_autosave();
		void callback_reset();
		~OptimizerBase();

	private:
		bool exec_sql(const std::string& sql);
		json exec_sql_with_callback( const std::string& sql);

		json _jcall;

	protected:
		std::string _db_path;
		json Permutation_to_JSON(const Permutation& per);
		Permutation JSON_to_Permutation(const json& j);

		bool createDB(const std::string& db_path);
		bool clearDB();
		virtual bool loadDB(const json& j);
		virtual bool loadStepDB(const json& j);
		virtual bool loadPermutationDB(const json& j);

		json dumpPermutations();
		json dumpSteps();

		bool addPermutation(const json& per);
		bool addStep(int step, json dna);
		bool changePermutation(const json& per);

		virtual void reset();
		virtual bool reachAbort(const std::vector<Permutation>& pers);

		

	public:
		std::shared_ptr<Permutation> findPermutation(std::vector<std::vector<double>> dna);
		std::shared_ptr<Permutation> getPermutation(int step, std::vector<std::vector<double>> dna);
		json getPermutations() override;
		std::vector<Permutation> findBest(int step);

		int sql_callback(int argc, char** argv, char** azColName);
	};
}