'''
    Season Simulation
    
    2017 Jacob Grishey

    For the purpose of simulating sports seasons
    and determining regular season standings.
'''

# IMPORTS

import json
import statistics
import numpy
from operator import itemgetter
import copy 

# Read JSON file (schedule, team list)

with open("./../data/season.json") as jsonfile:
    data = json.load(jsonfile)

# Parse JSON file

teams = {team: {'w': 0, 'l': 0, 'otl': 0, 'elo': 1500} for team in data['teams']}

schedule = data['schedule']

# Results

results = [{'name': team, 'seed1': 0, 'seed2': 0, 'seed3': 0, 'seed4': 0,
             'seed5': 0, 'seed6': 0, 'seed7': 0, 'seed8': 0, 'aw': 0,
              'al': 0, 'aotl': 0, 'r2': 0, 'r3': 0, 'cup': 0} for team in teams]

# Divisions

brent = ["Cape Cod Bluefins", "Trevor Phillips Industries",
        "Inglorious Basterds", "Crack Smoking Monkeys",
        "Moose Knuckles", "Hood Rich"]
drew = ["Small Sample Size", "The Bearded Bandits",
        "Row Row Fight the Powah", "Motor City Machine Guns",
        "Suck Our Dekes", "Tenacious V"]

# Separate past from future games

past = [game for game in schedule if game['h-score'] != -1]

future = [game for game in schedule if game['h-score'] == -1]

# Update teams with past results

for game in past:
    if game['h-score'] > game['a-score']:
        if game['ot'] == 1:
            teams[game['home']]['w'] += 1
            teams[game['away']]['otl'] += 1
        else:
            teams[game['home']]['w'] += 1
            teams[game['away']]['l'] += 1
    else:
        if game['ot'] == 1:
            teams[game['away']]['w'] += 1
            teams[game['home']]['otl'] += 1
        else:
            teams[game['away']]['w'] += 1
            teams[game['home']]['l'] += 1

# Expected Score function
#
# Given elo of team A and team B, calculate expected score of team A.

def expectedScoreA (eloA, eloB):
    return 1 / (1 + 10 ** ((eloB - eloA) / 400))

# New Rating Function
#
# Given Elo, actual score, expected score, and goal differential and calculate the team's new Elo rating.

def newRating (eloA, eloB, scoreActual, scoreExpected, goalDifferential, win):
    # K-Factor
    if eloA < 2100:
        K = 32
    elif eloA <= 2400:
        K = 24
    else:
        K = 16
    
    # Calculate for goal differential and autocorrelation
    marginMult = numpy.log(goalDifferential + 1) * (2.2 / (abs(eloA - eloB) * 0.01 + 2.2))

    # Return new rating
    return eloA + (marginMult * K) * (scoreActual - scoreExpected)

# Update elo from past games

for game in past:
    # Current Elo ratings
    currentEloA = teams[game['home']]['elo']
    currentEloB = teams[game['away']]['elo']

    # Get Expected Scores
    eA = expectedScoreA(currentEloA, currentEloB)
    eB = 1 - eA

    # Get scores
    homeGoals = game['h-score']
    awayGoals = game['a-score']
    goalDifferential = abs(homeGoals - awayGoals)

    # Get Actual Scores
    if homeGoals > awayGoals:
        if game['ot'] == 1:
            sA = 1.0
            sB = 0.5
            winA = True
            winB = False
        else:
            sA = 1.0
            sB = 0.0
            winA = False
            winB = True
    else:
        if game['ot'] == 1:
            sB = 1.0
            sA = 0.5
            winA = True
            winB = False
        else:
            sB = 1.0
            sA = 0.0
            winA = False
            winB = True

    
    # Calculate new Elo ratings
    newA = newRating(currentEloA, currentEloB, sA, eA, goalDifferential, winA)
    newB = newRating(currentEloB, currentEloA, sB, eB, goalDifferential, winB)

    # Apply Elo ratings
    teams[game['home']]['elo'] = newA
    teams[game['away']]['elo'] = newB

# Simulation

def runSeason (tempTeams):

    for game in future:
        # Current Elo ratings
        currentEloA = tempTeams[game['home']]['elo']
        currentEloB = tempTeams[game['away']]['elo']

        # Get Expected Scores
        eA = expectedScoreA(currentEloA, currentEloB)
        eB = 1 - eA

        # Random number between 0 and 1 to decide who wins.
        decideWin = numpy.random.random()

        # Random number between 0 and 1 to decide if it goes into Overtime.
        decideOT = numpy.random.random()
        
        # Actual Predicted Scores
        if decideOT <= 0.233:
            if decideWin <= eA:
                sA = 1.0
                tempTeams[game['home']]['w'] += 1
                sB = 0.5
                tempTeams[game['away']]['otl'] += 1
            else:
                sA = 0.5
                tempTeams[game['home']]['otl'] += 1
                sB = 1.0
                tempTeams[game['away']]['w'] += 1
        else:
            if decideWin <= eA:
                sA = 1.0
                tempTeams[game['home']]['w'] += 1
                sB = 0.0
                tempTeams[game['away']]['l'] += 1
            else:
                sA = 0.0
                tempTeams[game['home']]['l'] += 1
                sB = 1.0
                tempTeams[game['away']]['w'] += 1
        
        # Calculate new Elo ratings
        #newA = newRating(currentEloA, sA, eA)
        #newB = newRating(currentEloB, sB, eB)

        # Apply new Elo ratings
        #tempTeams[game['home']]['elo'] = newA
        #tempTeams[game['away']]['elo'] = newB
    
    # End of Season standings
    brentStandings = []
    drewStandings = []

    # Collect teams, calculate points.
    for team in tempTeams:
        if team in brent:
            brentStandings.append({"name": team, "pts": tempTeams[team]['w'] * 2 + tempTeams[team]['otl']})
            next(item for item in results if item["name"] == team)['aw'] += tempTeams[team]['w']
            next(item for item in results if item["name"] == team)['al'] += tempTeams[team]['l']
            next(item for item in results if item["name"] == team)['aotl'] += tempTeams[team]['otl']
        else:
            drewStandings.append({"name": team, "pts": tempTeams[team]['w'] * 2 + tempTeams[team]['otl']})
            next(item for item in results if item["name"] == team)['aw'] += tempTeams[team]['w']
            next(item for item in results if item["name"] == team)['al'] += tempTeams[team]['l']
            next(item for item in results if item["name"] == team)['aotl'] += tempTeams[team]['otl']
    
    # Sort by points
    brentStandings = sorted(brentStandings, key=itemgetter('pts'), reverse=True)
    drewStandings = sorted(drewStandings, key=itemgetter('pts'), reverse=True)

    # Cut off top 2, then concat and sort by points
    overall8 = sorted(brentStandings[2:] + drewStandings[2:], key=itemgetter('pts'), reverse=True)

    # Playoff Seeding
    playoffs = [{"seed": seed, "name": ""} for seed in range(1,9)]

    # Get playoff teams
    playoffTeams = sorted(brentStandings[:2] + drewStandings[:2] + overall8[:4],
                            key=itemgetter('pts'), reverse=True)
    
    # Add Results
    next(item for item in results if item["name"] == playoffTeams[0]['name'])['seed1'] += 1
    next(item for item in results if item["name"] == playoffTeams[1]['name'])['seed2'] += 1
    next(item for item in results if item["name"] == playoffTeams[2]['name'])['seed3'] += 1
    next(item for item in results if item["name"] == playoffTeams[3]['name'])['seed4'] += 1
    next(item for item in results if item["name"] == playoffTeams[4]['name'])['seed5'] += 1
    next(item for item in results if item["name"] == playoffTeams[5]['name'])['seed6'] += 1
    next(item for item in results if item["name"] == playoffTeams[6]['name'])['seed7'] += 1
    next(item for item in results if item["name"] == playoffTeams[7]['name'])['seed8'] += 1

    # Insert into seeds
    for (team, i) in zip(playoffTeams, range(0, 8)):
        playoffs[i]['name'] = team['name']
    
    # Schedule first round games
    firstRoundGames = []
    firstRoundSeries = []

    for i in range(0, 4):
        firstRoundSeries.append({
            'home': playoffs[i]['name'],
            'away': playoffs[7-i]['name'],
            'h-seed': playoffs[i]['seed'],
            'a-seed': playoffs[7-i]['seed'],
            'h-wins': 0,
            'a-wins': 0
        })
        for k in range(0, 4):
            firstRoundGames.append({
                'home': playoffs[i]['name'],
                'away': playoffs[7-i]['name']
            })
    
    # Simulate first round
    for game in firstRoundGames:
        # Current Elo ratings of both teams
        homeElo = tempTeams[game['home']]['elo']
        awayElo = tempTeams[game['away']]['elo']

        # Win probabilities
        eA = expectedScoreA(homeElo, awayElo)
        eB = 1 - eA

        # Decide win and OT
        decideWin = numpy.random.random()
        decideOT = numpy.random.random()

        # Get series data
        series = next(item for item in firstRoundSeries if item['home'] == game['home'])

        # For scheduling purposes
        previousLow = min([series['h-wins'], series['a-wins']])

        # Simulate game
        if decideOT <= 0.233:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    firstRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.5
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    firstRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.5
                sB = 1.0
        else:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    firstRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.0
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    firstRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.0
                sB = 1.0

        # Calculate new Elo ratings
        #newA = newRating(homeElo, sA, eA)
        #newB = newRating(awayElo, sB, eB)

        # Apply new Elo ratings
        #tempTeams[game['home']]['elo'] = newA
        #tempTeams[game['away']]['elo'] = newB
    
    # Collect series winners.
    secondRoundTeams = []
    for series in firstRoundSeries:
        if series['h-wins'] == 4:
            secondRoundTeams.append({'seed': series['h-seed'], 'name': series['home']})
            next(item for item in results if item['name'] == series['home'])['r2'] += 1
        else:
            secondRoundTeams.append({'seed': series['a-seed'], 'name': series['away']})
            next(item for item in results if item['name'] == series['away'])['r2'] += 1
    
    secondRoundTeams = sorted(secondRoundTeams, key=itemgetter('seed'))

    # Schedule second round games
    secondRoundGames = []
    secondRoundSeries = []

    for i in range(0, 2):
        secondRoundSeries.append({
            'home': secondRoundTeams[i]['name'],
            'away': secondRoundTeams[3-i]['name'],
            'h-seed': secondRoundTeams[i]['seed'],
            'a-seed': secondRoundTeams[3-i]['seed'],
            'h-wins': 0,
            'a-wins': 0
        })
        for k in range(0, 4):
            secondRoundGames.append({
                'home': secondRoundTeams[i]['name'],
                'away': secondRoundTeams[3-i]['name']
            })
    
    # Simulate second round
    for game in secondRoundGames:
        # Current Elo ratings of both teams
        homeElo = tempTeams[game['home']]['elo']
        awayElo = tempTeams[game['away']]['elo']

        # Win probabilities
        eA = expectedScoreA(homeElo, awayElo)
        eB = 1 - eA

        # Decide win and OT
        decideWin = numpy.random.random()
        decideOT = numpy.random.random()

        # Get series data
        series = next(item for item in secondRoundSeries if item['home'] == game['home'])

        # For scheduling purposes
        previousLow = min([series['h-wins'], series['a-wins']])

        # Simulate game
        if decideOT <= 0.233:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    secondRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.5
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    secondRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.5
                sB = 1.0
        else:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    secondRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.0
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    secondRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.0
                sB = 1.0

        # Calculate new Elo ratings
        #newA = newRating(homeElo, sA, eA)
        #newB = newRating(awayElo, sB, eB)

        # Apply new Elo ratings
        #tempTeams[game['home']]['elo'] = newA
        #tempTeams[game['away']]['elo'] = newB
    
    # Collect series winners.
    thirdRoundTeams = []
    for series in secondRoundSeries:
        if series['h-wins'] == 4:
            thirdRoundTeams.append({'seed': series['h-seed'], 'name': series['home']})
            next(item for item in results if item['name'] == series['home'])['r3'] += 1
        else:
            thirdRoundTeams.append({'seed': series['a-seed'], 'name': series['away']})
            next(item for item in results if item['name'] == series['away'])['r3'] += 1
    
    thirdRoundTeams = sorted(thirdRoundTeams, key=itemgetter('seed'))

    # Schedule second round games
    thirdRoundGames = []
    thirdRoundSeries = []

    for i in range(0, 1):
        thirdRoundSeries.append({
            'home': thirdRoundTeams[i]['name'],
            'away': thirdRoundTeams[1-i]['name'],
            'h-seed': thirdRoundTeams[i]['seed'],
            'a-seed': thirdRoundTeams[1-i]['seed'],
            'h-wins': 0,
            'a-wins': 0
        })
        for k in range(0, 4):
            thirdRoundGames.append({
                'home': thirdRoundTeams[i]['name'],
                'away': thirdRoundTeams[1-i]['name']
            })

    # Simulate third round
    for game in thirdRoundGames:
        # Current Elo ratings of both teams
        homeElo = tempTeams[game['home']]['elo']
        awayElo = tempTeams[game['away']]['elo']

        # Win probabilities
        eA = expectedScoreA(homeElo, awayElo)
        eB = 1 - eA

        # Decide win and OT
        decideWin = numpy.random.random()
        decideOT = numpy.random.random()

        # Get series data
        series = next(item for item in thirdRoundSeries if item['home'] == game['home'])

        # For scheduling purposes
        previousLow = min([series['h-wins'], series['a-wins']])

        # Simulate game
        if decideOT <= 0.233:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    thirdRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.5
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    thirdRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.5
                sB = 1.0
        else:
            if decideWin <= eA:
                series['h-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    thirdRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 1.0
                sB = 0.0
            else:
                series['a-wins'] += 1
                if min([series['h-wins'], series['a-wins']]) > previousLow:
                    thirdRoundGames.append({
                        'home': game['home'],
                        'away': game['away']
                    })
                sA = 0.0
                sB = 1.0

        # Calculate new Elo ratings
        #newA = newRating(homeElo, sA, eA)
        #newB = newRating(awayElo, sB, eB)

        # Apply new Elo ratings
        #tempTeams[game['home']]['elo'] = newA
        #tempTeams[game['away']]['elo'] = newB
    
    # Collect series winners.
    cupWinner = []
    for series in thirdRoundSeries:
        if series['h-wins'] == 4:
            cupWinner.append({'seed': series['h-seed'], 'name': series['home']})
            next(item for item in results if item['name'] == series['home'])['cup'] += 1
        else:
            cupWinner.append({'seed': series['a-seed'], 'name': series['away']})
            next(item for item in results if item['name'] == series['away'])['cup'] += 1

# Run simulation 100,000 times.

for i in range(0, 100000):
    runSeason(copy.deepcopy(teams))

# Calculate average season.

for team in results:
    team['aw'] /= 100000
    team['al'] /= 100000
    team['aotl'] /= 100000

# Add division info to each team.

for team in teams:
    if team in brent:
        teams[team]['division'] = "Brent"
    else:
        teams[team]['division'] = "Drew"
    next(item for item in results if item["name"] == team)['w'] = teams[team]['w']
    next(item for item in results if item["name"] == team)['l'] = teams[team]['l']
    next(item for item in results if item["name"] == team)['otl'] = teams[team]['otl']
    next(item for item in results if item["name"] == team)['elo'] = teams[team]['elo']
    next(item for item in results if item["name"] == team)['division'] = teams[team]['division']

# Write results to outfile.

with open('./../data/results.json', 'w') as outfile:
    json.dump(results, outfile, indent=4)