/*****************************************************************//**
 * \file   Node.h
 * \brief  Represents the node in the Branch and Price tree.
 * 
 * \author talmefta
 * \date   May 2022
 *********************************************************************/
#ifndef NODE_H
#define NODE_H

#include "Header.h"
#include "Constraint.h"
#include "ArcBranchConstraint.h"
#include "RMPNodeObserver.h"

/**
*  \class Node
 * \brief A generic node class representing the node in the Branch and Price tree.
 * \details contains a pointer to generic restricted master problem and a pointer to a generic pricing problem.
 */
static int global_id = 0;

template<class RestrictedMasterProblem, class PricingSubProblem, class ColumnGeneration, class ConstraintType>
class Node : public RMPNodeObserver {

protected:
	RestrictedMasterProblem*						  restricted_master_problem; /**< Pointer to the restricted master problem.*/
	PricingSubProblem*										 pricing_subproblem; /**< Pointer to the pricing sub-problem.*/
	ColumnGeneration*										  column_generation; /**< Pointer to the column_generation algorithm.*/
	int								  					                     id; /**< The node's id. Set to 0 if it is the root node.*/
	int													              parent_id; /**< The node parent's id. Set to 0 if it is the root node.*/
	double															dual_bound; /**< The dual bound in this node on the objective funtion of the restricted master problem. (it is lower\upper in case of min\max) */
	double															rmp_value; /**< The the objective funtion value of the restricted master problem at the end of the execution of the node. */
	double													 parent_bound; /**< The bound of the node's parent. */
	vector<double>												       solution; /**< The solution of the linear relaxation of the restricted master problem.*/
	vector<Constraint<ConstraintType>*>					  branching_constraints; /**< Stores the branching constraints.*/
	vector<Constraint<ConstraintType>*>							cut_constraints; /**< Stores the cuts constraints.*/
	int																	  depth; /**< Stores the depth of the node in the seatch tree.*/
	double													   total_time_spent; /**< Stores the time spent to solve the node.*/
	double										   column_generation_time_spent; /**< Stores the time spent to solve the node.*/
	double											  cut_generation_time_spent; /**< Stores the time spent to solve the node.*/
	int														   nb_columns_added;
	int															  nb_cuts_added;
	double													best_integer_value;
public:
	/**
	 * The constructor by default. Initializes all the pointers to nullptr and dual_bound to 0.
	 */
	Node() : restricted_master_problem(nullptr), pricing_subproblem(nullptr), column_generation(nullptr), id(global_id), parent_id(), dual_bound(), solution(),
		branching_constraints(), cut_constraints(), parent_bound(), depth(), total_time_spent(), column_generation_time_spent(), cut_generation_time_spent() {
	}

	/**
	 * Constructor with pointers to the restricted master problem, the pricing problem and to the column generation algorithm.
	 *
	 * \param restricted_master_problem: RestrictedMasterProblem*
	 * \param pricing_subproblem: PricingSubProblem*
	 * \param column_generation: ColumnGeneration*
	 */
	Node(RestrictedMasterProblem* restricted_master_problem, PricingSubProblem* pricing_subproblem, ColumnGeneration* column_generation, const double& best_integer_value):
		restricted_master_problem(restricted_master_problem), pricing_subproblem(pricing_subproblem), column_generation(column_generation), id(global_id/*++*/),
		parent_id(), dual_bound(), solution(), branching_constraints(), cut_constraints(), parent_bound(), depth(), total_time_spent(), nb_columns_added(), nb_cuts_added(),
		column_generation_time_spent(), cut_generation_time_spent() {
		this->best_integer_value = best_integer_value;
		/*if (restricted_master_problem)
			dual_bound = restricted_master_problem->get_objective_value();*/
		this->dual_bound = 1e15/*numeric_limits<double>::max()*/;
		this->rmp_value = 1e15/*numeric_limits<double>::max()*/;
	}

	/**
	 * Copy constructor.
	 * \param node: const Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>&
	 */
	Node(const Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstraintType>& node) :
		restricted_master_problem(node.restricted_master_problem), pricing_subproblem(node.pricing_subproblem), column_generation(node.column_generation), id(node.id),
		parent_id(node.parent_id), dual_bound(node.dual_bound), solution(node.solution), branching_constraints(node.branching_constraints), cut_constraints(node.cut_constraints),
		parent_bound(node.parent_bound), rmp_value(node.rmp_value), depth(node.depth), nb_columns_added(node.nb_columns_added),
		nb_cuts_added(node.nb_cuts_added), total_time_spent(node.total_time_spent), column_generation_time_spent(node.column_generation_time_spent),
		cut_generation_time_spent(node.cut_generation_time_spent), best_integer_value(node.best_integer_value) {
	}


