/*****************************************************************//**
 * \file   RestrictedMasterProblemInterface.h
 * \brief  The generic interface for restricted master problem that inherent classes should override to fit with the branch and price algorithm.
 * 
 * \author talmefta
 * \date   May 2022
 *********************************************************************/
#ifndef RESTRICTEDMASTERPROBLEMINTERFACE_H
#define RESTRICTEDMASTERPROBLEMINTERFACE_H

#include "Header.h"
#include "RestrictedMasterProblemInitializer.h"
#include "BaseComponent.h"
#include "Constraint.h"
#include "RMPNodeObserver.h"
#include "RMPCutInformationObserver.h"
#include "RMPBaPObserver.h"


/**
 * \class RestrictedMasterProblemInterface
 * \brief Generic interface of restrictd master problem that contains the essantial method signatures with which the branch and price algorithm works.
 */
template<class Instance, class SolutionType, class DualVariables, class ColumnsType, class TypeConstraint>
class RestrictedMasterProblemInterface: public BaseComponent {

protected:
	Instance&											                   instance;	/**< The studied instance.*/
	SolutionType														   solution;	/**< The solution value.*/
	ColumnsType																columns;	/**< The columns.*/
	double															objective_value;	/**< The objective value.*/
	double														objective_value_int;	/**< The objective value of the best integer solution.*/
	unordered_set<int>										   solution_columns_int;	/**< Indexes of the columns in the best integer solution.*/
	DualVariables													 dual_variables;	/**< The dual variables.*/
	RestrictedMasterProblemInitializer<Instance, ColumnsType>*			initializer;    /**< The initialization algorithm.*/
	string															        request;    /**< Initialization request.*/
	string															    cut_request;    /**< Cut generator request.*/
	string														  branching_request;    /**< Branching rule request.*/
	vector<Constraint<TypeConstraint>*>						  branching_constraints;	/**< Stores the branching constraints.*/
	vector<Constraint<TypeConstraint>*>							    cut_constraints;	/**< Stores the cut constraints.*/
	vector<Constraint<TypeConstraint>*>					ineffective_cut_constraints;	/**< Stores the ineffective cut constraints.*/
	list<RMPNodeObserver*>											 list_observers;
	list<RMPCutInformationObserver*>		   list_cut_family_information_observer;
	RMPBaPObserver* bapObserver;
	int															 number_new_columns;
	int														  solving_rmp_iteration;	/**< Variable counting the ith time the RMP is solved.*/
	double															   solving_time;
	double													 initial_solution_value;

public:
	/**
	 * Constructor with the instance.
	 * 
	 * \param instance: Instance&
	 * \param initializer: RestrictedMasterProblemInitializer*
	 */
	RestrictedMasterProblemInterface(Instance& instance) :
		instance(instance), solution(), columns(), objective_value(), dual_variables(), number_new_columns() {
	}

	/**
	 * Destructor.
	 * 
	 */
	virtual ~RestrictedMasterProblemInterface() {
	}

	/**
	 * Get method of instance.
	 *
	 * \return instance: Instance&
	 */
	Instance& get_instance() {
		return this->instance;
	}

	/**
	 * Get method of objective_value.
	 *
	 * \return objective_value: const double&
	 */
	const double get_objective_value() const {
		return this->objective_value;
	}

	const double& get_objective_value_int() const {
		return this->objective_value_int;
	}

	const unordered_set<int>& get_solution_columns_int() const {
		return this->solution_columns_int;
	}

	/**
	 * Get method of initial_solution_value.
	 *
	 * \return initial_solution_value: const double&
	 */
	const double get_initial_solution_value() const {
		return this->initial_solution_value;
	}
	
	/**
	 * Get method of solving_time.
	 *
	 * \return solving_time: const double&
	 */
	const double get_solving_time() const {
		return this->solving_time;
	}

	/**
	 * Get method of solution.
	 *
	 * \return solution: const SolutionType&
	 */
	const SolutionType& get_solution() const {
		return this->solution;
	}

