import numpy as np
from BaseRank import BaseRank


class LeastViolatedRank(BaseRank):
    """
    A ranking method which does not utilize the concept of state.
    Equivalent to linear ordering problem, which can be solved by ILP.
    Perhaps very time consuming when number of ranked objects is large. Take care!!
    """
    def __init__(self, minimize=True, verbose=0, solvertp='SCIP', *args, **kwargs):
        """Two options for solvertp:
        'SCIP': ILP backended by http://scip.zib.de/
        'CBC': ILP backedded by https://projects.coin-or.org/Cbc
        """
        super(LeastViolatedRank, self).__init__(*args, **kwargs)
        if solvertp!='SCIP' and solvertp!='CBC':
            raise KeyError("Invalid solver option.")
        self.solvertp = solvertp
        self.minimize = minimize
        self.verbose = verbose

    def rate(self, C):
        """
        C: a potential matrix generated by rankit.util.ConsistancyMatrix
        """
        minimize = self.minimize
        verbose = self.verbose
        solvertp = self.solvertp
        from ortools.linear_solver import pywraplp
        if solvertp=='SCIP':
            solver = pywraplp.Solver('BILP',pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)
        else:
            solver = pywraplp.Solver('BILP',pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)

        X=[]
        for i in xrange(C.shape[0]):
            temp = []
            for j in xrange(C.shape[1]):
                temp.append(solver.IntVar(0, solver.infinity(), "x_"+str(i)+"_"+str(j)))
            X.append(temp)


        constraint1=[]
        for i in xrange(C.shape[0]):
            for j in xrange(i, C.shape[1]):
                if i==j: continue;
                constraint1.append(solver.Constraint(1, 1))
                constraint1[-1].SetCoefficient(X[i][j],1)
                constraint1[-1].SetCoefficient(X[j][i],1)

        objective = solver.Objective()
        for i in xrange(C.shape[0]):
            for j in xrange(C.shape[0]):
                if i!=j:
                    objective.SetCoefficient(X[i][j], float(C[i,j]))

        if minimize:
            objective.SetMinimization()
        else:
            objective.SetMaximization()
        status = solver.Solve()
        if status!=0:
            raise RuntimeError("IP solver failed with error code %s"%str(status))
        x=np.fromfunction(np.vectorize(lambda i,j: X[int(i)][int(j)].solution_value()), C.shape, dtype=np.int32)

        niter = 1;
        while True:
            constraint2 = []
            nz = np.vstack(np.nonzero(x))
            for idx in xrange(nz.shape[1]):
                i = nz[0][idx]
                j = nz[1][idx]
                knz = np.nonzero(np.multiply(x[j,:], x[:,i]))[0]
                for k in knz:
                    # add triangle constraint!
                    if i==j or j==k or k==i: continue;
                    constraint2.append(solver.Constraint(-solver.infinity(), 2))
                    constraint2[-1].SetCoefficient(X[i][j], 1)
                    constraint2[-1].SetCoefficient(X[j][k], 1)
                    constraint2[-1].SetCoefficient(X[k][i], 1)
            if len(constraint2)==0:
                if verbose!=0:
                    print "Iteration #%s: %s new triangle constraints added. Problem solved!"\
                    %(str(niter), str(len(constraint2)))
                break;
            else:
                if verbose!=0:
                    print "Iteration #%s: %s new triangle constraints added."\
                    %(str(niter), str(len(constraint2)))
            status = solver.Solve()
            if status!=0:
                raise RuntimeError("IP solver failed with error code %s"%str(status))
            x=np.fromfunction(np.vectorize(lambda i,j: X[int(i)][int(j)].solution_value()), C.shape, dtype=np.int32)
            niter+=1
        return np.sum(x, axis=0)