	/**
	 * Destructor. 
	 */
	~Node() {
	}

	/**
	 * Get-method of restricted_master_problem.
	 * 
	 * \return RestrictedMasterProblem*.
	 */
	RestrictedMasterProblem* get_restricted_master_problem() {
		return this->restricted_master_problem;
	}

	/**
	 * Get-method of pricing_subproblem.
	 *
	 * \return PricingSubProblem*.
	 */
	PricingSubProblem* get_pricing_subproblem() {
		return this->pricing_subproblem;
	}

	/**
	 * Get-method of column_generation.
	 *
	 * \return ColumnGeneration*.
	 */
	ColumnGeneration* get_column_generation() {
		return this->column_generation;
	}

	/**
	 * Get-method of parent.
	 *
	 * \return Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>*.
	 */
	int get_parent_id() {
		return this->parent_id;
	}

	/**
	 * Get-method of parent.
	 *
	 * \return Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>*.
	 */
	int get_id() {
		return this->id;
	}

	/**
	 * Get-method of dual_bound.
	 *
	 * \return const double&.
	 */
	const double& get_dual_bound() const {
		return this->dual_bound;
	}

	const double& get_best_integer_value() const {
		return this->best_integer_value;
	}
	void set_best_integer_value(const double& val) {
		this->best_integer_value = val;
	}


	/**
	 * Get-method of rmp_value.
	 *
	 * \return const double&.
	 */
	const double& get_rmp_value() const {
		return this->rmp_value;
	}

	/**
	 * Get-method of parent_bound.
	 *
	 * \return const double&.
	 */
	const double& get_parent_bound() const {
		return this->parent_bound;
	}

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

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

	/**
	 * Get-method of total_time_spent.
	 *
	 * \return const double&.
	 */
	const double& get_total_time_spent() const {
		return this->total_time_spent;
	}

	/**
	 * Get-method of column_generation_time_spent.
	 *
	 * \return const double&.
	 */
	const double& get_column_generation_time_spent() const {
		return this->column_generation_time_spent;
	}

	/**
	 * Get-method of cut_generation_time_spent.
	 *
	 * \return const double&.
	 */
	const double& get_cut_generation_time_spent() const {
		return this->cut_generation_time_spent;
	}

	/**
	 * Get-method of nb_columns_added.
	 *
	 * \return const int&.
	 */
	const int& get_nb_columns_added() const {
		return this->nb_columns_added;
	}

	/**
	 * Get-method of nb_cuts_added.
	 *
	 * \return const int&.
	 */
	const int& get_nb_cuts_added() const {
		return this->nb_cuts_added;
	}

	/**
	 * Checks if the problem after solving is infeasible.
	 *
	 * \return true if it is infeasible, false otherwise.
	 */
	bool is_infeasible() const {
		return this->restricted_master_problem->is_infeasible();
	}


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

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

	/**
	 * \brief Adds branching constraint .
	 * 
	 * \param branching_constraint: const Constraint<ConstraintType> branching_constraint
	 */
	void add_branching_constraint(Constraint<ConstraintType>* branching_constraint) {
		this->branching_constraints.push_back(branching_constraint);
	}

	/**
	 * \brief Adds cut constraint .
	 *
	 * \param cut_constraint: const Constraint<ConstraintType> cut_constraint
	 */
	void add_cut_constraint(Constraint<ConstraintType>* cut_constraint) {
		this->cut_constraints.push_back(cut_constraint);
	}

	/**
	 * \brief Removes branching constraint from the list of branching constraints.
	 *
	 * \param branching_constraint: const Constraint<ConstraintType>& branching_constraint
	 */
	void remove_branching_constraint(const Constraint<ConstraintType>& branching_constraint) {
		auto branching = find(this->branching_constraints.begin(), this->branching_constraints.end(), branching_constraint);
		if (branching != this->branching_constraints.end())
			this->branching_constraints.erase(branching);
	}

