# %%
PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'
import os, sys
sys.path.insert(0, PROJECT_PATH)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "leagues.settings")


# XPRESS ENVIRONMENT
os.environ['XPRESSDIR'] = "/opt/xpressmp_8.4"
os.environ['XPRESS'] = "/opt/xpressmp_8.4/bin"
os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"+os.environ['LD_LIBRARY_PATH']
os.environ['DYLD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"
os.environ['SHLIB_PATH'] = os.environ['XPRESSDIR']+"/lib:"
os.environ['LIBPATH'] = os.environ['XPRESSDIR']+"/lib:"
# os.environ['PYTHONPATH'] =os.environ['XPRESSDIR']+"/lib:"+os.environ['PYTHONPATH']
os.environ['PYTHONPATH'] =os.environ['XPRESSDIR']+"/lib:"
os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprs.jar:"
os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprb.jar:"+os.environ['CLASSPATH']
os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprm.jar:"+os.environ['CLASSPATH']
os.environ['PATH'] =os.environ['XPRESSDIR']+"/bin:"+os.environ['PATH']


from leagues import settings
settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'

import django
django.setup()

from scheduler.models import *
import operator
from scheduler.solver.functions import *
from common.functions import distanceInKmByGPS
import pandas as pd
import numpy as np
from django.db.models import Count, F, Value
from pulp import *


def makeIntVar(v):
    if type(v) != int:
        v.cat = LpInteger
        # print ("int var ", v)


def setStart(v, vl):
    if type(v) != int:
        vl = float(vl)
        v.start = vl


def setLB(v, vl):
    if type(v) != int:
        vl = float(vl)
        v.lowBound = vl
        # print ("set lb ", v , " = ", vl)


def setUB(v, vl):
    if type(v) != int:
        vl = float(vl)
        v.upBound = vl
        # print ("set ub ", v , " = ", vl)


def getVal(v):
    if type(v) == int:
        return v
    else:
        return v.value()


user_is_staff = True
runMode = 'New'
localsearch_time = 0
solver = '?'

s2 = 3
thisScenario = Scenario.objects.get(id=s2)
thisSeason = thisScenario.season
thisLeague = thisSeason.league


mathModelName = thisLeague.name
if thisSeason.optimizationParameters != "":
    mathModelName = thisSeason.optimizationParameters

# %%


otherScenGames = []
if thisSeason.improvementObjective != "--":
    for otherScenario in Scenario.objects.filter(season=thisSeason):
        impObjWeight = 5
        if thisSeason.improvementObjective == "stick to old solutions":
            impObjWeight = -5
        if otherScenario.solutionlist() != [['']]:
            for g in otherScenario.solutionlist():
                otherScenGames.append(
                    (int(g[1]), int(g[2]), int(g[3]), impObjWeight))

getGlobalCountry = {gc['uefa']: gc['id']
                    for gc in GlobalCountry.objects.values()}

teamObjects = Team.objects.filter(
    season=thisSeason, active=True).order_by('position').values()
teams = []
realteams = []
faketeams = []
importantteams = []
shortteams = []
t_pos = {}
t_pot = {}
t_color = {}
t_country = {}
t_globalCountry = {}
t_stadium = {}
t_shortname = {}
t_usePhases = {}
t_lon = {}
t_lat = {}
t_attractivity = {}
getTeamById = {}
getTeamIdByName = {}
getTeamIdByShortName = {}
getTeamByName = {}
getStadiumById = {}
teamByShort = {}
top4 = []
for t in teamObjects:
    # print (t['name'],  t['id'])
    teams.append(t['id'])
    shortteams.append(t['shortname'])
    teamByShort[t['shortname']] = t['name']
    getTeamIdByShortName[t['shortname']] = t['id']
    if t['name'] != '-':
        realteams.append(t['id'])
    else:
        faketeams.append(t['id'])
    if t['very_important']:
        importantteams.append(t['id'])
    t_country[t['id']] = t['country']
    t_globalCountry[t['id']] = t['globalCountry_id']
    if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys():
        t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]]
    t_pos[t['name']] = t['position']
    t_color[t['name']] = "lightyellow"
    t_pot[t['id']] = t['pot']
    t_stadium[t['id']] = t['stadium']
    t_attractivity[t['id']] = t['attractivity']
    t_lon[t['id']] = t['longitude']
    t_lat[t['id']] = t['latitude']
    t_shortname[t['id']] = t['shortname']
    t_usePhases[t['id']] = thisScenario.usePhases
    getTeamById[t['id']] = t['name']
    getStadiumById[t['id']] = t['stadium']
    getTeamIdByName[t['name']] = t['id']
    getTeamByName[t['name']] = t
    if t['attractivity'] >= thisSeason.topTeamMinCoefficient:
        top4.append(t['id'])

inactive_teams = []
for t in Team.objects.filter(season=thisSeason, active=False).order_by('position').values():
    inactive_teams.append(t['id'])
    t_attractivity[t['id']] = t['attractivity']
    t_lon[t['id']] = t['longitude']
    t_lat[t['id']] = t['latitude']
    t_country[t['id']] = t['country']
    getTeamById[t['id']] = t['name']


lowerBoundFound = False
distance = {}
attractivity = {}
distanceById = {}
distanceInDaysById = {}
stadium_names = set([t['stadium'] for t in teamObjects if t['stadium'] != ''])
stadium_id = {}
stadium_name = {}
sid = 0
for stadium in stadium_names:
    stadium_name[sid] = stadium
    stadium_id[stadium] = sid
    sid += 1
stadiums = list(stadium_name.keys())
teamsOfStadium = {st: [] for st in stadiums}

travelDict = thisSeason.travelDict()
for t1 in teamObjects:
    if t1['stadium'] != '':
        teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id'])
    for t2 in teamObjects:
        # distance[t1['name'],t2['name']] = distanceInKm(t1,t2)
        # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] )
        distance[t1['name'], t2['name']
                 ] = travelDict[t1['id']][t2['id']]['distance']
        distanceById[t1['id'], t2['id']] = distance[t1['name'], t2['name']]
        distanceInDaysById[t1['id'], t2['id']] = int(
            distance[t1['name'], t2['name']]/350 + 0.99)
        attractivity[t1['id'], t2['id']] = int(
            100*t1['attractivity']*t2['attractivity'])


dayObjects = Day.objects.filter(season=thisSeason).values()
days = [d['id'] for d in dayObjects if d['round'] > 0]

higherSeasons = thisSeason.higherSeasons()
higherGames = thisSeason.higherGames()

this_season_team_names = list(getTeamByName.keys())

higherTeamObjects = Team.objects.filter(
    season__in=higherSeasons, active=True).values()
higherDayObjects = Day.objects.filter(
    season__in=higherSeasons, maxGames__gte=1).values()
higherTeams = [t['id'] for t in higherTeamObjects]
higherTeamsOf = {t: [] for t in teams}
for t in higherTeamObjects:
    getTeamById[t['id']] = t['name']
    t_country[t['id']] = t['country']
    if t['name'] in this_season_team_names:
        higherTeamsOf[getTeamIdByName[t['name']]].append(t['id'])

print("Teams : ", teams)
print("VIP Teams : ", importantteams)
print("TOP Teams : ", top4)

allConferences = Conference.objects.filter(scenario=s2)
sharedStadiums = False
for ff in thisSeason.federationmember.all():
    sharedStadiums = ff.federation.sharedStadiums

# print ("\nGroups : ")
t_conference = {t: 0 for t in teams}
conf_teams = {}
for c in Conference.objects.filter(scenario=s2, regional=False).order_by('name'):
    # print ("A" , c)
    cteams = c.teams.filter(active=True)
    conf_teams[c.name] = []
    for t in cteams:
        conf_teams[c.name].append(t.id)
        # print ("group for ", t)
        # if t_conference[t.id]!=0:
        # print (getTeamById[t.id] , " in several groups")
        if t_conference[t.id] == 0 or len(cteams) < len(t_conference[t.id].teams.filter(active=True)):
            t_conference[t.id] = c
            # print  (" is " , c )
# for t in set([t for t in teams if t_conference[t]==0 ]):
    # print (" no group " ,  getTeamById[ t])
# return ''

prioVal = {'A': 25, 'B': 5, 'C': 1, 'Hard': 1000}

sw_prio = {}
sw_float1 = {}
sw_float2 = {}
sw_int1 = {}
sw_int2 = {}
sw_text = {}
for sw in SpecialWish.objects.filter(scenario=s2):
    sw_prio[sw.name] = prioVal[sw.prio] if sw.active else 0
    sw_float1[sw.name] = sw.float1
    sw_float2[sw.name] = sw.float2
    sw_int1[sw.name] = sw.int1
    sw_int2[sw.name] = sw.int2
    sw_text[sw.name] = sw.text
special_wishes = list(sw_prio.keys())
special_wishes_active = [sw for sw in special_wishes if sw_prio[sw] > 0]

print(special_wishes_active)

noBreakLimitTeams = []
if "allowManyBreaksInRow" in special_wishes_active:
    noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",")
    print(noBreakLimitTeams)
    noBreakLimitTeams = [
        t for t in teams if getTeamById[t] in noBreakLimitTeams]
    print(noBreakLimitTeams)

pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values()
pairings_tmp2 = Pairing.objects.filter(
    scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values()
pairings_tmp3 = Pairing.objects.filter(
    scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values()
pairings = []
for p in [p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams] + [p for p in pairings_tmp2] + [p for p in pairings_tmp3]:
    if p not in pairings:
        pairings.append(p)
breaks = Break.objects.filter(season=thisSeason).values()
blockings_tmp = Blocking.objects.filter(scenario=s2, active=True).values()
blockings = [bl for bl in blockings_tmp if bl['team_id'] in teams]

# times = ['Early', 'Late']
timeObjects = TimeSlot.objects.filter(season=thisSeason).values()
times = [str(t['id']) for t in timeObjects]
getTimeById = {str(t['id']): t['name'] for t in timeObjects}
getIdByTime = {t['name']: str(t['id']) for t in timeObjects}

blocked_arena = {
    (t, d, tm): False for t in teams for d in days for tm in ["----"] + times}
hidden_arena = {(t, d, tm): False for t in teams for d in days for tm in [
    "----"] + times}
nBlockingHome = 0
nBlockingAway = 0

for bl in blockings:
    if bl['type'] in ["Hide"]:
        hidden_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True

    if bl['type'] in ["Home", "Hide"]:
        nBlockingHome += 1
        blocked_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True
    else:
        nBlockingAway += 1

nTeams = len(teams)
nPhases = thisSeason.numPhases
groupView = thisSeason.groupBased

gameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams}
undirectedGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams}