	/**
	 * Get method of columns.
	 *
	 * \return solution: const ColumnsType&
	 */
	ColumnsType& get_columns() {
		return this->columns;
	}

	/**
	 * Get method of dual_variables.
	 *
	 * \return dual_variables: const SolutionType&
	 */
	DualVariables& get_dual_variables() {
		return this->dual_variables;
	}

	/**
	 * Set method of request.
	 *
	 * \param new_request: const string&
	 */
	void set_request(const string& new_request) {
		this->request = new_request;
	}

	/**
	 * Set method of cut_request.
	 *
	 * \param new_cut_request: const string&
	 */
	void set_cut_request(const string& new_cut_request) {
		this->cut_request = new_cut_request;
	}

	/**
	 * Get method of cut_request.
	 *
	 * \return cut_request: const string&
	 */
	const string& get_cut_request() const {
		return this->cut_request;
	}

	/**
	 * Get method of branching_request.
	 *
	 * \return branching_request: const string&
	 */
	const string& get_branching_request() const {
		return this->branching_request;
	}

	/**
	 * Get method of number_new_columns.
	 *
	 * \return number_new_columns: const int&
	 */
	const int& get_number_new_columns() const {
		return this->number_new_columns;
	}

	/**
	 * Get method of solving_rmp_iteration.
	 *
	 * \return solving_rmp_iteration: const int&
	 */
	const int& get_solving_rmp_iteration() const {
		return this->solving_rmp_iteration;
	}

	/**
	 * Increment method of solving_rmp_iteration.
	 */
	void increment_solving_rmp_iteration() {
		this->solving_rmp_iteration++;
	}

	/**
	 * Set method of number_new_columns.
	 *
	 * \param new_number_new_columns: const int&
	 */
	void set_number_new_columns(const int& new_number_new_columns) {
		this->number_new_columns = new_number_new_columns;
	}

	/**
	 * Set method of initial_solution_value.
	 *
	 * \param new_value: const double&
	 */
	void set_initial_solution_value(const double& new_value) {
		this->initial_solution_value = new_value;
	}

	/**
	 * Set method of solving_time.
	 *
	 * \param new_value: const int&
	 */
	void set_solving_time(const double& new_value) {
		this->solving_time = new_value;
	}

	/**
	 * Set method of branching_request.
	 *
	 * \param new_branching_request: const string&
	 */
	void set_branching_request(const string& new_branching_request) {
		this->branching_request = new_branching_request;
	}

	/**
	* Set method of initializer.
	*
	* \param new_initializer: RestrictedMasterProblemInitializer<Instance, ColumnsType>*
	*/
	void set_intializer(RestrictedMasterProblemInitializer<Instance, ColumnsType>* new_initializer) {
		this->initializer = new_initializer;
	}

	/**
	 * Initialize the restricted master problem with a set of feasible columns depending on the strategy of initialization.
	 *
	 */
	virtual void initialize() {
		RestrictedMasterProblemInitializer<Instance, ColumnsType>* algo = this->initializer;
		while (algo != nullptr) {
			ColumnsType result = algo->initialize(request);
			for (auto& column : result)
				this->columns.push_back(column);
			algo = algo->get_next_algorithm();
		}
	}

	/**
	 * Get method of intializer.
	 * 
	 * \return 
	 */
	RestrictedMasterProblemInitializer<Instance, ColumnsType>* get_initializer() {
		return this->initializer;
	}

	/**
	 * construct the model with the adequate variables, constraints and objective function. Has to be override.
	 * 
	 */
	virtual void construct() = 0;

	/**
	 * Solves the model with the adequate parameters. Has to be override.
	 * 
	 */
	virtual void solve() = 0;
	virtual void call_restricted_master_heuristic_new_MIP(const double& timeLimit, const unordered_set<int>& warmStart, const bool& fix_variables = false) = 0;
	virtual string to_string_integer_solution() = 0;

	virtual void sanity_check_integer_solution() const = 0;

