import csv
import operator
import os
import random
import sys
import time
import timeit
import datetime
# import networkx as nx

from datetime import datetime

from dateutil.parser import parse
from math import sqrt, pow, sin, cos, atan2, pi

try:
    import simplejson as json
except:
    import json

# from gurobipy import *
import xpress as xp
from multiprocessing import Pool, cpu_count

# import config
def simulate_craw_and_calendar(thread, nSimulations):
    logfile = f'draw_feasibility/log_{datetime.now().strftime("%Y%m%d_%H%M%S")}_{thread}'
    
    getTeamsOfPot = { p: [ p+str(i) for i in range(1,10) ]  for p in ["A","B","C","D"] }

    allTeams = getTeamsOfPot['A']+getTeamsOfPot['B']+getTeamsOfPot['C']+getTeamsOfPot['D']
    activeTeams= allTeams
    pots = getTeamsOfPot.keys()
    # print (pots)

    gamesAgainst = { (p1,p2) : 2  for p1 in  pots  for p2 in pots}

    # for p in pots:
    #     gamesAgainst[(p,p)]=2

    # for (p1,p2) in [('A','D'), ('D','A'), ('B','C'), ('C','B') ]:
    #     gamesAgainst[(p1,p2)]=2

    nMatchDays=8


    # for p1 in pots:
    #     for p2 in pots:
    #         print (p1,p2,gamesAgainst[(p1,p2)])
    #     print()

    # print (allTeams)

    cntr=0

    # mbus = Model("Draw")
    mbus = xp.problem("Draw")
        
    possAssigns= [ (t1,t2) for t1 in allTeams for t2 in allTeams if t1!=t2]
    # print (possAssigns)


    # plays = mbus.addVars(possAssigns, vtype=GRB.BINARY)
    plays = {pa:xp.var(vartype=xp.binary) for pa in possAssigns}
    mbus.addVariable(plays)

    for (t1,t2) in possAssigns:
        if t1<t2:
            # mbus.addConstr( plays[(t1,t2)]  ==plays[(t2,t1)] )
            mbus.addConstraint( plays[(t1,t2)]  ==plays[(t2,t1)] )

    for p in pots:
        for t in getTeamsOfPot[p]:
            for p2 in pots:
                # mbus.addConstr( quicksum(plays[(t,t2)] for t2  in getTeamsOfPot[p2] if t!=t2 ) == gamesAgainst[(p,p2)])
                mbus.addConstraint( xp.Sum(plays[(t,t2)] for t2  in getTeamsOfPot[p2] if t!=t2 ) == gamesAgainst[(p,p2)])


    # for (t1,t2) in [("A1","A2"), ("A2","A3"), ("A3","A1"),
    #                 ("B1","B2"), ("B2","B3"), ("B3","B1"), 
    #                 ("C1","C2"), ("C2","C3"), ("C3","C1"), 
    #                 # ("D1","D2"), ("D2","D3"), ("D3","D1"),
    #                 ] :
    #     mbus.addConstr( plays[(t1,t2)]  == 1)


    # for (p1,p2) in [ ("A","B"), ("B","C"),  ("C","D"),  ("D","A") ]:
    #     for (n1,n2) in [ (1,1),(1,2),(3,2),(3,3),(2,3) ,(2,1)]:
    #         mbus.addConstr( plays[( p1+str(n1) , p2+str(n2))]  == 1)


    # potpairs = [ (p1,p2) for p1 in pots for p2 in pots if p1!=p2]
    # clusters = [[1,2,3], [4,5,6], [7,8,9]]

    # for cl in clusters: 
    #     for (p1,p2) in potpairs:
    #         if len(cl)==2:
    #             mbus.addConstr( quicksum(plays[( p1+str(n1),p2+str(n2))] for n1 in cl for n2 in cl ) == 4)
    #             print ( [( p1+str(n1),p2+str(n2))  for n1 in cl for n2 in cl ])

    # for (p1,p2) in [ ("A","B"), ("B","C"),  ("C","D"),  ("D","A") ]:
    #     for (n1,n2) in [ (1,1),(1,2),(3,2),(3,3),(2,3) ,(2,1)]:
    #         mbus.addConstr( plays[( p1+str(n1) , p2+str(n2))]  == 1)


    badExample=False

    if badExample: 
        for p in pots: 
            for i in [2,3]:
                mbus.addConstr( plays[( p+str(i-1),p+str(i))]==1)
                # print ( p+str(i-1),p+str(i))
            mbus.addConstr( plays[( p+"3",p+"1")]==1)

        for p1 in pots: 
            for i in [1,2,3]:
                for p2 in pots:
                    if p1!=p2:
                        # mbus.addConstr( quicksum(plays[( p1+str(i),p2+str(j))] for j in [4,5,6] )  ==2)
                        mbus.addConstraint( xp.Sum(plays[( p1+str(i),p2+str(j))] for j in [4,5,6] )  ==2)

    # mbus.Params.OutputFlag = 0
    mbus.setControl ('outputlog', 0)
    mbus.setControl ('presolve', 0)

    debug=False


    now = datetime.now()

    nSims=nSimulations

    # if len(sys.argv)>1:
    #     nSims=int(sys.argv[1])


    mbus_solvetime = 0
    cal_vartime = 0
    cal_constrtime = 0
    cal_solvetime = 0
    cal_time3 = 0
    report_time = 0

    solutions =[]
    solution_strings =[]
    for i in range(nSims):
        
        
        if i%100==0:
            with open(f"{logfile}.log", "a") as f:
                f.write(f"{thread}: {i} after {datetime.now()-now}\n")
                f.write(f"\tmbus_solvetime: {mbus_solvetime}\n")
                f.write(f"\tcal_vartime: {cal_vartime}\n")
                f.write(f"\tcal_constrtime: {cal_constrtime}\n")
                f.write(f"\tcal_solvetime: {cal_solvetime}\n")
                f.write(f"\tcal_time3: {cal_time3}\n")
                f.write(f"\treport_time: {report_time}\n")
                
            with open(f"{logfile}.txt", "a") as f:    
                for s in solutions:
                    for s2 in s:
                        f.write(s2[0]+s2[1])
                    f.write("\n")
                solutions = []

        cntr+=1
        # mbus.setObjective(quicksum([  int(100*random.random()) * plays[pa] for pa in possAssigns ]))    
        mbus.setObjective(xp.Sum([  int(100*random.random()) * plays[pa] for pa in possAssigns ]))    
        # mbus.setObjective(quicksum([  max(0,int(pa[0][1]) - int(pa[1][1] )) * plays[pa] for pa in possAssigns ]))    

        # print ("DRAWING !!!!")
        ttt = time.time()

        # mbus.optimize()
        mbus.solve()
        checker_sol = mbus.getSolution(plays)
        
        mbus_solvetime += time.time()-ttt
        ttt = time.time()
        
        sol= [ pa for pa in possAssigns if checker_sol[pa]>0.9]
        
        sol2 = [('A1', 'A7'), ('A1', 'A9'), ('A1', 'B2'), ('A1', 'B3'), ('A1', 'C1'), ('A1', 'C2'), ('A1', 'D1'), ('A1', 'D2'), ('A2', 'A3'), ('A2', 'A4'), ('A2', 'B1'), ('A2', 'B4'), ('A2', 'C2'), ('A2', 'C3'), ('A2', 'D1'), ('A2', 'D4'), ('A3', 'A4'), ('A3', 'B3'), ('A3', 'B4'), ('A3', 'C3'), ('A3', 'C4'), ('A3', 'D2'), ('A3', 'D3'), ('A4', 'B1'), ('A4', 'B2'), ('A4', 'C1'), ('A4', 'C4'), ('A4', 'D3'), ('A4', 'D4'), ('A5', 'A8'), ('A5', 'A9'), ('A5', 'B5'), ('A5', 'B9'), ('A5', 'C8'), ('A5', 'C9'), ('A5', 'D6'), ('A5', 'D9'), ('A6', 'A7'), ('A6', 'A8'), ('A6', 'B6'), ('A6', 'B8'), ('A6', 'C5'), ('A6', 'C6'), ('A6', 'D5'), ('A6', 'D6'), ('A7', 'B5'), ('A7', 'B6'), ('A7', 'C5'), ('A7', 'C7'), ('A7', 'D7'), ('A7', 'D8'), ('A8', 'B7'), ('A8', 'B9'), ('A8', 'C8'), ('A8', 'C9'), ('A8', 'D5'), ('A8', 'D7'), ('A9', 'B7'), ('A9', 'B8'), ('A9', 'C6'), ('A9', 'C7'), ('A9', 'D8'), ('A9', 'D9'), ('B1', 'B2'), ('B1', 'B4'), ('B1', 'C2'), ('B1', 'C3'), ('B1', 'D3'), ('B1', 'D4'), ('B2', 'B3'), ('B2', 'C3'), ('B2', 'C4'), ('B2', 'D1'), ('B2', 'D3'), ('B3', 'B4'), ('B3', 'C1'), ('B3', 'C2'), ('B3', 'D2'), ('B3', 'D4'), ('B4', 'C1'), ('B4', 'C4'), ('B4', 'D1'), ('B4', 'D2'), ('B5', 'B8'), ('B5', 'B9'), ('B5', 'C6'), ('B5', 'C7'), ('B5', 'D7'), ('B5', 'D9'), ('B6', 'B7'), ('B6', 'B9'), ('B6', 'C5'), ('B6', 'C8'), ('B6', 'D5'), ('B6', 'D9'), ('B7', 'B8'), ('B7', 'C6'), ('B7', 'C8'), ('B7', 'D6'), ('B7', 'D7'), ('B8', 'C5'), ('B8', 'C9'), ('B8', 'D6'), ('B8', 'D8'), ('B9', 'C7'), ('B9', 'C9'), ('B9', 'D5'), ('B9', 'D8'), ('C1', 'C2'), ('C1', 'C4'), ('C1', 'D3'), ('C1', 'D4'), ('C2', 'C3'), ('C2', 'D1'), ('C2', 'D2'), ('C3', 'C4'), ('C3', 'D1'), ('C3', 'D3'), ('C4', 'D2'), ('C4', 'D4'), ('C5', 'C6'), ('C5', 'C8'), ('C5', 'D5'), ('C5', 'D6'), ('C6', 'C9'), ('C6', 'D7'), ('C6', 'D8'), ('C7', 'C8'), ('C7', 'C9'), ('C7', 'D7'), ('C7', 'D8'), ('C8', 'D6'), ('C8', 'D9'), ('C9', 'D5'), ('C9', 'D9'), ('D1', 'D2'), ('D1', 'D3'), ('D2', 'D4'), ('D3', 'D4'), ('D5', 'D6'), ('D5', 'D8'), ('D6', 'D9'), ('D7', 'D8'), ('D7', 'D9')]
        sol = []
        for (g1,g2) in sol:
            sol.append((g1,g2))
            sol.append((g2,g1))
        
        solutions.append(sol.copy())
        
        report_time += time.time()-ttt
        ttt = time.time()

        if debug:
            print ("Solution")
            # exit(0)
            print ("INPOT")
            for p in pots: 
                print ("")
                print ("POT",p)
                for (t1,t2) in [(t1,t2) for (t1,t2) in sol if  t1<t2 and t1[0]==p and t2[0]==p]:
                    print ( t1 , "-" , t2)

            print ("OUTPOT")
            for p in pots: 
                print ("")
                print ("POT",p)
                for (t1,t2) in [(t1,t2) for (t1,t2) in sol if t1[0]==p and t2[0]!=p]:
                    print ( t1 , "-" , t2)

        solstring =""
        for pa in possAssigns:
            solstring+=  str(int(checker_sol[pa] +0.1) )   
        if nSims < 1000:
            solution_strings.append(solstring)
        report_time += time.time()-ttt
        ttt = time.time()

        mds =range(1,nMatchDays+1)

        pas2= [ (t1,t2,md) for md in mds for (t1,t2) in sol]
        # print ("pas2", pas2)

        # cal = Model("Calendar")
        cal = xp.problem("Calendar")

        # x  = cal.addVars(pas2, vtype=GRB.BINARY)
        x = {p:xp.var(vartype=xp.binary) for p in pas2}
        cal.addVariable(x)

        computeCalendarsHA = True
        computeCalendarsHA = False
        cal_vartime += time.time()-ttt
        ttt = time.time()
        if computeCalendarsHA:

            pos_patterns = [
                [1,0,1,1,0,0,1,0 ],
                [1,0,1,0,1,0,1,0 ],
                [1,0,1,0,1,0,0,1 ],
                [1,0,1,0,0,1,1,0 ],
                [1,0,1,0,0,1,0,1 ],
                [1,0,0,1,1,0,1,0 ],
                [1,0,0,1,1,0,0,1 ],
                [1,0,0,1,0,1,1,0 ],
                [1,0,0,1,0,1,0,1 ],
            ]

            # add reverse patterns (all those starting with 0,1 )
            pos_patterns+=[ [ 1-p[i] for i in range(8)] for p in pos_patterns ]
            # print ("pos_patterns",pos_patterns)

            positions = range (len(pos_patterns))

            # pos = cal.addVars( [(t,p) for t in allTeams for p in positions], vtype=GRB.CONTINUOUS)
            pos = {p:xp.var(vartype=xp.continuous) for p in positions}
            cal.addVariable(pos)

            for t in allTeams:
                # cal.addConstr( quicksum( [ pos[t,p] for p in positions ]) ==1)
                cal.addConstraint( xp.Sum( [ pos[t,p] for p in positions ]) ==1)
                for md in mds:
                    # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 ) == quicksum( [ pos[t,p] for p in positions if pos_patterns[p][md-1]==1 ]) )
                    cal.addConstraint( xp.Sum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 ) == xp.Sum( [ pos[t,p] for p in positions if pos_patterns[p][md-1]==1 ]) )

            for (t1,t2) in sol :
                if t1<t2:
                    # cal.addConstr( quicksum(x[(t1,t2,md)] + x[(t2,t1,md)] for md in mds) <=1 )
                    cal.addConstraint( xp.Sum(x[(t1,t2,md)] + x[(t2,t1,md)] for md in mds) <=1 )

            for t in allTeams:
                # every team 4 home games
                # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 for md in mds ) == 4)
                cal.addConstraint( xp.Sum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 for md in mds ) == 4)
                # every team one game a day
                for md in mds:
                    # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t in [t1,t2] ) == 1)
                    cal.addConstraint( xp.Sum(x[(t1,t2,md)] for (t1,t2) in sol if t in [t1,t2] ) == 1)

            for p in pots:
                for t in getTeamsOfPot[p]:
                    for p2 in pots:
                        # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 and t2 in getTeamsOfPot[p2] for md in mds ) >=  int(0.5*gamesAgainst[(p,p2)]) ) 
                        cal.addConstraint( xp.Sum(x[(t1,t2,md)] for (t1,t2) in sol if t ==t1 and t2 in getTeamsOfPot[p2] for md in mds ) >=  int(0.5*gamesAgainst[(p,p2)]) ) 
        else:
            for (t1,t2) in sol :
                if t1<t2:
                    # cal.addConstr( quicksum(x[(t1,t2,md)] for md in mds) ==1 )
                    cal.addConstraint( xp.Sum(x[(t1,t2,md)] for md in mds) ==1 )
                    for md in mds:
                        # cal.addConstr( x[(t1,t2,md)] == x[(t2,t1,md)]  )
                        cal.addConstraint( x[(t1,t2,md)] == x[(t2,t1,md)]  )

            for t in activeTeams:
                for md in mds:
                    # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t==t1 ) == 1)
                    # cal.addConstr( quicksum(x[(t1,t2,md)] for (t1,t2) in sol if t==t1 ) == 1)
                    cal.addConstraint( xp.Sum(x[(t1,t2,md)] for (t1,t2) in sol if t==t1 ) == 1)

        # cal.setObjective(quicksum( -x[pa] for pa in x.keys() ))  
        cal.setObjective(xp.Sum( -x[pa] for pa in x.keys() ))  

        # cal.Params.OutputFlag = 0
        cal.setControl ('outputlog', 1)
        cal.setControl ('presolve', 0)
        # print ("SCHEDULING !!!!")
        
        cal_constrtime += time.time()-ttt
        ttt = time.time()
        
        # cal.optimize()
        cal.solve()

        cal_solvetime += time.time()-ttt
        ttt = time.time()

        # cal_sol= [pa for pa in x.keys() if x[pa].x>0.9 ]
        # print( cal_sol)
        # for pa in cal_sol :
        #     print (pa)   
        # # print (cal.status)
        # print(cal.objVal)

        # if cal.status!=2 or cal.objVal>-288:
        if cal.getProbStatus()!=6 or cal.getObjVal()>-287.5:
            # sol= [ (t1,t2) for (t1,t2) in sol if t1<t2]
            # sol= [ (t1,t2) for (t1,t2) in sol if t1[0]=="A" and t2[0]=="D"  ]
            print ("No calendar for ", sol)
            print ("Tested " , str(i+1) , " calendars" )
            print("Status",cal.getProbStatus(),f"{cal.getProbStatus()}")
            print("Solution",cal.getObjVal())
            exit()

        # pas2_sol= [ pas for pas in pas2 if cal.getSolution(x[pas])>0.9]
        # print (pas2_sol)


        # showSolution= False
        # showSolution= True
        # if showSolution and debug:
        #     for md in mds:
        #         print (md,":")
        #         for (t1,t2,md2) in pas2_sol:
        #             # if md2==md and t1<t2 and "C" in [t1[0],t2[0]] :
        #             if md2==md  :
        #                 print ("  " ,t1, " -" ,t2)

        schedule = cal.getSolution(x)
        for t in activeTeams:
            for md in mds:
                if not computeCalendarsHA and sum(schedule[(t1,t2,md)] for (t1,t2) in sol if t==t1 ) < 0.9:
                    print ("ALERT ",t,md)
                    print ("No calendar for ", sol)
                    print ("Tested " , str(i+1) , " calendars" )
                    print("Status",cal.getProbStatus(),f"{cal.getProbStatus()}")
                    print("Solution",cal.getObjVal())
                    exit()

        # print (solutions)
        # oldGm = [ gg for gg in solutions[1] if gg not in solutions[0]] 
        # if len(solutions)>1 and showSolution:
        #     newGm = [ gg for gg in solutions[-1] if gg not in solutions[-2]] 

        cal_time3 += time.time()-ttt
        ttt = time.time()

        # print (oldGm)
            # print (newGm)

    
        
n = sys.maxsize
# n = 1000
n = 1

pool = Pool()
result = {}
answer = {}
# threads = cpu_count()//2
threads = 1

for cpu in range(threads):
    result[cpu] = pool.apply_async(simulate_craw_and_calendar, args=(f'thread_{cpu}', n,))

for cpu in range(threads):
    answer[cpu] = result[cpu].get()