for t1 in teams:
    for t2 in teams:
        if t1 != t2:
            gameCntr[(t1, t2)] += nPhases/2

for (t1, t2) in gameCntr.keys():
    if gameCntr[(t1, t2)] % 1 != 0:
        undirectedGameCntr[(t1, t2)] = 1
    gameCntr[(t1, t2)] = int(gameCntr[(t1, t2)])

games = [gm for gm in gameCntr.keys() if gameCntr[gm] +
         undirectedGameCntr[gm] > 0]

objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(
    scenario=s2).values()

gew = {}
gew['Trips'] = 5
gew['Derbies'] = 5
gew['Pairings'] = 5
for ow in objectiveFunctionWeights:
    gew[ow['name']] = ow['use'] * ow['prio']

objectivePrio = 'Breaks'
if gew['Trips'] > gew['Breaks']:
    objectivePrio = 'Trips'

print("objectivePrio:", objectivePrio)

specialGameControl = mathModelName in ["Florida State League"]

if len(games) == 0:
    games = [(t1, t2) for t1 in teams for t2 in teams if t1 != t2]
    specialGameControl = True

realgames = [(t1, t2) for (t1, t2) in games if getTeamById[t1]
             != "-" and getTeamById[t2] != "-"]

# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert
evalRun = (runMode == "Improve" and localsearch_time == 0
           and len(thisScenario.solutionlist()) == sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames]))

opponents = {t: set([]) for t in realteams}

for (t1, t2) in games:
    if t1 in realteams and t2 in realteams:
        opponents[t1].add(t2)
        opponents[t2].add(t1)

# %%

gmClusters = range(1, nTeams+2)

if nPhases > 0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl:
    gmClusters = range(1, 2)

print("gmClusters", gmClusters)


gameClusterTeams = {c: [t for t in teams] for c in gmClusters}
gameClusters = [c for c in gameClusterTeams.keys() if len(
    gameClusterTeams[c]) > 0]
biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters])

# %%
nPhases = min([gameCntr[(t1, t2)] + gameCntr[(t2, t1)] +
              undirectedGameCntr[(t1, t2)] for (t1, t2) in realgames])
tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize == "None" else int(
    thisSeason.tripStartHeuristicGroupsize)
defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2
nPhases = max(1, int(nPhases/defaultGameRepetions))
phases = range(nPhases)

useBasicGames = nPhases > 0

if not useBasicGames:
    print('no basic games but biggest group size ',
          biggestGroupSize, ' in nPhases ', nPhases)

nRounds = thisSeason.nRounds
rounds = range(1, nRounds+1)

# nRoundsPerPhase= 1
# if nPhases>0:
nRoundsPerPhase = int(nRounds/nPhases)

print('nRounds ', nRounds)
print('nPhases ', nPhases)
print('nRoundsPerPhase ', nRoundsPerPhase)
print('defaultGameRepetions ', defaultGameRepetions)
print('tripStartHeuristicGroupsize ', tripStartHeuristicGroupsize)

getPhaseOfRound = {r: min(nPhases-1, int((r-1)/nRoundsPerPhase))
                   for r in rounds}
getDaysOfPhase = {p: [] for p in phases}
getDays = {r: [] for r in rounds}
roundGamesMax = {r: 0 for r in rounds}
roundGamesMin = {r: 0 for r in rounds}
getDayById = {d['id']: d for d in dayObjects}
getDayByDateTime = {}
getNiceDayRaw = {d['id']: d['day'] for d in dayObjects}
getNiceDay = {d['id']: d['day'] for d in dayObjects}
getWeekDay = {d['id']: '' for d in dayObjects}
getDateTimeDay = {d['id']: '' for d in dayObjects}
getRoundByDay = {d['id']: d['round'] for d in dayObjects if d['round'] > 0}
getDayMinGames = {d['id']: d['minGames'] for d in dayObjects if d['round'] > 0}
getDayMaxGames = {d['id']: d['maxGames'] for d in dayObjects if d['round'] > 0}
getRoundsByDay = {d['id']: [] for d in dayObjects}
nDerbies = {d['id']: [d['nDerbies']] for d in dayObjects}
roundDays = []
roundDaysMin = {}
roundDaysMax = {}
wds = {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun'}
for d in dayObjects:
    if d['round'] > 0:
        getRoundsByDay[d['id']].append(d['round'])
        getDays[d['round']].append(d['id'])
        roundDays.append((d['round'], d['id']))
        roundDaysMin[(d['round'], d['id'])] = d['minGames']
        roundDaysMax[(d['round'], d['id'])] = d['maxGames']
        roundGamesMax[d['round']] = min(
            nTeams/2, roundGamesMax[d['round']]+d['maxGames'])
        roundGamesMin[d['round']] += d['minGames']
        ph = getPhaseOfRound[d['round']]
        getDaysOfPhase[ph].append(d['id'])
    if d['round2'] > 0:
        getRoundsByDay[d['id']].append(d['round2'])
        getDays[d['round2']].append(d['id'])
        roundDays.append((d['round2'], d['id']))
        roundDaysMin[(d['round2'], d['id'])] = d['minGames2']
        roundDaysMax[(d['round2'], d['id'])] = d['maxGames2']
        roundGamesMax[d['round2']] = min(
            nTeams/2, roundGamesMax[d['round2']]+d['maxGames2'])
        roundGamesMin[d['round2']] += d['minGames2']

    dt = parse(d['day'])
    getDateTimeDay[d['id']] = dt
    getDayByDateTime[dt] = d['id']
    getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y'))
    getWeekDay[d['id']] = str(wds[dt.weekday()])

countries = list(set([t_country[t] for t in teams]))

getWeekDaysPerRound = {r: [getWeekDay[d] for d in getDays[r]] for r in rounds}

wd = {"Mondays": 0, "Tuesdays": 1, "Wednesdays": 2,
      "Thursdays": 3, "Fridays": 4, "Saturdays": 5, "Sundays": 6}
t_site_bestTimeSlots = {(t, d): [] for t in realteams for d in days}
prio_weight = {"A": 0, "B": 50, "C": 100}


toTime = False


earliestDay = {r: getDays[r][0] for r in rounds}
latestDay = {r: getDays[r][0] for r in rounds}
for r in rounds:
    for d in getDays[r]:
        dt = getDateTimeDay[d]
        if dt > getDateTimeDay[latestDay[r]]:
            latestDay[r] = d
        if dt < getDateTimeDay[earliestDay[r]]:
            earliestDay[r] = d


# %%
basicTeams = teams
basicGames = [(t1, t2)
              for (t1, t2) in games if t1 in basicTeams and t2 in basicTeams]

nBasicTeams = len(basicTeams)
nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9)
if nBasicTeamsPerCluster % 2 == 1:
    nBasicTeamsPerCluster += 1


nRounds1 = nBasicTeamsPerCluster-1
nBasicRounds = nPhases * (nBasicTeamsPerCluster-1)

if nBasicTeamsPerCluster == 1 or nBasicRounds == 1:
    nBasicRounds = nRounds
    nRounds1 = nRounds

basicRounds = range(1, nBasicRounds+1)

print("nPhases ", nPhases)
print("nGameClusters ", len(gameClusters))
print("nBasicTeamsPerCluster ", nBasicTeamsPerCluster)
print("nBasicTeams ", nBasicTeams)
print("nBasicRounds ", nBasicRounds)
print("nRounds1 ", nRounds1)


stretch = nRounds/nBasicRounds
# print ("regionalPatternUse " , regionalPatternUse)
rounds1 = range(1, nRounds1+1)
nGames = nTeams*nRounds1


getBasicRound = {r: int((r-1)/stretch)+1 for r in rounds}
getRealRounds = {br: [r for r in rounds if getBasicRound[r] == br]
                 for br in basicRounds}
# print ("stretch : " , stretch)

getBasicDays = {r: [] for r in basicRounds}
for r in rounds:
    getBasicDays[getBasicRound[r]] += (getDays[r])


getRoundDaysByDay = {d: [rd for rd in roundDays if rd[1] == d] for d in days}
getRoundDaysByRound = {
    r: [rd for rd in roundDays if rd[0] == r] for r in rounds}

daysSorted = []
for dt in sorted([getDateTimeDay[d] for d in days]):
    daysSorted.append(getDayByDateTime[dt])

minRest = {(t, s1, s2): thisSeason.minDaysBetweenGames for t in teams for s1 in [
    'A', 'H'] for s2 in ['A', 'H']}
for t in teamObjects:
    minRest[(t['id'], 'H', 'H')] = max(
        minRest[(t['id'], 'H', 'H')], t['minRest_HH'])
    minRest[(t['id'], 'H', 'A')] = max(
        minRest[(t['id'], 'H', 'A')], t['minRest_HA'])
    minRest[(t['id'], 'A', 'H')] = max(
        minRest[(t['id'], 'A', 'H')], t['minRest_AH'])
    minRest[(t['id'], 'A', 'A')] = max(
        minRest[(t['id'], 'A', 'A')], t['minRest_AA'])

maxMinRest = {t: max([minRest[(t, s1, s2)] for s1 in ['A', 'H']
                     for s2 in ['A', 'H']]) for t in teams}

excessGames = {rd: -roundDaysMax[rd] for rd in roundDays}
deficientGames = {rd: roundDaysMin[rd] for rd in roundDays}

excessGames = {rd:  max(0, excessGames[rd]) for rd in roundDays}
deficientGames = {rd: max(0, deficientGames[rd]) for rd in roundDays}

# %%


allowed_weekdays = {'--': [0, 1, 2, 3, 4, 5, 6], 'Mondays': [0], 'Tuesdays': [1], 'Wednesdays': [2], 'Thursdays': [3], 'Fridays': [4],
                    'Saturdays': [5], 'Sundays': [6], 'Weekdays': [0, 1, 2, 3, 4], 'Weekends': [5, 6], 'Mon.-Thu.': [0, 1, 2, 3], 'Fri.-Sun.': [4, 5, 6]}

hawishes = HAWish.objects.filter(
    scenario=s2, active=True).order_by('reason').values()
hawTeams = {}
hawDays = {}
hawTimes = {}
hawRounds = {}
hawRoundsString = {}
for c in HAWish.objects.filter(scenario=s2):
    # print ()
    # print (c.reason   )
    hawDays[c.id] = []
    hawTeams[c.id] = []
    hawTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()]
    if c.selection_mode == 0:
        for t in c.teams.filter(active=True):
            hawTeams[c.id].append(t.id)
    elif c.selection_mode == 1:
        for t in c.groups.all():
            for t2 in t.teams.filter(active=True):
                hawTeams[c.id].append(t2.id)
    elif c.selection_mode == 2:
        cids = [cn.id for cn in c.countries.all()]
        for t in teams:
            if t_globalCountry[t] in cids:
                hawTeams[c.id].append(t)

    if c.multidate:
        hawDays[c.id] = [dd.id for dd in c.dates.all()]
        # print ("multidate")
    else:
        if c.day and not c.day2:
            hawDays[c.id].append(c.day.id)
            # print('+ ',getDayById[e['day_id']])

        if not c.day and c.day2:
            hawDays[c.id].append(c.day2.id)
            # print('+ ',getDayById[e['day2_id']])

        if not c.day and not c.day2:
            for d in days:
                dt = getDateTimeDay[d]
                if dt.weekday() in allowed_weekdays[c.weekdays]:
                    # print (hawDays[e['id']])
                    hawDays[c.id].append(d)
                    # print('+ ',getDayById[d])

        if c.day and c.day2:
            day1 = getDateTimeDay[c.day.id]
            day2 = getDateTimeDay[c.day2.id]
            for d in days:
                dt = getDateTimeDay[d]
                # print (day1, "<=" , dt , "<=", day2 , " " ,  day1<=dt and dt<=day2 )
                if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]:
                    # print (day1, "<=" , dt , "<=", day2 , " " ,  day1<=dt and dt<=day2 )
                    hawDays[c.id].append(d)
                    # print('+ ',getDayById[d])

print("processing encounters")
encwishes = EncWish.objects.filter(scenario=s2, active=True).values()
encTeams1 = {}
encTeams2 = {}
encGroups = {}
encGames = {}
encTeams1String = {}
encTeams2String = {}
encDays = {e['id']: [] for e in encwishes}
encDaySets = {}
encTimes = {}
encRounds = {e['id']: [] for e in encwishes}
encRoundsString = {e['id']: "" for e in encwishes}
for c in EncWish.objects.filter(scenario=s2, active=True):
    encTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()]
    encTeams1[c.id] = []
    encTeams2[c.id] = []
    encGroups[c.id] = []
    encTeams1String[c.id] = ''
    encTeams2String[c.id] = ''
    for t in c.encounterGroups.all():
        encGroups[c.id].append(t)
    if c.useGroups:
        for gr in c.teams1_groups.all():
            for t in gr.teams.filter(active=True):
                encTeams1[c.id].append(t.id)
                encTeams1String[c.id] += gr.name + ', '
        for gr in c.teams2_groups.all():
            for t in gr.teams.filter(active=True):
                encTeams2[c.id].append(t.id)
                encTeams2String[c.id] += t.name + ', '
    else:
        for t in c.teams1.filter(active=True):
            encTeams1[c.id].append(t.id)
            encTeams1String[c.id] += t.name + ', '
        for t in c.teams2.filter(active=True):
            encTeams2[c.id].append(t.id)
            encTeams2String[c.id] += t.name + ', '
    encTeams1String[c.id] = encTeams1String[c.id][:-2]
    encTeams2String[c.id] = encTeams2String[c.id][:-2]

    if c.useEncounterGroups:
        tmp_games = [(t1.id, t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all(
        ) for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2]
        tmp_games += [(t2.id, t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all()
                      for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2 and ec.symmetry]
        encGames[c.id] = [tmp_games]
    else:
        elemHomeTeams = [encTeams1[c.id]]
        if c.forEachTeam1:
            elemHomeTeams = [[t] for t in encTeams1[c.id]]
        elemAwayTeams = [encTeams2[c.id]]
        if c.forEachTeam2:
            elemAwayTeams = [[t] for t in encTeams2[c.id]]
        encGames[c.id] = []
        # print ("NEW ENC ", elemHomeTeams,elemAwayTeams)
        for elh in elemHomeTeams:
            for ela in elemAwayTeams:
                # print (" ---  ENC ", elh,ela)
                tmp_games = [(t1, t2) for t1 in elh for t2 in ela if t1 != t2]
                if c.symmetry:
                    tmp_games += [(t1, t2)
                                  for t1 in ela for t2 in elh if t1 != t2]
                encGames[c.id].append(tmp_games)

    if c.multidate:
        encDays[c.id] = [dd.id for dd in c.dates.all()]
    else:
        if c.day:
            day1 = getDateTimeDay[c.day.id]

        if c.day and not c.day2:
            encDays[c.id].append(c.day.id)

        if not c.day and c.day2:
            encDays[c.id].append(c.day2.id)

        if not c.day and not c.day2:
            for d in days:
                dt = getDateTimeDay[d]
                # dt = parse(getDayById[d]['day'])
                if dt.weekday() in allowed_weekdays[c.weekdays]:
                    encDays[c.id].append(d)

        if c.day and c.day2:
            # day1= parse(c.day.day)
            # day2= parse(c.day2.day)
            day1 = getDateTimeDay[c.day.id]
            day2 = getDateTimeDay[c.day2.id]
            for d in days:
                dt = getDateTimeDay[d]
                # dt = parse(getDayById[d]['day'])
                if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]:
                    encDays[c.id].append(d)

    encDaySets[c.id] = []
    elemDays = [encDays[c.id]]
    if c.forEachDay or c.forOneDay:
        elemDays = [[d] for d in encDays[c.id]]

    lastDaySet = []
    for d in elemDays:
        tmpDays = d
        if (c.forEachDay or c.forOneDay) and c.timeframe != 0:
            tmpDays = []
            # day1= parse(getDayById[d[0]]['day'])
            day1 = getDateTimeDay[d[0]]
            if c.timeframe > 0:
                day2 = day1 + datetime.timedelta(days=c.timeframe-1)
                # print (e)
                # print (day1, day2)
                for d3 in days:
                    dt = getDateTimeDay[d3]
                    # dt = parse(getDayById[d3]['day'])
                    if day1 <= dt and dt <= day2:
                        tmpDays.append(d3)
            else:
                r1 = getDayById[d[0]]['round']
                for d3 in days:
                    # dt = parse(getDayById[d3]['day'])
                    dt = getDateTimeDay[d3]
                    if day1 <= dt and getRoundByDay[d3] < r1 + (-c.timeframe):
                        tmpDays.append(d3)
                # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe'])
                # for d4 in elemEncWishDays[cntr]:
                #     print (" - " ,getDayById[d4]['day'])

        if len([d for d in tmpDays if d not in lastDaySet]) > 0:
            encDaySets[c.id].append(tmpDays)
            lastDaySet = tmpDays
            # print("-.--- NEW DAYS", tmpDays)

    for ds in encDaySets[c.id]:
        for d3 in ds:
            encRounds[c.id] += getRoundsByDay[d3]
    encRounds[c.id] = sorted(list(set(encRounds[c.id])))
    for r in encRounds[c.id]:
        encRoundsString[c.id] += str(r)+"_"
    if encRoundsString[c.id] != "":
        encRoundsString[c.id] = encRoundsString[c.id][:-1]
    # print (encRoundsString[c.id] , " # " ,c.affected_rounds ,  encRoundsString[c.id] != c.affected_rounds)


onlyEarlyDays = []
onlyLateDays = []


alwaysConsiderAllGames = not mathModelName in [
    "Florida State League", "UEFA NL"]


elemEncWishes = {e['id']: [] for e in encwishes}
elemEncWishGames = {}
elemEncWishDays = {}

cntr = 0
for e in encwishes:
    for eg in encGames[e['id']]:
        for ed in encDaySets[e['id']]:
            if len(eg) > 0 and len(ed) > 0:
                cntr += 1
                elemEncWishes[e['id']].append(cntr)
                elemEncWishGames[cntr] = eg
                elemEncWishDays[cntr] = ed


# print (elemEncWishGames)
# print (elemEncWishDays)


encRelRoundsMin = {el: []
                   for enc in encwishes for el in elemEncWishes[enc['id']]}
encRelRoundsMax = {el: []
                   for enc in encwishes for el in elemEncWishes[enc['id']]}

for enc in encwishes:
    # print (e)
    # print ("ENC !! "  , enc['reason'] )
    for el in elemEncWishes[enc['id']]:
        relDaysSet = set(elemEncWishDays[el])
        for r in basicRounds:
            if len(relDaysSet.intersection(set(getBasicDays[r]))) > 0:
                frac = len(relDaysSet.intersection(
                    set(getBasicDays[r]))) / len(getBasicDays[r])
                # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac)
                if frac > 0:
                    encRelRoundsMin[el].append(r)
                if frac == 1:
                    encRelRoundsMax[el].append(r)

nElemEncWishes = sum([len(elemEncWishes[enc['id']]) for enc in encwishes])

# print (encRelRoundsMin)

elemHaWishes = {e['id']: [] for e in hawishes}
elemHaWishTeams = {}
elemHaWishDays = {}
elemHaWishFirstDay = {}

cntr = 1
for e in hawishes:
    elemTeams = [hawTeams[e['id']]]
    if e['forEachTeam']:
        elemTeams = [[t] for t in hawTeams[e['id']]]

    elemDays = [hawDays[e['id']]]
    if e['forEachDay'] or e['forOneDay']:
        elemDays = [[d] for d in hawDays[e['id']]]

    elemHaWishes[e['id']] = []
    allElemDays = []
    thisDaySet = []
    lastDaySet = []
    for d in elemDays:
        # print (e)
        if (e['forEachDay'] or e['forOneDay']) and e['timeframe'] != 1:
            thisDaySet = []
            # day1=  parse(getDayById[d[0]]['day'])
            day1 = getDateTimeDay[d[0]]
            if e['timeframe'] > 1:
                day2 = day1 + datetime.timedelta(days=e['timeframe']-1)
                for d3 in days:
                    # dt = parse(getDayById[d3]['day'])
                    dt = getDateTimeDay[d3]
                    if day1 <= dt and dt <= day2:
                        thisDaySet.append(d3)
            else:
                r1 = getDayById[d[0]]['round']
                for d3 in days:
                    dt = getDateTimeDay[d3]
                    # dt = parse(getDayById[d3]['day'])
                    if day1 <= dt and getRoundByDay[d3] < r1 + (-e['timeframe']):
                        thisDaySet.append(d3)
                print(" ROUND HA WISH ", e, thisDaySet, e['timeframe'])
        else:
            thisDaySet = d

        # only create wish id new day set is superset
        if len([d for d in thisDaySet if d not in lastDaySet]) > 0:
            for t in elemTeams:
                cntr += 1
                elemHaWishes[e['id']].append(cntr)
                elemHaWishTeams[cntr] = t
                elemHaWishDays[cntr] = thisDaySet.copy()
                elemHaWishFirstDay[cntr] = d[0]
            lastDaySet = thisDaySet.copy()
            allElemDays += thisDaySet

    hawRounds[e['id']] = []
    for d3 in set(allElemDays):
        hawRounds[e['id']] += getRoundsByDay[d3]
    hawRounds[e['id']] = sorted(list(set(hawRounds[e['id']])))
    hawRoundsString[e['id']] = ""
    for r in hawRounds[e['id']]:
        hawRoundsString[e['id']] += str(r)+"_"
    if hawRoundsString[e['id']] != "":
        hawRoundsString[e['id']] = hawRoundsString[e['id']][:-1]

nElemHaWishes = sum([len(elemHaWishes[enc['id']]) for enc in hawishes])

gameAttractivity = {(t1, t2): attractivity[t1, t2]
                    for (t1, t2) in games if t1 < t2}


sorted_gameAttractivity = sorted(
    gameAttractivity.items(), key=operator.itemgetter(1))

nGames = len(games)
topGames = [sorted_gameAttractivity[i] for i in range(
    int(0.8*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))]
goodGames = [sorted_gameAttractivity[i] for i in range(
    int(0.6*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))]

broadcastingwishes = BroadcastingWish.objects.filter(scenario=s2)


nonBlocked = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams}
travelDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams}
hideDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams}
nonBlockedRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams}
travelRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams}
hideRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams}

for bl in blockings:
    bl_val = 1.0 if bl['time'] == "----" else 0.5
    # if  bl['time']!="----":
    # print (bl, bl_val , getDayById[bl['day_id']] ,getDayById[bl['day_id']]['round'] ,getDayById[bl['day_id']]['round'] !=0 )
    if getDayById[bl['day_id']]['round'] != 0:
        if bl['type'] in ["Home", "Hide"]:
            nonBlocked[(
                bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val
            nonBlockedRounds[(
                bl['team_id'], getDayById[bl['day_id']]['round'])] -= bl_val
            # print ('FOUND HOME BLOCKING ', bl)
        if bl['type'] in ["Away", "Hide"]:
            travelDays[(
                bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val
            travelRounds[(bl['team_id'], getDayById[bl['day_id']]
                          ['round'])] -= bl_val
            # print ('FOUND AWAY BLOCKING ', bl)
        if bl['type'] in ["Hide"]:
            hideDays[(
                bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val
            hideRounds[(bl['team_id'], getDayById[bl['day_id']]
                        ['round'])] -= bl_val
            # print ('FOUND AWAY BLOCKING ', bl)

noPlayRounds = {t:  [r for r in rounds if nonBlockedRounds[(
    t, r)] + travelRounds[(t, r)] == 0] for t in teams}
noHomeRounds = {
    t:  [r for r in rounds if nonBlockedRounds[(t, r)] == 0] for t in teams}
# print("nonBlockedRounds" ,nonBlockedRounds)
# print("noHomeRounds" ,noHomeRounds)
# print("noPlayRounds" ,noPlayRounds)

playRounds = {t:  sorted(
    [r for r in rounds if hideRounds[(t, r)] > 0]) for t in teams}


# scale blocking of basic round to [0,1]
for t in teams:
    for r in basicRounds:
        if len(getBasicDays[r]) > 0:
            nonBlocked[(t, r)] /= len(getBasicDays[r])
            travelDays[(t, r)] /= len(getBasicDays[r])
            if getTeamById[t] == "AX Armani Exchange Milan":
                print(r, getRealRounds[r],
                      nonBlocked[(t, r)], nonBlocked[(t, r)])

for t in teams:
    if mathModelName == "UEFA NL" and noPlayRounds[t] in [[3, 4]]:
        t_usePhases[t] = False
        print("No need for phases  ", getTeamById[t])
        for t2 in opponents[t]:
            t_usePhases[t2] = False
            print(" -- also no need for phases  ", getTeamById[t2])
    if getTeamById[t] == "AX Armani Exchange Milan":
        t_usePhases[t] = False
        print("setting t_usePhases of ", getTeamById[t], t_usePhases[t])

runPatternAssignmentFirst = False


chosenGames = []

useFullModel1 = False

usePatterns = not mathModelName in ["NHL", "LNR"]
usePatterns = not mathModelName in ["NHL"]


balanceBreaks = mathModelName == "LNR"
haSymmetric = not mathModelName in ["NHL", "LNR"]
haSymmetric = not mathModelName in [
    "NHL", "Ligue 1", "Costa Rica Premier League"]
haSymmetric = not mathModelName in ["NHL"]
# haSymmetric = not mathModelName in ["NHL" , "LNR"]
if thisSeason.symmetry:
    haSymmetric = True

use2BreakPatterns = False

if thisSeason.initBreaks >= 2:
    use2BreakPatterns = True

half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1


prev_mirror_round = {r:  0 if r <= nRounds1 else r -
                     nRounds1+half_symmetry_offset for r in basicRounds}


for r in basicRounds:
    if r > nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1):
        prev_mirror_round[r] = int(
            (r-1)/nRounds1-1)*nRounds1 + half_symmetry_offset+1-prev_mirror_round[r] % nRounds1


getPhaseOfBasicRound = {
    br: getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds}
getBasicRoundsOfPhase = {ph: [
    br for br in basicRounds if getPhaseOfBasicRound[br] == ph] for ph in phases}

if use2BreakPatterns or nTeams > 200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams > 0:
    useFullModel1 = True
useFullModel1 = True


preplan_phases = phases
preplan_phases = [0]
if not haSymmetric:
    preplan_phases = phases

fixedGamesRounds = []

starweight = {t: 0.0 for t in teams}
for t1 in teams:
    for t2 in teams:
        starweight[t1] += distance[getTeamById[t1], getTeamById[t2]]

available_days = {(t, d): 1 for t in teams for d in days}


for bl in blockings:
    if bl['type'] in ["Home", "Hide"] and bl['time'] == '----':
        available_days[bl['team_id'], bl['day_id']] = 0

t_blocked_at = {(t, r): sum([available_days[t, d]
                             for d in getDays[r]]) == 0 for t in teams for r in rounds}

# %%

model2 = LpProblem(
    "League_Scheduling_Model_--_Schedule_Games_"+str(thisScenario.id), LpMinimize)

nextDay = {d: -1 for d in days}
previousDay = {d: -1 for d in days}

# cntr=0
lastDay = parse("01.01.1990")
for thisDay in sorted(getDayByDateTime.keys()):
    if thisDay-lastDay == datetime.timedelta(days=1):
        # cntr+=1
        d1 = getDayByDateTime[lastDay]
        d2 = getDayByDateTime[thisDay]
        nextDay[d1] = d2
        previousDay[d2] = d1
        # print ("next day of " ,  getNiceDay[d1] , ' ' , getNiceDay[d2])
    lastDay = thisDay

# print (cntr)
# cntr=0
# for d in days+higherLeagueDayIds :
#     for d2 in days+higherLeagueDayIds:
#         if  getDateTimeDay[d2]-getDateTimeDay[d]==datetime.timedelta(days=1):
#             cntr+=1
#             nextDay[d]=d2
#             previousDay[d2]=d
#             print ("next day of " ,  getNiceDay[d] , ' ' , getNiceDay[d2])
# print (cntr)


cntr = 0
cntr2 = 0

# print ("games",games)
# print (roundDays)

seedTV = []
dontPlay = []
dontPlayGames = []

for bl in blockings:
    if bl['type'] == "Hide":
        dontPlay += [(bl['team_id'], bl['day_id'])]

dontPlay += [(t, d) for d in days for t in teams if getDayMaxGames[d] == 0]


x = {(t1, t2, rd): 0 for t1 in teams for t2 in teams for rd in roundDays}
for (t1, t2) in games:
    for rd in roundDays:
        x[(t1, t2, rd)] = 1

for (t1, t2, rd) in x.keys():
    if blocked_arena[(t1, rd[1], "----")] and runMode == 'Improve' and not thisSeason.allowBlockingViosInImprove:
        # cntr +=1
        # print ("FORBIDDING")

        x[(t1, t2, rd)] = 0

for (t, d) in dontPlay:
    for t3 in teams:
        if not t3 in [t1, t2]:
            for rd in getRoundDaysByDay[d]:
                x[(t, t3, rd)] = 0
                x[(t3, t, rd)] = 0

for (t1, t2, d) in dontPlayGames:
    for rd in getRoundDaysByDay[d]:
        x[(t1, t2, rd)] = 0

attendance = {(t1, t2, d): 0 for (t1, t2) in games for d in days}

# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays}
x_round = {(t1, t2, r): 0 for t1 in teams for t2 in teams for r in rounds}

for (t1, t2) in games:
    # for rd in roundDays:
    #     if x[(t1, t2, rd)] == 1:
    #         if not evalRun:
    #             x[(t1, t2, rd)] = LpVariable('x_'+str(t1)+'_'+str(t2)+'_' +
    #                                               str(rd[0])+'_'+str(rd[1]), lowBound=0, upBound=1, cat=LpContinuous)
    #             cntr += 1
    #         else:
    #             cntr += len(roundDays)
                
    for r in rounds:
        x_round[(t1, t2, r)] = LpVariable('x_round_'+str(t1)+'_' +
                                               str(t2)+'_'+str(r), lowBound=0, upBound=1, cat=LpContinuous)
        # model2 += x_round[(t1, t2, r)] == sum([x[(t1, t2, rd)]
        #                                        for rd in getRoundDaysByRound[r]]), f'game_{t1}_{t2}_sum_of_days_equals_round_{r}'


t_prev_mirror_round = {(t, r): 0 for t in teams for r in rounds}

for t1 in teams:
    phaseLength = int(len(playRounds[t1])/nPhases+0.5)
    # print ("phaseLength", phaseLength)
    for i in range(len(playRounds[t1])):
        # print (i, phaseLength)
        if i >= phaseLength:
            # print ("setting " , playRounds[t1], playRounds[t1][i-phaseLength])
            # print ("setting " , playRounds[t1][i], "->",playRounds[t1][i-phaseLength])
            t_prev_mirror_round[(t1, playRounds[t1][i])
                                ] = playRounds[t1][i-phaseLength]
            # t_prev_mirror_round[(t,playRounds[t1][i])]=5



if not evalRun:
    for (t1, t2) in games:
        for r in rounds:
            if r > nRounds1 and thisSeason.symmetry:
                prev_round = prev_mirror_round[r]
                if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]:
                    prev_round = t_prev_mirror_round[t1, r]

                if prev_round > 0:
                    model2 += x_round[(t1, t2, r)
                                      ] == x_round[(t2, t1, prev_round)], f"symmetric_{r}_{prev_round}"


homeInRound = {(t1, r): LpVariable('homeInRound_'+str(t1)+'_'+str(r),
                                        lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds}
awayInRound = {(t1, r): LpVariable('awayInRound_'+str(t1)+'_'+str(r),
                                        lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds}
gameInBasicRound = {(t1, t2, r): LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound=0,
                                                 upBound=defaultGameRepetions, cat=LpContinuous) for (t1, t2) in games for r in basicRounds}
homeInBasicRound = {(t1, r): LpVariable('homeInBasicRound_'+str(t1)+'_'+str(
    r), lowBound=0,  cat=LpContinuous) for t1 in teams for r in basicRounds}
awayInBasicRound = {(t1, r): LpVariable('awayInBasicRound_'+str(t1)+'_'+str(
    r), lowBound=0,  cat=LpContinuous) for t1 in teams for r in basicRounds}
break3InRound = {(t1, r): LpVariable('break3InRound_'+str(t1)+'_'+str(r),
                                          lowBound=0, cat=LpContinuous) for t1 in teams for r in rounds}
# breakInRound= {(t1,r) : LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = LpContinuous) for t1 in teams for r in rounds}
# breaksTotal =  LpVariable('breaksTotal', lowBound = 0, cat = LpContinuous)
pairingVio = {(pair['id'], d): LpVariable('pairingVio_'+str(pair['id'])+'_' +
                                               str(d), lowBound=0, cat=LpContinuous) for pair in pairings for d in days}
derbyMissing = {d: LpVariable(
    'derbyMissing_'+str(d), lowBound=0, cat=LpContinuous) for d in days}

breakVio = {(br['id'], t): LpVariable('breakVio_' + str(br['id'])+'_' +
                                           str(t), lowBound=0, cat=LpContinuous) for br in breaks for t in teams}
blockingVio = {bl['id']: LpVariable(
    'blockingVio_' + str(bl['id']), lowBound=0, cat=LpContinuous) for bl in blockings}
# blockingVioTotal =  LpVariable('blockingVioTotal', lowBound = 0, cat = LpContinuous)

hawVio = {haw['id']: LpVariable(
    'hawVio' + str(haw['id']), lowBound=0, cat=LpContinuous) for haw in hawishes}
HawVioTooLess = {el: LpVariable('havviotooless_' + str(el), lowBound=0, cat=LpContinuous)
                 for haw in hawishes for el in elemHaWishes[haw['id']]}
HawVioTooMuch = {el: LpVariable('havviotoomuch_' + str(el), lowBound=0, cat=LpContinuous)
                 for haw in hawishes for el in elemHaWishes[haw['id']]}
# HawVioTotal =  LpVariable('HawVioTotal', lowBound = 0, cat = LpContinuous)
encVio = {enc['id']: LpVariable(
    'encVio' + str(enc['id']), lowBound=0, cat=LpContinuous) for enc in encwishes}
encVioTooLess = {el: LpVariable('encViotooless' + str(enc['id'])+"_"+str(
    el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]}
encVioTooMuch = {el: LpVariable('encViotoomuch' + str(enc['id'])+"_"+str(
    el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]}
# encVioTotal =  LpVariable('encVioTotal', lowBound = 0, cat = LpContinuous)
# broadVio =  { d : LpVariable('broadVio'+ str(d) , lowBound = 0, cat = LpContinuous) for d in days}
broadVioTm = {(b.id, r): LpVariable('broadVioTm_' + str(b.id) + "_" + str(r),
                                         lowBound=0, cat=LpContinuous) for b in broadcastingwishes for r in rounds}
gamesTooClose2 = {(t, r): LpVariable('gamesTooClose2_' + str(t) + '_' +
                                          str(r), lowBound=0, cat=LpContinuous) for t in teams for r in rounds}
missingGamesVio = {(t1, t2): LpVariable('missingGamesVio_' + str(t1) +
                                             '_'+str(t2), lowBound=0, cat=LpContinuous) for (t1, t2) in games}

home = {}
away = {}
home_time = {}
away_time = {}
away_in_cluster = {}
away_in_cluster_day = {}

getRoundDaysByBasicRound = {br: [rd for r in getRealRounds[br]
                                 for rd in getRoundDaysByRound[r]] for br in basicRounds}
getMaxGameOnRoundDaysByBasicRound = {br: sum(
    roundDaysMax[rd] for r in getRealRounds[br] for rd in getRoundDaysByRound[r]) for br in basicRounds}

for t in realteams:
    for d in days:
        away[t, d] = lpSum([x[(t2, t, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable(
            'away'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous)
        home[t, d] = lpSum([x[(t, t2, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable(
            'home'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous)
        

# for t in realteams:
#     for r in rounds:
#         if thisSeason.gamesPerRound == "one day":
#             model2 += homeInRound[(t, r)] == lpSum([x_round[(t, t2, r)]
#                                                     for t2 in opponents[t] ]), f'team_{t}_home_in_round_{r}'
#             model2 += awayInRound[(t, r)] == lpSum([x_round[(t2, t, r)]
#                                                     for t2 in opponents[t] ]), f'team_{t}_away_in_round_{r}'
#             # model2 += homeInRound[(t, r)] == lpSum([x[(t, t2, rd)]
#             #                                         for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_home_in_round_{r}'
#             # model2 += awayInRound[(t, r)] == lpSum([x[(t2, t, rd)]
#             #                                         for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_away_in_round_{r}'
        
#         model2 += homeInRound[(t, r)] + awayInRound[(t, r)] <= 1, f'homeInR_awayInR_{t}_{r}'
#         if r >= 3:
#             model2 += break3InRound[(t, r)] + 2 >= homeInRound[(t, r-2)] + \
#                 homeInRound[(t, r-1)]+homeInRound[(t, r)], f'team_{t}_home_break3_in_round_{r}'
#             model2 += break3InRound[(t, r)] + 2 >= awayInRound[(t, r-2)] + \
#                 awayInRound[(t, r-1)]+awayInRound[(t, r)], f'team_{t}_away_break3_in_round_{r}'

#     model2 += lpSum(break3InRound[(t,r)] for r in rounds) <= 1



breakVioBalance = {t: LpVariable(
    'breakVioBalance_'+str(t), lowBound=0, cat=LpContinuous) for t in realteams}
numBreaks = {t:  lpSum([breakVio[(bl['id'], t)]
                       for bl in breaks]) for t in realteams}

succBreaks = [bl for bl in breaks if bl['round1']+1 == bl['round2']]

# for t in realteams:
#     for bl in breaks:
#         bl_id = bl['id']
#         model2 += breakVio[(bl['id'], t)] + 1 >= homeInRound[t,
#                                                              bl['round1']] + homeInRound[t, bl['round2']], f'breakvio_home_{bl_id}_for_{t}'
#         model2 += breakVio[(bl['id'], t)] + 1 >= awayInRound[t,
#                                                              bl['round1']] + awayInRound[t, bl['round2']], f'breakvio_away_{bl_id}_for_{t}'

getDays[0] = []


use_currentSolution = False

currentGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams}


# if use_currentSolution and len(currentSolution) !=len(fixedGames):

teamGameCntr = {(t, r): 0 for t in teams for r in rounds}


# print ("days " , days)
for d in days:
    minG = sum([roundDaysMin[rd] - deficientGames[rd]
               for rd in getRoundDaysByDay[d]])
    maxG = sum([roundDaysMax[rd] + excessGames[rd]
               for rd in getRoundDaysByDay[d]])
    # model2 += lpSum([home[(t, d)] for t in realteams]) <= maxG, f'max_games_on_day_{d}'

    # if len(getRoundDaysByDay[d]) > 1:
    #     for rd in getRoundDaysByDay[d]:
    #         model2 += lpSum([x[(t1, t2, rd)] for (t1, t2) in games]
    #                         ) <= roundDaysMax[rd] + excessGames[rd]

print("rounds ", rounds, nRounds)
for r in rounds:
    # one game a round for everyone
    for t1 in realteams:
        if thisSeason.gamesPerRound == "one day":
            # cnstr = lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)]
            #               for t2 in opponents[t1] for rd in getRoundDaysByRound[r]])
            cnstr = lpSum([x_round[(t1, t2, r)] + x_round[(t2, t1, r)]
                          for t2 in opponents[t1]])
            if len(cnstr) > 0:
                model2 += cnstr <= 1, f'one_game_in_round_{r}_for_{t1}'
        
# exit(0)
print("realteams ", realteams)




print("taking care of right numbers of games ... ")
cntr = 0

if not specialGameControl:
    for (t1, t2) in realgames:
        if undirectedGameCntr[(t1, t2)] == 0:
            # model2 += lpSum([x_round[(t1, t2, r)] for r in rounds]
            #                 ) == gameCntr[(t1, t2)] - missingGamesVio[(t1, t2)], f'let_{t1}_{t2}_play_gameCntr_times'
            model2 += lpSum([x_round[(t1, t2, r)] for r in rounds]
                            ) == gameCntr[(t1, t2)], f'let_{t1}_{t2}_play_gameCntr_times'
        
        missingGamesVio[(t1, t2)].upBound = max(
            0, gameCntr[(t1, t2)]+undirectedGameCntr[(t1, t2)] - currentGameCntr[(t1, t2)])


# %%

breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \
    2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)]
            for bl in breaks for t in importantteams])
break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds])

missingGamesVioTotal = lpSum(
    [missingGamesVio[(t1, t2)] for (t1, t2) in basicGames])

standardObjectives = 0 \
    + missingGamesVioTotal\
    # + gew['Breaks']*breakVioTotal \
    # + gew['Breaks']*2*break3VioTotal \

model2 += standardObjectives

for v in model2.variables():
    v.cat = LpInteger


with open ("model2_xpress.txt", "w") as f:
    f.write(model2.__repr__())

# %%

model2.solve(XPRESS(msg=1))

home_dict = {}
away_dict = {}
missing_games = []
for key in x_round.keys():
    if type(x_round[key]) != int and x_round[key].value() > 0:
        home_dict[key[2],key[0]] = key[1]
        away_dict[key[2],key[1]] = key[0]
for key in missingGamesVio.keys():
    if type(missingGamesVio[key]) != int and missingGamesVio[key].value() > 0:
        missing_games.append((t_shortname[split_key[0]],t_shortname[split_key[1]]))


sol = " \
    <style> \
    table, th, td { \
    border: 1px solid black; \
    border-collapse: collapse; \
    } \
    </style> \
"
sol += "<table style='border:1px solid black'>\n"
sol += "<thead>\n"
sol += "<tr><th></th>"
for r in rounds:
    sol += f"<th>{r}</th>"
sol += "</tr>"
sol += "</thead>\n"
sol += "<tbody>\n"
for t in teams:
    tname = t_shortname[t]
    sol += f"<tr><td>{tname}</td>"
    for r in rounds:
        if (r,t) in home_dict.keys():
            opponent = t_shortname[home_dict[(r,t)]]
            sol += f"<td style='background-color:lightsteelblue'>{opponent}</td>"
        elif (r,t) in away_dict.keys():
            opponent = t_shortname[away_dict[(r,t)]]
            sol += f"<td style='background-color:lightyellow'>{opponent}</td>"
        else:
            sol += "<td></td>"
    sol += "</tr>"
sol += "</tbody>\n"
sol += "</table>\n"
sol += "<br><br><br>\n"
for game in missing_games:
    sol += f'{game}<br>\n'


with open('xpress_sol.html', 'w') as f:
    f.write(sol)
# %%

# API_KEY = 'AJwxeJRRZtJ9rOKVGTV6ruvG9TvC1wnE'

# from quantagonia.qubo import QuboModel
# from quantagonia.enums import HybridSolverConnectionType
# from quantagonia.runner import Runner
# from quantagonia.runner_factory import RunnerFactory
# from quantagonia.spec_builder import QuboSolverType, QUBOSpecBuilder


# spec = QUBOSpecBuilder()
# spec.set_time_limit(time_limit=600)

# runner = RunnerFactory.getRunner(HybridSolverConnectionType.CLOUD, api_key=API_KEY)


# # %%

# qubo_prob = QuboModel()

# qubos = {}
# for var in model2.variables():
#     qubos[str(var)] = qubo_prob.addVariable(f"{var}", initial=0)


# for var in model2.objective:
#     qubo_prob.objective += -1*qubos[str(var)]

# # one_game_in_round
# for ckey,cval in model2.constraints.items():
#     if cval.sense == -1:
#         vars_in_cons = cval.toDict()['coefficients']
#         for q in itertools.combinations(vars_in_cons,2):
#             qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']]


# # let_play_gameCntr_times
# for ckey,cval in model2.constraints.items():
#     if cval.sense == 0:
#         qubo_prob.objective += 1
#         vars_in_cons = cval.toDict()['coefficients']
#         for var in vars_in_cons:
#             qubo_prob.objective += -1*-1*qubos[var['name']]
#         for q in itertools.combinations(vars_in_cons,2):
#             qubo_prob.objective += -1*2*qubos[q[0]['name']]*qubos[q[1]['name']]


# with open('model2_qubo.txt','w') as f:
#     f.write(str(qubo_prob))
# status = qubo_prob.solve(spec.getd(), runner=runner)


# # %%

# home_dict = {}
# away_dict = {}
# missing_games = []
# for key in qubo_prob.vars.keys():
#     if qubo_prob.vars[key].eval() > 0:        
#         split_key = key.split('_')
#         if split_key[0] == 'x':
#             home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3])
#             away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2])
#         elif split_key[0] == 'missingGamesVio':
#             missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])]))


# sol = " \
#     <style> \
#     table, th, td { \
#     border: 1px solid black; \
#     border-collapse: collapse; \
#     } \
#     </style> \
# "
# sol += "<table style='border:1px solid black'>\n"
# sol += "<thead>\n"
# sol += "<tr><th></th>"
# for r in rounds:
#     sol += f"<th>{r}</th>"
# sol += "</tr>"
# sol += "</thead>\n"
# sol += "<tbody>\n"
# for t in teams:
#     tname = t_shortname[t]
#     sol += f"<tr><td>{tname}</td>"
#     for r in rounds:
#         if (r,t) in home_dict.keys():
#             opponent = t_shortname[home_dict[(r,t)]]
#             sol += f"<td style='background-color:lightsteelblue'>{opponent}</td>"
#         elif (r,t) in away_dict.keys():
#             opponent = t_shortname[away_dict[(r,t)]]
#             sol += f"<td style='background-color:lightyellow'>{opponent}</td>"
#         else:
#             sol += "<td></td>"
#     sol += "</tr>"
# sol += "</tbody>\n"
# sol += "</table>\n"
# sol += "<br><br><br>\n"
# for game in missing_games:
#     sol += f'{game}<br>\n'


# with open('qubo_sol.html', 'w') as f:
#     f.write(sol)


# exit()

# %%

from ortools.sat.python import cp_model
"""Minimal CP-SAT example to showcase calling the solver."""
# Creates the model.
model = cp_model.CpModel()

# Creates the variables.
# x = model.NewBoolVar('x')
# y = model.NewBoolVar('y')
# z = model.NewBoolVar('z')

sat = {}
for var in model2.variables():
    sat[str(var)] = model.NewBoolVar(f'var')

# %%

for ckey,cval in model2.constraints.items():
    cons_dict = cval.toDict()
    vars = cons_dict['coefficients']
    sense = cons_dict['sense']
    constant = cons_dict['constant']
    if sense == -1:
        model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant <= 0)
    elif sense == 0:
        model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant == 0)
    elif sense == 1:
        model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant >= 0)


objective_terms = []
# for var in model2.objective.toDict():
#     objective_terms.append(var['value']*sat[var['name']])
model.Minimize(sum(objective_terms)) 

# %%

# # Creates the constraints.
# # one_game_in_round
# for ckey,cval in model2.constraints.items():
#     if cval.sense == -1:
#         vars_in_cons = cval.toDict()['coefficients']
#         model.AddAtMostOne(sat[i['name']] for i in vars_in_cons)
#         # for q in itertools.combinations(vars_in_cons,2):
#         #     qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']]

# # let_play_gameCntr_times
# for ckey,cval in model2.constraints.items():
#     if cval.sense == 0:
#         vars_in_cons = cval.toDict()['coefficients']
#         model.AddExactlyOne(sat[i['name']] for i in vars_in_cons)

  
# objective_terms = []
# for var in model2.objective:
#     objective_terms.append(sat[str(var)])
# model.Minimize(sum(objective_terms))

# %%

print("STARTING SAT-SOLVER")
# Creates a solver and solves the model.
solver = cp_model.CpSolver()
solver.parameters.log_search_progress = True
print("STARTING SAT-SOLVER")
status = solver.Solve(model)


# %%

home_dict = {}
away_dict = {}
missing_games = []
for key in sat.keys():
    if solver.BooleanValue(sat[key]):                
        split_key = key.split('_')
        if split_key[0] == 'x':
            home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3])
            away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2])
        elif split_key[0] == 'missingGamesVio':
            missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])]))


sol = " \
    <style> \
    table, th, td { \
    border: 1px solid black; \
    border-collapse: collapse; \
    } \
    </style> \
"
sol += "<table style='border:1px solid black'>\n"
sol += "<thead>\n"
sol += "<tr><th></th>"
for r in rounds:
    sol += f"<th>{r}</th>"
sol += "</tr>"
sol += "</thead>\n"
sol += "<tbody>\n"
for t in teams:
    tname = t_shortname[t]
    sol += f"<tr><td>{tname}</td>"
    for r in rounds:
        if (r,t) in home_dict.keys():
            opponent = t_shortname[home_dict[(r,t)]]
            sol += f"<td style='background-color:lightsteelblue'>{opponent}</td>"
        elif (r,t) in away_dict.keys():
            opponent = t_shortname[away_dict[(r,t)]]
            sol += f"<td style='background-color:lightyellow'>{opponent}</td>"
        else:
            sol += "<td></td>"
    sol += "</tr>"
sol += "</tbody>\n"
sol += "</table>\n"
sol += "<br><br><br>\n"
for game in missing_games:
    sol += f'{game}<br>\n'


with open('sat_sol.html', 'w') as f:
    f.write(sol)
# %%
exit()

if not evalRun:
    for (t1, t2) in realgames:
        # every pair plays each other in each phase once
        for p in phases:
            # phase violation in last phase possible if it contained more rounds than necessary
            if p < len(phases)-1 or True:
                if t_usePhases[t1] or t_usePhases[t2]:
                    if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]:
                        relDays = []
                        phaseLength = int(len(playRounds[t1])/nPhases+0.5)
                        for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]:
                            relDays += getDays[rr]
                            print("adding days of round ", rr, " to phase ", p)
                    else:
                        relDays = getDaysOfPhase[p]
                    model2 += lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)]
                                    for d in relDays for rd in getRoundDaysByDay[d]]) <= 1
                    # print (len(relDays),"reldays")
                    # print (getTeamById[t1],getTeamById[t2], sum([x[(t1,t2,rd)] + x[(t2,t1,rd)]  for d in relDays for rd in getRoundDaysByDay[d] ] ))

if not evalRun:
    for t in realteams:
        if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases:
            for p in phases:
                rds = [r for r in rounds if getPhaseOfRound[r] == p]
                nblocked = [r for r in noHomeRounds[t] if r in rds]
                print(p, len(rds), len(nblocked),
                      rds, nblocked, getTeamById[t])
            for p in phases:
                phaseLength = int(len(playRounds[t])/nPhases+0.5)
                model2 += lpSum([home[(t, d)]
                                for d in getDaysOfPhase[p]]) <= int(phaseLength/2+1)

    if thisSeason.minRoundsBetweenGameOfSameTeams > 0:
        for r in rounds:
            if r + thisSeason.minRoundsBetweenGameOfSameTeams <= nRounds:
                rds = []
                for r2 in range(r, r+thisSeason.minRoundsBetweenGameOfSameTeams+1):
                    rds += getRoundDaysByRound[r2]
                rds2 = []
                for r2 in range(r, r+int(0.5*thisSeason.minRoundsBetweenGameOfSameTeams)):
                    rds2 += getRoundDaysByRound[r2]
                # print ("ONLY ONE IN " , rds)
                # print ("ONLY ONE IN " , rds2)
                for t1 in realteams:
                    for t2 in realteams:
                        if t1 < t2 and (gameCntr[(t1, t2)]*gameCntr[(t2, t1)] > 0 or x_round[(t1, t2, r)] != 0):
                            # model2 +=  sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r]
                            model2 += sum([(x[(t1, t2, rd)]+x[(t2, t1, rd)])
                                          for rd in rds]) <= 1


if not evalRun:
    # max length home stands /trips
    for r in range(1, nRounds-thisSeason.maxTourLength+1):
        # print (" at least one home and away in rounds ")
        # for r3  in range(r,r+thisSeason.maxTourLength+1):
        #     print (r3 )
        for t in teams:
            if t not in noBreakLimitTeams:
                # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1
                model2 += lpSum([awayInRound[t, r2] for r2 in range(r, r +
                                thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength
                model2 += lpSum([homeInRound[t, r2] for r2 in range(r, r +
                                thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength

print("check 1")

# blockings
for bl in blockings:
    if getDayById[bl['day_id']]['round'] != 0:
        if thisSeason.useFeatureKickOffTime and bl['time'] != '----':
            if bl['type'] in ["Home", "Hide"]:
                model2 += blockingVio[bl['id']
                                      ] == home_time[bl['team_id'], bl['day_id'], bl['time']]
                # print ('FOUND HOME BLOCKING ', bl)
            else:
                model2 += blockingVio[bl['id']
                                      ] == away_time[bl['team_id'], bl['day_id'], bl['time']]
                # print ('FOUND AWAY BLOCKING ', bl)
        else:
            if bl['type'] in ["Home", "Hide"]:
                model2 += blockingVio[bl['id']
                                      ] == home[bl['team_id'], bl['day_id']]
                # print ('FOUND HOME BLOCKING ', bl)
            else:
                model2 += blockingVio[bl['id']
                                      ] == away[bl['team_id'], bl['day_id']]
                # print ('FOUND AWAY BLOCKING ', bl)


print("check 2")

hawOneVio = {}
hawForOneNotViolated = {}


def getStringFromSet(ss):
    s2 = ""
    for s in ss:
        s2 += str(s)+"_"
    return s2[:-1]


    # hawishes
for haw in hawishes:
    print(haw['reason'])
    for el in elemHaWishes[haw['id']]:
        if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']]) > 0:
            if haw['homeAway'] == 'Home':
                relGames = lpSum([home_time[t, d, tm] for d in elemHaWishDays[el]
                                 for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]])
                # print (haw['id'] ," haw : ", relGames, hawTimes[haw['id']])
            elif haw['homeAway'] == 'Away':
                relGames = lpSum([away_time[t, d, tm] for d in elemHaWishDays[el]
                                 for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]])
            else:
                # print(haw,el)
                relGames = lpSum([home_time[t, d, tm] + away_time[t, d, tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \
                    - lpSum([x_time[(t1, t2, rd, tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']]
                            for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd, tm) in x_time.keys()])

        else:
            if haw['homeAway'] == 'Home':
                relGames = lpSum([home[t, d] for d in elemHaWishDays[el]
                                 for t in elemHaWishTeams[el]])
            elif haw['homeAway'] == 'Away':
                relGames = lpSum([away[t, d] for d in elemHaWishDays[el]
                                 for t in elemHaWishTeams[el]])
            else:
                relGames = lpSum([home[t, d] + away[t, d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1, t2, rd] for d in elemHaWishDays[el]
                                                                                                                              for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd) in x.keys()])
        if haw['minGames'] > 0:
            model2 += relGames >= haw['minGames'] - HawVioTooLess[el]
            # print ("adding min ha constraint")
        if haw['maxGames'] >= 0:
            model2 += relGames <= haw['maxGames'] + HawVioTooMuch[el]
            # print ("adding max ha constraint")

    usedConstraintNames = [""]
    if haw['forOneDay']:
        # print (haw['forOneDay'], hawDays[haw['id']])
        # print ([el for el in elemHaWishes[haw['id']] ])
        # for el in elemHaWishes[haw['id']] :
        #     print("- " , elemHaWishDays[el]  , elemHaWishTeams[el] )
        # print ([ (el, elemHaWishFirstDay[el])  for el in elemHaWishes[haw['id']] ])
        relTeamString = {el: getStringFromSet(
            elemHaWishTeams[el]) for el in elemHaWishes[haw['id']]}
        relTeams = set([relTeamString[el] for el in elemHaWishes[haw['id']]])
        # print (relTeams)
        for rt in relTeams:
            rtname = rt
            if len(rt) > 50:
                scname = ""
                while scname in usedConstraintNames:
                    ttt = bytes(rt + ''.join(random.choice(string.ascii_lowercase)
                                for i in range(10)), 'utf-8')
                    scname = hashlib.sha224(ttt).hexdigest()
                    rtname = scname[:10]
                    usedConstraintNames.append(rtname)
                    # print ("use rtname to encode wishes ", rtname)

            hawOneVio[(haw['id'], rt)] = LpVariable(
                'hawOneVio_'+str(haw['id'])+"_"+rtname, cat=LpContinuous)
            for fd in hawDays[haw['id']]:
                relWishes = [el for el in elemHaWishes[haw['id']]
                             if elemHaWishFirstDay[el] == fd]
                # relWishes = [el for el in elemHaWishes[haw['id']] ]
                # print (" -" , relWishes , [elemHaWishDays[el] for el in relWishes])
                hawForOneNotViolated[(haw['id'], rt, fd)] = 0
                if len(relWishes) > 0:
                    hawForOneNotViolated[(haw['id'], rt, fd)] = LpVariable(
                        'hawForOneViolated_'+str(haw['id'])+"_"+rtname+"_"+str(fd), cat=LpBinary)
                    model2 += lpSum(HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el]
                                    == rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'], rt, fd)])
            model2 += lpSum(hawForOneNotViolated[(haw['id'], rt, fd)]
                            for fd in hawDays[haw['id']]) >= haw['forOneDayNum']-hawOneVio[(haw['id'], rt)]
        model2 += lpSum(hawOneVio[(haw['id'], rt)]
                        for rt in relTeams) == hawVio[haw['id']]
    else:
        # print (haw['forOneDay'], hawDays[haw['id']])
        model2 += hawVio[haw['id']] == lpSum(HawVioTooMuch[el]+HawVioTooLess[el]
                                             for el in elemHaWishes[haw['id']])

    if haw['prio'] == "Hard" and "HardHAWishesNotBreakable" in special_wishes_active:
        model2 += hawVio[haw['id']] == 0
        print("WISH HARD", haw['reason'])


# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]])
# print (hawDays)
# print (elemHaWishFirstDay)

encOneVio = {}
encForOneNotViolated = {}
print("check 3")
# encwishes
for enc in encwishes:
    print(enc)
    for el in elemEncWishes[enc['id']]:
        # print (enc)
        # model2+=  encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']]

        if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']]) > 0:
            if enc['minGames'] > 0:
                model2 += encVioTooLess[el] >= enc['minGames'] - sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (
                    t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games])
            if enc['maxGames'] >= 0:
                # if enc['maxGames']==0:
                print(enc['reason'], '  ', elemEncWishDays[el], '  ', enc['time'],
                      '  ', encTeams1[enc['id']], '  ', encTeams2[enc['id']])
                #     print (sum([x_time[(t1,t2,rd, enc['time'])]  for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games  ]))
                model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (
                    t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games])
        else:
            if enc['minGames'] > 0:
                model2 += encVioTooLess[el] >= enc['minGames'] - sum([x[t1, t2, rd] for d in elemEncWishDays[el]
                                                                     for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games])
            if enc['maxGames'] >= 0:
                # if enc['maxGames']==0:
                #     print (enc['reason'], '  ', encDays[enc['id']], '  ', encTeams1[enc['id']] , '  ',encTeams2[enc['id']]   )
                model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x[t1, t2, rd] for d in elemEncWishDays[el]
                                                                      for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games])

    if enc['forOneDay']:
        for ed in encDaySets[enc['id']]:
            if len(ed) > 0:
                relWishes = [el for el in elemEncWishes[enc['id']]
                             if elemEncWishDays[el] == ed]
                print("###", ed, relWishes)
                encForOneNotViolated[(enc['id'], ed[0])] = LpVariable(
                    'encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=LpBinary)
                model2 += sum(encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * (
                    1-encForOneNotViolated[(enc['id'], ed[0])])
        model2 += sum(encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len(
            ed) > 0) >= enc['forOneDayNum']-encVio[enc['id']]
    else:
        model2 += encVio[enc['id']] == sum(encVioTooMuch[el]+encVioTooLess[el]
                                           for el in elemEncWishes[enc['id']])

    if enc['prio'] == "Hard" and "HardEncWishesNotBreakable" in special_wishes_active:
        model2 += encVio[enc['id']] == 0
        print("WISH HARD", enc['reason'])


print("check 4")


# return ""
weekdayHomePref = {}
dayHomePref = {}
for t in teams:
    tm = getTeamByName[getTeamById[t]]
    weekdayHomePref[(t, 'Mon')] = tm['home_pref_mo']
    weekdayHomePref[(t, 'Tue')] = tm['home_pref_tu']
    weekdayHomePref[(t, 'Wed')] = tm['home_pref_we']
    weekdayHomePref[(t, 'Thu')] = tm['home_pref_th']
    weekdayHomePref[(t, 'Fri')] = tm['home_pref_fr']
    weekdayHomePref[(t, 'Sat')] = tm['home_pref_sa']
    weekdayHomePref[(t, 'Sun')] = tm['home_pref_su']
    for d in days:
        dayHomePref[(t, d)] = weekdayHomePref[(t, getWeekDay[d])]


maxTravelDistance = max([distanceById[t1, t2]
                        for t1 in realteams for t2 in realteams])
maxTravelDistance = max(1, maxTravelDistance)

singleTripWeight = 50
breakImbalanceTotal = lpSum([breakVioBalance[t] for t in realteams])
# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]])
HawVioTotal = lpSum([prioVal[haw['prio']] * hawVio[haw['id']]
                    for haw in hawishes])
encVioTotal = lpSum([prioVal[enc['prio']] * encVio[enc['id']]
                    for enc in encwishes])
seedVioTotal = lpSum([100*prioVal[enc['prio']] * encVio[enc['id']]
                     for enc in encwishes if enc['reason'] == "Seed Game"])
# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes])
broadVioTotal = lpSum([10 * broadVioTm[(b.id, r)]
                      for b in broadcastingwishes for r in rounds])
breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \
    2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)]
            for bl in breaks for t in importantteams])
break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds])
tooManyTop4InRowTotal = lpSum([10*tooManyTop4InRow[(t, r)]
                              for t in teams for r in rounds])
pairingVioTotal = lpSum([5 * prioVal[pair['prio']] * pairingVio[(pair['id'], d)]
                        for pair in pairings for d in days])
# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Home"])
# print (blockings)
# for bl in blockings:
# if bl['type'] in ["Home"]:
# print (blocked_arena[(bl["team_id"],bl["day_id"],"----")], getTeamById[bl["team_id"]], getNiceDay[bl["day_id"]]  , bl )

fulfBlocks = set([(bl["team_id"], getRoundByDay[bl["day_id"]]) for bl in blockings if bl['type'] in [
                 "Home"] and blocked_arena[(bl["team_id"], bl["day_id"], "----")]])

blockingVioTotal2 = lpSum([-30 * homeInRound[tr]
                          for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode == 'Improve'])
blockingVioTotal = lpSum([100 * blockingVio[bl['id']]
                         for bl in blockings if bl['type'] in ["Home", "Hide"]]) + blockingVioTotal2

travelVioTotal = lpSum([100 * blockingVio[bl['id']]
                       for bl in blockings if bl['type'] == "Away"])
derbiesMissingTotal = lpSum([30 * derbyMissing[d] for d in days])
unpreferredTotal = lpSum(
    [home[t, d] for t in teams for d in days if dayHomePref[(t, d)] == 0])
# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions])
missingGamesVioTotal = lpSum(
    [2000000 * missingGamesVio[(t1, t2)] for (t1, t2) in realgames])
# TODO -  UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games])
oldScenGamesTotal = lpSum([wg * x[t1, t2, rd] for (t1, t2, r, wg)
                          in otherScenGames for rd in getRoundDaysByRound[r]])
totalAttendance = lpSum([attendance[(t1, t2, d)] * x[t1, t2, (r, d)]
                        for (t1, t2) in games for (r, d) in roundDays])

specialObjectives = 0
specialWishItems = {sw: [] for sw in special_wishes_active}
specialWishVio = {}


optCameraMovement = "Standard"

# tvkitproblem = {}
move2 = {}
newtrip2 = {}


standardObjectives = 1+gew['Home-/Away']*HawVioTotal\
    + gew['Home-/Away']*3*unpreferredTotal \
    + gew['Pairings']*pairingVioTotal \
    + gew['Blockings']*blockingVioTotal \
    + gew['Traveling']*travelVioTotal \
    + gew['Breaks']*breakVioTotal \
    + gew['Breaks']*2*break3VioTotal \
    + gew['Breaks']*1*breakImbalanceTotal \
    + 5*gew['Encounters']*encVioTotal \
    + 5*gew['Encounters']*seedVioTotal \
    + gew['Broadcasting']*broadVioTotal \
    + gew['Derbies']*derbiesMissingTotal \
    + 1.0*tooManyTop4InRowTotal\
    + missingGamesVioTotal\
    + oldScenGamesTotal\
    - 0.01*totalAttendance


model2 += standardObjectives

# %%
script = thisSeason.optimizationScript
if script == '':
    if useBasicGames:
        script += "HEURISTIC\n"
        script += "GAME;1-"+str(nRounds)+";0.1;200\n"
    if thisSeason.groupBased:
        for c in allConferences:
            if not c.regional and c.teams.count() <= 12:
                script += "GROUP;1-"+str(nRounds)+";0.05;30;"+c.name+"\n"
                for cr in range(nPhases):
                    minIntRound = min((cr) * nRoundsPerPhase+1, nRounds)
                    maxIntRound = min((cr+1)*nRoundsPerPhase, nRounds)


script = script.replace('\r', '')
print("script")
print(script)
optSteps = [st.split(';') for st in script.split("\n")]
print(optSteps)

# %%

print("Model built now solving .... ")

nRuns = 1
maxSolveTime = 300

if thisSeason.groupBased:
    maxSolveTime = 40

mipgap = 0.01

# print ("######## Testing")
# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1))


use_LP_heuristic = False
print(runMode == 'New', useBasicGames, runPatternAssignmentFirst)

nRuns = nPhases
# maxSolveTime = 120
mipgap = 0.0
# mipgap=0.95

# nRuns =1
onlyReopt = True
onlyReopt = False
onlyFewTrips = False
singleTripWeight = 10

if onlyReopt:
    nRuns = 0

maxIntRound = nRounds


missing_imp = []
cntr = 0

for st in optSteps:
    print(" - ", st)
    cntr += 1
    if runMode == 'New' and st[0] == "HARDCONSTRAINTS":
        for bl in blockings:
            blockingVio[bl['id']].upBound = 0
            print("blocking tightened : ",
                  getTeamById[bl['team_id']], bl['day_id'])
        for enc in encwishes:
            if enc['reason'] == "Seed Game":
                encVio[enc['id']].upBound = 0

    if runMode == 'New' and len(st) >= 1 and st[0] in ["PATTERNS", "HOMEAWAY", "BASICGAME", "GAME", "GAMES", "GROUP", "TRIPS", "LP-HEURISTIC"]:
        newRounds = []
        print()
        optTarget = st[0]
        if len(st) > 1:
            for rf in st[1].split(","):
                rr = rf.split("-")
                if len(rr) == 1:
                    newRounds.append(min(nRounds, int(rr[0])))
                else:
                    for ii in range(int(rr[0]), min(nRounds, int(rr[1])+1)):
                        newRounds.append(ii)
            newRoundsString = st[1]
        else:
            newRounds = rounds
            newRoundsString = "1-"+str(nRounds)

        optsteptime = maxSolveTime
        optstepgap = mipgap

        if len(st) >= 3 and st[2] != "":
            optstepgap = float(st[2])
        if len(st) >= 4 and st[3] != "":
            optsteptime = float(st[3])

        print(newRounds)

        if st[0] == "HOMEAWAY":
            for t in teams:
                for r in newRounds:
                    homeInRound[(t, r)].cat = LpInteger

        if st[0] == "BASICGAME":
            for (t1, t2) in games:
                for r in newRounds:
                    gameInBasicRound[(t1, t2, r)].cat = LpInteger

        if st[0] in ["GAME", "GAMES"]:
            for (t, t2) in games:
                for r in newRounds:
                    for rd in getRoundDaysByRound[r]:
                        makeIntVar(x[(t, t2, rd)])

        print('########################')
        print('# SOLVING MODEL '+optTarget+'  FOR ROUNDS ' + newRoundsString +
              ' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' #')
        print('########################')

#         if solver == "CBC":
#             model2.solve(PULP_CBC_CMD(fracGap=optstepgap,
#                          maxSeconds=optsteptime, threads=8, msg=1))
#         elif solver == "Gurobi":
#             model2.solve(GUROBI(MIPGap=optstepgap,
#                          TimeLimit=optsteptime, msg=1, Method=2, NodeMethod=2))
#         else:
#             # for debugging:
#             # with open ("model2.txt", "w") as f:
#             #     f.write(model2.__repr__())
#             model2.solve(XPRESS(msg=1, targetGap=optstepgap, maxSeconds=optsteptime,  options=[
#                          "THREADS=12,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True))

#         if model2.status < 0:
#             print("Status: ", model2.status)

#         if not lowerBoundFound:
#             lowerBoundFound = value(model2.objective)

#         cntr_rnd = 0

#         if st[0] == "HOMEAWAY":
#             print(teams)
#             print(newRounds)
#             for t in teams:
#                 for r in newRounds:
#                     if homeInRound[(t, r)].value() > 0.9:
#                         print('fixing home ' + str(r) + ' : ' +
#                               getTeamById[t] + "  " + str(homeInRound[(t, r)].value()))
#                         homeInRound[(t, r)].lowBound = 1
#                     else:
#                         homeInRound[(t, r)].upBound = 0

#         if st[0] == "BASICGAME":
#             for r in newRounds:
#                 for (t1, t2) in games:
#                     if getTeamById[t1] != "-" and getTeamById[t2] != "-" and (t1, t2, r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1, t2, r)]) != int:
#                         if getVal(gameInBasicRound[(t1, t2, r)]) > 0.9:
#                             gameInBasicRound[(t1, t2, r)].lowBound = 1
#                         else:
#                             gameInBasicRound[(t1, t2, r)].upBound = 0

#         if st[0] in ["GAME", "GAMES"]:
#             feedback = "Optimize games...."
#             missing_imp = []

#             for (t1, t2) in realgames:
#                 if missingGamesVio[(t1, t2)].value() > 0.9:
#                     feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str(
#                         missingGamesVio[(t1, t2)].value()) + '<br> \n'
#                     missing_imp += [(1, nRounds, [t1, t2], 50)]

#             print(missing_imp)
#             print(feedback)
#             print("number of assigned games : ", sum(
#                 [getVal(x[ttrd]) for ttrd in x.keys()]))

#             for (t1, t2) in games:
#                 for r in newRounds:
#                     for rd in getRoundDaysByRound[r]:
#                         if getVal(x[(t1, t2, rd)]) > 0.9:
#                             setLB(x[(t1, t2, rd)], 1)
#                             print("fixing ", t1, t2, rd,
#                                   x[(t1, t2, rd)].lowBound)
#                         else:
#                             setUB(x[(t1, t2, rd)], 0)

#         for r in basicRounds:
#             for t1 in realteams:
#                 homeInBasicRound[(t1, r)].lowBound = 0
#                 homeInBasicRound[(t1, r)].upBound = 10
#                 awayInBasicRound[(t1, r)].lowBound = 0
#                 awayInBasicRound[(t1, r)].upBound = 10
#                 for t2 in opponents[t1]:
#                     if (t1, t2) in games:
#                         gameInBasicRound[(t1, t2, r)].cat = LpContinuous
#                         gameInBasicRound[(t1, t2, r)].lowBound = 0

#         for t in realteams:
#             for r in newRounds:
#                 homeInRound[(t, r)].cat = LpContinuous
#                 for t2 in opponents[t]:
#                     for rd in getRoundDaysByRound[r]:
#                         if (t, t2) in games and getVal(x[(t, t2, rd)]) > 0.9:
#                             setLB(x[(t, t2, rd)], x[(t, t2, rd)].value())
#         currentSolution = [(t1, t2, r, d) for (t1, t2) in realgames for (
#             r, d) in roundDays if getVal(x[(t1, t2, (r, d))]) > 0.9]


# for r in rounds:
#     for t in teams:
#         homeInRound[(t, r)].lowBound = 0
#         homeInRound[(t, r)].upBound = 1

# for (t1, t2) in games:
#     for r in rounds:
#         for rd in getRoundDaysByRound[r]:
#             makeIntVar(x[(t1, t2, rd)])

# print('Solved Again')
# print('NOW REOPT')

# # %%

# %%