	virtual bool is_infeasible_dummy_variable() const = 0;

	/**
	* Compute the part of the Lagrangian bound concerning the RMP
	*/
	virtual double compute_lagrangian_bound_RMP_obj_term() const = 0;

	/**
	 * Notifies the mediator after solving the problem and communicates the dual variables.
	 *
	 */
	void notify_mediator() const {
		this->mediator->notify((BaseComponent*)(this), "NEWDUALVARIABLES");
	}

	/**
	 * Get method of branching_constraints.
	 * 
	 * \return vector<Constraint>&
	 */
	vector<Constraint<TypeConstraint>*>& get_branching_constraints() {
		return this->branching_constraints;
	}

	/**
	 * Get method of cut_constraints.
	 *
	 * \return vector<Constraint>&
	 */
	vector<Constraint<TypeConstraint>*>& get_cut_constraints() {
		return this->cut_constraints;
	}

	/**
	 * Get method of ineffective_cut_constraints.
	 *
	 * \return vector<Constraint>&
	 */
	vector<Constraint<TypeConstraint>*>& get_ineffective_cut_constraints() {
		return this->ineffective_cut_constraints;
	}

	void remove_ineffective_cuts() {
		this->ineffective_cut_constraints.clear();
	}

	/**
	 * Extracts the solution values. Has to be override.
	 *
	 */
	virtual void extract_solution_value() = 0;


	/**
	 * Extracts the dual solution values. Has to be override.
	 *
	 */
	virtual void extract_dual_solution_value() = 0;


	/**
	 * Writes the solution values on the screen or whatever wanted method. Has to be override.
	 *
	 */
	virtual void write_solution_value() = 0;
	virtual string write_solution_value_in_string() = 0;


	/**
	 * Extracts the dual solution values on the screen or whatever wanted method. Has to be override.
	 *
	 */
	virtual void write_dual_solution_value() = 0;

	/**
	 * Inserts new columns provided to nurture the restricted model.
	 * 
	 * \param new_columns: const ColumnsType&
	 */
	virtual void insert_columns(const ColumnsType& new_columns) = 0;

	/**
	 * Checks if all variables are binary.
	 * 
	 * \return true if it is correct, false otherwise.
	 */
	virtual bool are_all_variables_binary() const = 0;
	virtual void update_integer_solution() = 0;

	/**
	 * Checks if the problem after solving is infeasible.
	 *
	 * \return true if it is infeasible, false otherwise.
	 */
	virtual bool is_infeasible() const = 0;
	virtual bool is_feasible_MIP() const = 0;

	virtual void change_variable_type_to_binary() = 0;

	virtual void change_variable_type_to_continuous() = 0;

	//virtual bool add_auxiliary_variables_and_induced_linking_constraints() = 0;
	//virtual bool remove_auxiliary_variables_and_induced_linking_constraints() = 0;

	//OBSERVER design pattern methods
	////////////////////////////////////////////
	virtual void attach(RMPNodeObserver* observer) = 0;

	virtual void detach(RMPNodeObserver* observer) = 0;

	//virtual void notify_nodes_arc_branching_constraints(const vector<pair<int, pair< int, int>>>& new_column_indexes_and_involved_items) = 0;
	
	virtual void notify_nodes_clique_inequalities(const vector<int>& new_column_indexes) = 0;

	virtual void notify_nodes_for_ineffective_cuts() = 0;

	virtual void notify_BaP_for_new_incumbent() = 0;

	virtual void attach(RMPCutInformationObserver* observer) = 0;

	virtual void detach(RMPCutInformationObserver* observer) = 0;

	virtual void attach(RMPBaPObserver* observer) = 0;

	virtual void detach_BaP_observer() = 0;

	virtual void notify_cut_information_observer() = 0;
	/////////////////////////////////////////////////////////////
	/// 
	virtual bool detect_conflicting_branching_rules() const = 0;



};
#endif //RESTRICTEDMASTERPROBLEMINTERFACE_H
