import numpy as np
import gurobipy as gp
from gurobipy import GRB

STATI = { GRB.OPTIMAL : 'ottimo', GRB.INFEASIBLE : "inammissibile", GRB.UNBOUNDED: "illimitato"}
LIMITI = {">=": (0.0, GRB.INFINITY), "<=":(-GRB.INFINITY, 0.0), "libera":(-GRB.INFINITY, GRB.INFINITY)}

DA_VINCOLO_A_VARIABILE = {"min":{">=":">=", "<=":"<=", "==": "libera"},
                          "max":{"<=":">=", ">=":"<=", "==":"libera"}}

DA_VARIABILE_A_VINCOLO = {"min":{">=":"<=", "<=":">=", "libera":"=="},
                           "max":{">=":">=", "<=":"<=", "libera":"=="}}




class Problem_PL:

    def __init__(self, A, b, c, tipo_vincoli = "<=", senso = "min", tipo_variabili = ">=", nome = "x"):
        self.A = np.array(A, dtype = float)
        self.b = np.array(b, dtype = float)
        self.c = np.array(c, dtype = float)

        self.m, self.n = self.A.shape

        if type(tipo_vincoli) == str:
            tipo_vincoli = [tipo_vincoli]*self.m
        self.tipo_vincoli = tipo_vincoli

        self.senso = senso

        if type(tipo_variabili) == str:
            tipo_variabili = [tipo_variabili]*self.n
        self.tipo_variabili = tipo_variabili

        self.nome = nome

        self.x = None
        self.modello = None
        self.vincoli = []
        self.stato = "non ancora risolto"

    def costruisci_modello(self):

        self.modello = gp.Model("problema_PL")
        self.modello.Params.OutputFlag = 0 # sopprime il log di Gurobi
        self.modello.Params.Method = 0
        self.modello.Params.DualReductions = 0 #fa distinguere a gurobi il caso illimitato dal caso inammissibile

        # nomi = []
        # for j in range(self.n):
        #     nomi.append("x" + str(j + 1))
        #
        # self.x = self.modello.addMVar(self.n, lb = 0.0, name = nomi)

        nomi = []
        inferiori = []
        superiori = []
        for j in range(self.n):
            nomi.append(self.nome + str(j+1))
            lb, ub = LIMITI[self.tipo_variabili[j]]
            inferiori.append(lb)
            superiori.append(ub)

        self.x = self.modello.addMVar(self.n, lb = inferiori, ub = superiori, name = nomi)

        self.vincoli = []
        for i in range(self.m):
            riga = self.A[i]@self.x
            nome = "vincolo"+str(i+1)
            if self.tipo_vincoli[i] == "<=":
                vincolo = self.modello.addConstr(riga<= self.b[i], name = nome)
            elif self.tipo_vincoli[i] == ">=":
                vincolo = self.modello.addConstr(riga >= self.b[i], name=nome)
            elif self.tipo_vincoli[i] == "==":
                vincolo = self.modello.addConstr(riga == self.b[i], name=nome)
            else:
                print("TIPO DI VINCOLO NON VALIDO")
                quit()

            self.vincoli.append(vincolo)

        if self.senso == "max":
            self.modello.setObjective(self.c @ self.x, GRB.MAXIMIZE)
        else:
            self.modello.setObjective(self.c @ self.x, GRB.MINIMIZE)


    def mostra_modello(self):
        self.modello.update()
        self.modello.write("modello.lp")
        file = open("modello.lp")
        print(file.read())
        file.close()


    def risolvi(self):
        self.costruisci_modello()
        self.modello.optimize()

        codice = self.modello.Status
        if codice in STATI:
            self.stato = STATI[codice]
        else:
            self.stato = "altro"

    def Valore(self):
        return self.modello.ObjVal

    def soluzione (self):
        return self.x.X


    def duale(self):
        if self.senso =="min":
            senso_duale = "max"
        else:
            senso_duale = "min"

        tipo_variabili_duali=[]
        for t in self.tipo_vincoli:
            tipo_variabili_duali.append(DA_VINCOLO_A_VARIABILE[self.senso][t])

        tipo_vincoli_duali=[]
        for t in self.tipo_variabili:
            tipo_vincoli_duali.append(DA_VARIABILE_A_VINCOLO[self.senso][t])

        if self.nome =="x":
            nome_duale = "y"

        return Problem_PL(self.A.T, self.c, self.b, tipo_vincoli = tipo_vincoli_duali, senso = senso_duale, tipo_variabili=tipo_variabili_duali, nome = nome_duale)