	/**
	 * \brief Removes cut constraint from the list of cut constraints.
	 *
	 * \param cut_constraint: const Constraint<ConstraintType>& cut_constraint
	 */
	void remove_cut_constraint(const Constraint<ConstraintType>& cut_constraint) {
		auto cut = find(this->cut_constraints.begin(), this->cut_constraints.end(), cut_constraint);
		if (cut != this->cut_constraints.end())
			this->cut_constraints.erase(cut);
	}


	/**
	 * Set-method of restricted_master_problem.
	 * \param new_restricted_master_problem : const RestrictedMasterProblem*
	 */
	void set_restricted_master_problem(const RestrictedMasterProblem* new_restricted_master_problem) {
		this->restricted_master_problem = new_restricted_master_problem;
	}

	/**
	 * Set-method of pricing_subproblem.
	 * \param new_pricing_subproblem : const PricingSubProblem*
	 */
	void set_pricing_subproblem(const PricingSubProblem* new_pricing_subproblem) {
		this->pricing_subproblem = new_pricing_subproblem;
	}

	/**
	 * Set-method of column_generation.
	 * \param new_column_generation : const ColumnGeneration*
	 */
	void set_column_generation(const ColumnGeneration* new_column_generation) {
		this->column_generation = new_column_generation;
	}

	/**
	 * Set-method of parent.
	 * \param new_parent : Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>*
	 */
	void set_parent_id(int new_parent) {
		this->parent_id = new_parent;
	}

	/**
	 * Set-method of parent.
	 * \param new_parent : Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>*
	 */
	void set_id(int new_id) {
		this->id = new_id;
	}

	/**
	 * Set-method of dual_bound.
	 * \param dual_bound : double
	 */
	void set_dual_bound(double new_dual_bound) {
		this->dual_bound = new_dual_bound;
	}

	/**
	 * Set-method of rmp_value.
	 * \param rmp_value : double
	 */
	void set_rmp_value(double new_rmp_value) {
		this->rmp_value = new_rmp_value;
	}

	/**
	 * Set-method of parent_bound.
	 * \param new_parent_bound : double
	 */
	void set_parent_bound(double new_parent_bound) {
		this->parent_bound = new_parent_bound;
	}

	/**
	 * Set-method of solution.
	 * \param new_solution : const vector<double>&
	 */
	void set_solution(const vector<double>& new_solution) {
		this->solution = new_solution;
	}


	/**
	 * Set-method of depth.
	 * \param new_depth : int
	 */
	void set_depth(int new_depth) {
		this->depth = new_depth;
	}

	/**
	 * Set-method of nb_columns_added.
	 * \param new_nb_columns_added : int
	 */
	void set_nb_columns_added(int new_nb_columns_added) {
		this->nb_columns_added = new_nb_columns_added;
	}

	/**
	 * Set-method of nb_cuts_added.
	 * \param new_nb_columns_added : int
	 */
	void set_nb_cuts_added(int new_nb_cuts_added) {
		this->nb_cuts_added = new_nb_cuts_added;
	}

	/**
	 * Set-method of total_time_spent.
	 * \param new_total_time_spent : double
	 */
	void set_total_time_spent(double new_total_time_spent) {
		this->total_time_spent = new_total_time_spent;
	}

	/**
	 * Set-method of column_generation_time_spent.
	 * \param new_column_generation_time_spent : double
	 */
	void set_column_generation_time_spent(double new_column_generation_time_spent) {
		this->column_generation_time_spent = new_column_generation_time_spent;
	}

	/**
	 * Set-method of cut_generation_time_spent.
	 * \param new_cut_generation_time_spent: double
	 */
	void set_cut_generation_time_spent(double new_cut_generation_time_spent) {
		this->cut_generation_time_spent = new_cut_generation_time_spent;
	}

	/**
	 * Runs the column (and row) generation.
	 */
	void solve(double max_time, const bool& is_maximisation, const bool& is_objective_integer, const double& best_incumbent_value) {
		this->column_generation->set_dual_bound(this->dual_bound);
		/*if (::isToPrint) {
			::appendToFile("fileCheck.log", "NODE " + to_string(this->id) + "\t FIRST DB " + to_string(this->dual_bound) + "\n");
		}*/

//		this->restricted_master_problem->export_model();
		this->column_generation->run(max_time, is_maximisation, is_objective_integer, best_incumbent_value);
		set_column_generation_time_spent(get_column_generation_time_spent() + this->column_generation->get_time_spent());
		set_solution(this->restricted_master_problem->get_solution());
		set_dual_bound(this->column_generation->get_dual_bound());
		set_rmp_value(this->restricted_master_problem->get_objective_value());         //CHANGE needed when updating the incumbent in the B&P class
		//cout << "NODE STORE " << setprecision(9) << this->restricted_master_problem->get_objective_value() << "\t" << setprecision(9) << this->column_generation->get_dual_bound() << "\n";
		set_nb_columns_added(get_nb_columns_added() + this->column_generation->get_nb_columns_found());

	}

	/**
	*	\brief	Operator = (assignment)
	*	\param	node : const Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>&
	*	\return : The result of assigning all the attributes of node to the current node: Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstrainttType>&
	*/
	Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstraintType>& operator=(const Node<RestrictedMasterProblem, PricingSubProblem, ColumnGeneration, ConstraintType>& node) {
		this->restricted_master_problem	  = node.restricted_master_problem;
		this->pricing_subproblem			  = node.pricing_subproblem;
		this->column_generation			  = node.column_generation;
		this->id		 					  = node.id;
		this->parent_id					  = node.parent_id;
		this->dual_bound					= node.dual_bound;
		this->rmp_value						= node.rmp_value;
		this->solution					  = node.solution;
		this->branching_constraints		  = node.branching_constraints;
		this->cut_constraints				  = node.cut_constraints;
		this->parent_bound			  = node.parent_bound;
		this->depth						  = node.depth;
		this->nb_columns_added			  = node.nb_columns_added;
		this->nb_cuts_added				  = node.nb_cuts_added;
		this->total_time_spent			  = node.total_time_spent;
		this->column_generation_time_spent  = node.column_generation_time_spent;
		this->cut_generation_time_spent     = node.cut_generation_time_spent;
		return *this;
	}

	/*void update_arc_branching_constraints(const vector<pair<int, pair< int, int>>>& new_column_indexes_and_involved_items) override {
		for (int i = 0; i < branching_constraints.size(); i++)
			for (int k = 0; k < new_column_indexes_and_involved_items.size(); k++) {
				if (branching_constraints[i]->get_name() == "ARCBRANCH")
					if (dynamic_cast<ArcBranchConstraint<GRBConstr>*>(branching_constraints[i])->get_items() == new_column_indexes_and_involved_items[k].second)
						if (find(this->branching_constraints[i]->get_variables_indexes().begin(), this->branching_constraints[i]->get_variables_indexes().end(), new_column_indexes_and_involved_items[k].first) == this->branching_constraints[i]->get_variables_indexes().end()) {
							this->branching_constraints[i]->get_variables_indexes().push_back(new_column_indexes_and_involved_items[k].first);
						}
			}
	}*/

	void update_clique_inequalities(const vector<int>& new_column_indexes) override {
		/*for (int i = 0; i < this->cut_constraints.size(); i++) {
			if (this->cut_constraints[i]->get_name() == "CLIQUE") {
				for (int k = 0; k < new_column_indexes.size(); k++) {
					bool conflicting_with_all = true;

					for (int l = 0; l < this->cut_constraints[i]->get_variables_indexes().size(); l++) {
						if (!this->restricted_master_problem->get_columns()[k].conflicts_with(this->restricted_master_problem->get_columns()[this->cut_constraints[i]->get_variables_indexes()[l]])) {
							conflicting_with_all = false;
							break;
						}
					}

					if (conflicting_with_all) {
						this->cut_constraints[i]->get_variables_indexes().push_back(new_column_indexes[k]);
					}
				}
			}
		}*/
	}

	void update_ineffective_cuts() override {
		typename vector<Constraint<ConstraintType>*>::iterator cut = this->cut_constraints.begin();
		while (cut != this->cut_constraints.end()) {
			if ((*cut)->get_ineffective_counter() >= MAX_NB_ITER_INEFFECTIVE_CUT) {
				cut = this->cut_constraints.erase(cut);
			}
			else
				cut++;
		}
	}


	void remove_me_from_the_list_observers() {
		this->restricted_master_problem->detach(this);
	}

	void add_me_to_the_list_observers() {
		this->restricted_master_problem->attach(this);
	}


};

#endif //NODE_H