/*****************************************************************//**
 * \file   CutPool.h
 * \brief  Represents the pool of stored cuts in the branch and price and cut algorithm.
 *
 * \author talmefta
 * \date   October 2022
 *********************************************************************/
#ifndef CUTPOOL_H
#define CUTPOOL_H

#include "Header.h"
#include "CutGeneratorAbstract.h"
#include "RestrictedMasterProblem.h"
#include "PricingProblem.h"
#include "ColumnGeneration.h"

 /**
  * \class CutStrategy
  * \brief Interface of Strategy design pattern dealing with the cut strategy.
  */

class CutPool : public CutGeneratorAbstract<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr> {

protected:
    vector<Constraint<GRBConstr>*> ineffective_cuts;

public:
    /**
    * Constructor
    *
    */
    CutPool() : CutGeneratorAbstract(), ineffective_cuts() {}

    /**
     * Virtual destructor.
     *
     */
    virtual ~CutPool()
    {
        for (auto& element : information)
            delete element;
    }


    /**
    * \brief Evaluates if the handler (the algorithm) can handle (solve) the cutting procedure based on the code of request.
    *
    * \param request : string
    * \return True if the concrete algorithm can find the cuts, false otherwise.
    */
    bool can_cut(const string& request) const override {
        return request == "CUTPOOL" || request == "WHATEVER";
    }

    /**
     * Add new_ineffective cut to the pool of ineffective_cuts.
     *
     * \param new_ineffective_cut: const Constraint<ConstraintType>&
     */
    void add_ineffective_cut(Constraint<GRBConstr>* new_ineffective_cut) {
        this->ineffective_cuts.push_back(new_ineffective_cut);
    }

    /**
    * Add new_ineffective cut to the pool of ineffective_cuts.
    *
    * \param new_ineffective_cut: const Constraint<ConstraintType>&
    */
    void add_ineffective_cuts(vector<Constraint<GRBConstr>*>& new_ineffective_cuts) {
        vector<Constraint<GRBConstr>*>::iterator cut = new_ineffective_cuts.begin();
        while (cut != new_ineffective_cuts.end()) {
            add_ineffective_cut(*cut);
            cut++;
        }
    }

    /**
     * Tries to find cuts to add to the node. Stores the new cuts if any in the node's cut_constraints.
     *
     * \param node: Node<class RestrictedMasterProblem, class PricingSubProblem, class ColumnGeneration>&
     */
    bool cut(Node<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr>& node) {
        if (can_cut(node.get_restricted_master_problem()->get_cut_request())) {
            clock_t start = clock();
            add_ineffective_cuts(node.get_restricted_master_problem()->get_ineffective_cut_constraints());
            node.get_restricted_master_problem()->remove_ineffective_cuts();

            cut_iteration++;

            double sum;
            bool at_least_one_violated_cut_added = false;
            vector<Constraint<GRBConstr>*>::iterator cut = ineffective_cuts.begin();
            int nb_violated_cuts_added = 0;

            double minimal_violation = 1e5;
            double maximal_violation = 0;

            while (cut != ineffective_cuts.end()) {
                sum = 0;
                /*for (auto& k : (*cut)->get_variables_indexes())
                    sum += node.get_restricted_master_problem()->get_solution()[k];*/



                if (sum > (*cut)->get_rhs() + VIOLATIONEPSILON) {
                    at_least_one_violated_cut_added = true;
                    nb_violated_cuts_added++;
                    (*cut)->set_ineffective_counter(0);
                    (*cut)->set_become_effective(node.get_restricted_master_problem()->get_solving_rmp_iteration());
                    node.add_cut_constraint(*cut);
                    node.get_restricted_master_problem()->add_cut_constraint(node.get_cut_constraints().back(), node.get_cut_constraints().size() - 1, true);
                    if (sum - (*cut)->get_rhs() < minimal_violation)
                        minimal_violation = sum - (*cut)->get_rhs();

                    if (sum - (*cut)->get_rhs() > maximal_violation)
                        maximal_violation = sum - (*cut)->get_rhs();

                    cut = ineffective_cuts.erase(cut);
                }
                else {
                    (*cut)->set_ineffective_counter((*cut)->get_ineffective_counter() + 1);

                    //Check if the cut becomes really ineffective and it is not worth it to keep it
                    //If so, we delete it to keep the size of the cut pool as small as possible
                    //I have to consider also the quality of the cut as a measure of deleting it or keeping it
                    //Quality here may be the degree of non violation (striclty positive slack variable > threshold)
                  /*  if ((*cut)->get_ineffective_counter() >= 3 * MAX_NB_ITER_INEFFECTIVE_CUT)
                    {
                        delete* cut;
                        cut = ineffective_cuts.erase(cut);
                    }
                    else*/
                    cut++;
                }
            }
            node.set_nb_cuts_added(static_cast<int>(node.get_nb_cuts_added() + nb_violated_cuts_added));
            clock_t end = clock();
            double duration = (float)(end - start) / CLOCKS_PER_SEC;
            node.set_cut_generation_time_spent(node.get_cut_generation_time_spent() + duration);

            //stats collection
            cut_counter += nb_violated_cuts_added;
            separation_time += duration;
            information.push_back(new CutFamilyInformation("CutPool", node.get_id(), cut_iteration, nb_violated_cuts_added, node.get_dual_bound(), duration, minimal_violation, maximal_violation));


            if (at_least_one_violated_cut_added) {
                return at_least_one_violated_cut_added;
            }
            else
                return CutGeneratorAbstract<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr>::cut(node);
        }
        else
            return CutGeneratorAbstract<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr>::cut(node);

    }

};

#endif //CUTPOOL_H

