import random

from mittab.libs import tab_logic, mwmatching, errors
from mittab.apps.tab.models import *


def add_judges(pairings, judges, panel_points):
    # First clear any existing judge assignments
    for pairing in pairings:
        pairing.judges.clear()

    current_round_number = TabSettings.objects.get(key="cur_round").value - 1

    # Try to have consistent ordering with the round display
    random.seed(1337)
    random.shuffle(pairings)
    random.seed(1337)
    random.shuffle(judges)

    # Order the judges and pairings by power ranking
    judges = sorted(judges, key=lambda j: j.rank, reverse=True)
    pairings.sort(key=lambda x: tab_logic.team_comp(x, current_round_number),
                  reverse=True)

    pairing_groups = [list() for panel_point in panel_points] + [list()]
    panel_gaps = {}
    current_group = 0
    for pairing in pairings:
        pairing_groups[current_group].append(pairing)
        if current_group < len(
                panel_points) and pairing == panel_points[current_group][0]:
            panel_gaps[current_group] = panel_points[current_group][1]
            current_group += 1

    for (group_i, group) in enumerate(pairing_groups):
        num_rounds = len(group)
        # Assign chairs (single judges) to each round using perfect pairing
        graph_edges = []
        for (judge_i, judge) in enumerate(judges):
            for (pairing_i, pairing) in enumerate(group):
                if not judge_conflict(judge, pairing.gov_team,
                                      pairing.opp_team):
                    graph_edges.append((pairing_i, judge_i + len(group),
                                        calc_weight(judge_i, pairing_i)))
        judge_assignments = mwmatching.maxWeightMatching(graph_edges,
                                                         maxcardinality=True)
        # If there is no possible assignment of chairs, raise an error
        if -1 in judge_assignments[:num_rounds] or (num_rounds > 0
                                                    and not graph_edges):
            if not graph_edges:
                raise errors.JudgeAssignmentError(
                    "Impossible to assign judges, consider reducing your gaps if you"
                    " are making panels, otherwise find some more judges.")
            elif -1 in judge_assignments[:num_rounds]:
                pairing_list = judge_assignments[:len(pairings)]
                bad_pairing = pairings[pairing_list.index(-1)]
                raise errors.JudgeAssignmentError(
                    "Could not find a judge for: %s" % str(bad_pairing))
            else:
                raise errors.JudgeAssignmentError()

        # Save the judges to the pairings
        for i in range(num_rounds):
            group[i].judges.add(judges[judge_assignments[i] - num_rounds])
            group[i].chair = judges[judge_assignments[i] - num_rounds]
            group[i].save()

        # Remove any assigned judges from the judging pool
        for pairing in group:
            for judge in pairing.judges.all():
                judges.remove(judge)

        # Function that tries to panel num_to_panel rounds of the potential_pairings
        # Has built in logic to retry with lower number of panels if we fail due
        # to either scratches or wanting to many rounds
        def try_paneling(potential_pairings, all_judges, num_to_panel, gap):
            if not potential_pairings or num_to_panel <= 0:
                # Base case, failed to panel
                print("Failed to panel")
                return {}

            def sort_key(round_obj):
                team_comp_result = tab_logic.team_comp(round_obj,
                                                       current_round_number)
                get_rank = lambda judge: judge.rank
                rank_tuple = (argmin(round_obj.judges.all(), get_rank).rank, )
                return rank_tuple + tuple([-1 * i for i in team_comp_result])

            rounds = sorted(potential_pairings, key=sort_key)
            base_judge = argmax(
                rounds[:num_to_panel][-1].judges.all(), lambda j: j.rank)
            print("Found maximally ranked judge {0}".format(base_judge))
            potential_panelists = [
                j for j in all_judges
                if j.rank > (float(base_judge.rank) - float(gap))
            ]
            print("Potential panelists:", potential_panelists)
            # If we don't have enough potential panelists, try again with fewer panels
            if len(potential_panelists) < 2 * num_to_panel: