import pygeneraldichotomy.libpygdichotomy as libpygdichotomy

from enum import Enum

class Weights(object):
    """
        Defines an extreme weight of the weight set decomposition
        Attributes:
            weights: the weight vector
            z: the scalarized cost on this weight
            adjacency: the list of adjacent solutions and weight set boundaries
    """
    
    def __init__(self):
        self.weights = None
        self.adjacency = None
        self.z = None

class AbstractSolver(object):
    """
        Abstract class to define a single-objective solver
    """


    def __init__(self):
        ...
    
    def nobj(self):
        """
            Return the number of objectives of the problem
        """
        raise NotImplementedError


    def solve(self, weights, adj_solutions):
        """
            Solver the weighted problem and return the cost vector
            
            Args:
                - weights the weights to apply
                - adj_solutions list of solution indexes that are adjacent in the current approximation

            Return:
                - tuple with the objective cost vector and the weighted optimal value
        """
        raise NotImplementedError
    
    def is_new(self, is_new_arg):
        """
            Callback function notifying if the last solution found was new or not. Useful to avoid recording solution duplicates             
        """
        raise NotImplementedError
    



class GeneralDichotomy(object):

    class Polytope_backend(Enum):
        """
            Defines a backend for the polytope vertex enumeration procedure 
        """
        CDD = 1
        PPL = 2

    class Direction(Enum):
        """
            Defines an optimization direction 
        """
        MIN = 0
        MAX = 1

    class SearchScheme(Enum):
        """
            Search scheme for the weight set exploration
        """
        DIVERSIFICATION=0
        INTENSIFICATION=1


    def __init__(self, user_solver: AbstractSolver):
        """
            Constructor
            Args:
                - user_solver the user-defined solver object
        """

        self.user_solver = user_solver

    def run(self, **kwargs):
        """
            Run the general dichotomy algorithm
            Args:
                verbose: verbosity level
                backend: the polytope enumeration backend to use
                directions: a tuple containing a direction (min or max) for each objective (minimization by default)
                wnorm: the normalizing value of the weights
                max_iter: limit on the number of iterations (0 meaning no limit)
                max_depth: maximum depth of search
                cost_scale: scaling factor for objective costs for ppl based polytope computation
                initial_weight: initial weight vector for the first solution, optional
                search_scheme: search scheme to apply: SearchScheme.DIVERSIFICATION or SearchScheme.INTENSIFICATION (default=SearchScheme.DIVERSIFICATION)
                export_weights: export the extreme weights
                export_weight_graph: export the weight graph
        """
        
        defaultKwargs = {'verbose': 0, 'backend': GeneralDichotomy.Polytope_backend.CDD, 'directions': tuple(), 'wnorm' : 10., 'max_iter': 0, 'max_depth': 0, 'cost_scale':1., 'initial_weight': [], 'search_scheme': GeneralDichotomy.SearchScheme.DIVERSIFICATION, 'export_weights':False, 'export_weight_graph': False}
        kwargs = { **defaultKwargs, **kwargs}

        return libpygdichotomy.run_algorithm(kwargs['backend'].value, tuple([direction.value for direction in kwargs['directions']]), kwargs['wnorm'], kwargs['max_iter'], kwargs['max_depth'], kwargs['cost_scale'], kwargs['initial_weight'], kwargs['search_scheme'].value, int(kwargs['export_weights']), int(kwargs['export_weight_graph']), kwargs['verbose'], self.user_solver.nobj(), self.user_solver.solve, self.user_solver.is_new)