from __future__ import division
import random
from collections import Counter, namedtuple
import bisect
import math

# for my own purposes.
debug = False

def weighted_choice(choices):
    values, weights = zip(*choices)
    total = 0
    cum_weights = []
    for w in weights:
        total += w
        cum_weights.append(total)
    x = random.random() * total
    i = bisect.bisect(cum_weights, x)
    return values[i]

def debug_print(*args, **kwargs):
    if debug:
        print(*args, **kwargs)

maps = [
    'Urchin Underpass',
    'Walleye Warehouse',
    'Saltspray Rig',
    'Blackbelly Skatepark',
    'Arowana Mall',
    'Port Mackerel',
    'Kelp Dome',
    'Bluefin Depot',
    'Moray Towers',
    'Camp Triggerfish',
    'Flounder Heights',
    'Hammerhead Bridge',
    "Museum d'Alfonsino",
    'Mahi-Mahi Resort',
    'Piranha Pit',
    'Ancho-V Games'
]

# probability for each is the second parameter
# e.g. 40 -> 40%
# modes = [('Splat Zones', 45), ('Rainmaker', 10), ('Tower Control', 45)]
modes = ['Splat Zones', 'Rainmaker', 'Tower Control']

Game = namedtuple('Game', ['map', 'mode'])

# the total number of rounds being played -- including Losers for DE and GF2
rounds = 7

# the number of games per round
games_per_round = 9

# the total number of times a game/mode combo can be played overall in the tournament
overall_count = math.ceil(math.log(rounds, 2))

# number of times RM can show up
maximum_rainmaker = (games_per_round / 2) - 1

# for output purposes, note if it's a double elimination tournament.
is_double_elimination = False

assert games_per_round % 2 != 0

# the last game in a decisive victory set, e.g. Bo5 -> 3, Bo7 -> 4
best_of_game = math.ceil(games_per_round / 2.0)

def can_be_added_to_pool(game, overall_pool, pool, previous_pool):
    if overall_pool.count(game) == overall_count:
        debug_print('overall_pool -> False')
        return False

    counter = Counter((entry.mode for entry in pool))
    has_one_of_each_mode = all(counter[mode] for mode in modes)

    if len(pool) < best_of_game:
        if not has_one_of_each_mode and counter[game.mode] > 0:
            debug_print('every best_of_game needs a unique mode')
            return False

    if counter[game.mode] > (maximum_rainmaker - 1) and game.mode == 'Rainmaker':
        debug_print('too much rainmaker')
        return False

    # map/mode from previous round cannot be played
    if game in previous_pool:
        debug_print('duplicate from other game')
        return False

    # the previous mode cannot be played
    if len(pool) and game.mode == pool[-1].mode:
        debug_print('previous mode')
        return False

    if counter[game.mode] == best_of_game - 1:
        debug_print('too much of this mode')
        return False

    for entry in pool:
        # no duplicate maps
        if entry.map == game.map:
            debug_print('duplicate map')
            return False

    # well considering we're back here, let's add it
    debug_print('perfectly fine')
    return True

def pool_to_string(pool):
    result = []
    for i, game in enumerate(pool):
        result.append('Game {0}: {1.mode} on {1.map}'.format(i + 1, game))
    return '\n'.join(result)

# so we say we need 5 random picks from the permutations above..
pool = []
previous_pool = []
overall_pool = []
for r in range(rounds):
    # make sure it's a unique choice
    while len(pool) < games_per_round:
        stage = random.choice(maps)
        # mode = weighted_choice(modes)
        mode = random.choice(modes)
        game = Game(map=stage, mode=mode)
        debug_print('Round {}: {} (pool: {})'.format(r + 1, game, pool))
        if can_be_added_to_pool(game, overall_pool, pool, previous_pool):
            pool.append(game)
            overall_pool.append(game)
            # print('valid')

    output = pool_to_string(pool)

    if is_double_elimination:
        number = r + 1

        # rounds is GF2, and rounds // 2 - 1 is GF1, etc.
        gf1 = (rounds // 2) - 1
        losers_finals = rounds - 1
        losers_semi = rounds - 2
        winners_finals = gf1 - 1
        winners_semis = gf1 - 2

        if number < gf1:
            print("Winner's Round {} has:\n{}\n".format(number, output))
        elif number == gf1:
            print("Winner's Grand Finals has:\n{}\n".format(output))
        elif number == winners_finals:
            print("Winner's Finals has:\n{}\n".format(output))
        elif number == winners_semis:
            print("Winner's Semifinals has:\n{}\n".format(output))
        elif number == losers_finals:
            print("Loser's Finals has:\n{}\n".format(output))
        elif number == losers_semi:
            print("Loser's Semifinals has:\n{}\n".format(output))
        elif number == rounds:
            print("Winner's Grand Finals 2 has:\n{}\n".format(output))
        else:
            print("Loser's Round {} has:\n{}\n".format(number - gf1, output))

    else:
        print('Round {} has:\n{}\n'.format(r + 1, output))
    previous_pool = list(pool)
    pool = []

print('Sanity Check:')
overall = Counter(overall_pool)
modes = Counter(map(lambda x: x[1], overall_pool))
maps = Counter(map(lambda x: x[0], overall_pool))
print(modes)
print(maps)