import json
import sys
import os
import re


tree_strategies = [
    "orthodox",
    "semisplit",
    "rollup"]

split_strategies = [
    "Mod", "",
    "ModPlus", "Plus",
    "ModPlusShift", "PlusShift",
    "ModPlusShiftStar", "PlusShiftStar",
    "ModShift", "Shift"]


# general
general = {
    "buffer_time" : 100,
    "simulate_during_opp_turn" : False,
    "reasoning_overhead" : 1
}

# algorithm
exploration_constant = 0.4
rave_exploration_constant = 0.2
max_semidepth = 50
mastmix_threshold = 7
ravemix_threshold = 7

# heuristics
mast_config = {
    "epsilon" : 0.4,
    "decay_factor" : 0.2,
    "tree_only" : False
}

rave_config = {
    "equivalence_parameter": 250,
    "ref": 0
}

##########

makefile_rules = dict()

def is_valid_agent(tree, simulation, split_strategy, mast, rave):
    # orthodox tree + ravecontext/ravemix
    if tree == "orthodox" and rave in ["RAVECONTEXT", "RAVEMIX"]:
        return False
    # orthodox simulation + mastcontext/mastmix
    if simulation == "orthodox" and mast in ["MASTCONTEXT", "MASTMIX"]:
        return False
    # orthodox_semisplit / semisplit_orthodox / rollup_orthodox / rollup_semisplit + mast / rave
    if tree != simulation and (mast == "MAST" or rave == "TGRAVE"):
        if tree != "orthodox" or rave != "TGRAVE" or mast == "MAST":
            return False
    # semisplit_semisplit Mod + mast
    if tree == simulation == "semisplit" and mast == "MAST" and "Mod" in split_strategy:
        return False
    # ravemix + not Mod semisplit tree
    if rave == "RAVEMIX" and not (tree == "semisplit" and "Mod" in split_strategy):
        return False
    return True

def replace_many(string, trans):
    for key, val in trans.items():
        string = string.replace(key, val)
    return string

def shorten_split_strategy(split_strategy):
    if split_strategy is None:
        return ""
    if split_strategy == "":
        return "N"
    return split_strategy.replace("Mod", "M").replace("Plus", "P").replace("Shift", "S").replace("Star", "T")

def shorten_tree_strategy(strategy, is_nodal):
    strategy = strategy[0].upper()
    if is_nodal is not None:
        return strategy + ("n" if is_nodal else "r")
    return strategy

def shorten_simulation_strategy(strategy):
    return strategy[0].upper()

def shorten_heuristic(heuristic, threshold):
    if len(heuristic) <= 4:
        return "" if len(heuristic) == 0 else heuristic[0]
    heuristic = heuristic[0:1] + heuristic[4].lower()
    if heuristic[1] == "m":
        return heuristic + threshold
    return heuristic

def get_file_name(tree, simulation, is_nodal, split_strategy, mast, rave):
    if rave == "TGRAVE":
        rave = "RAVE"
    if tree == "semisplit" and rave == "RAVE" and "Mod" in split_strategy:
        rave = "RAVEs"

    if tree == simulation == "orthodox":
        if mast == "MAST":
            mast = "MASTj"
    if tree == "orthodox":
        if rave == "RAVE":
            rave = "RAVEj"

    mast = shorten_heuristic(mast, str(mastmix_threshold))
    rave = shorten_heuristic(rave, str(ravemix_threshold))

    short_split_strategy = shorten_split_strategy(split_strategy)
    tree = shorten_tree_strategy(tree, is_nodal)
    simulation = shorten_simulation_strategy(simulation)

    basename = '_'.join([tree + rave.lower().capitalize(), simulation + mast.lower().capitalize()]).replace("__", "_").strip("_")
    return "mcts" + short_split_strategy + "_" + basename + ".json"

def generate_makefile_rule(tree, simulation, mast, rave):
    rule_name = "_".join([tree.upper(), simulation.upper(), mast.upper(), rave.upper()]).replace("__", "_").strip("_")
    if mast == "MAST":
        mast = "MAST_ORTHODOX"
    elif mast:
        mast = "MAST_" + mast[4:]
    if rave == "TGRAVE":
        rave = "TGRAVE_ORTHODOX"
    elif rave:
        rave = "RAVE_" + rave[4:]

    rules = [tree.upper() + "_MCTS", simulation.upper() + "_SIMULATOR"]
    if mast:
        rules.append(mast)
    else:
        rules.append("UNIFORM_CHOOSER")
    if rave:
        rules.append(rave)
    rules.extend(["COMMON", "GEN_DIR"])
    if rule_name in makefile_rules:
        assert(makefile_rules[rule_name] == rules)
    else:
        makefile_rules[rule_name] = rules
    # special case: semisplit tree + RAVE, as Mod strategy requires rules different than the others
    if tree == "semisplit" and rave == "TGRAVE_ORTHODOX":
        split_rule_name = rule_name.replace("TGRAVE", "RAVESPLIT")
        split_rules = ["TGRAVE_SPLIT" if r == "TGRAVE_ORTHODOX" else r for r in rules]
        if split_rule_name in makefile_rules:
            assert(makefile_rules[split_rule_name] == split_rules)
        else:
            makefile_rules[split_rule_name] = split_rules

def generate_config_file(agent_config, file_name):
    with open(file_name, "w") as config_file:
        json.dump(agent_config, config_file, indent=4)

def get_heuristics_config_list(mast, rave):
    heuristics = []
    if mast != "":
        heuristic_config = {
            "name" : mast,
            "parameters" : mast_config.copy()
        }
        if mast == "MASTMIX":
            heuristic_config["parameters"]["mastmix_threshold"] = mastmix_threshold
        heuristics.append(heuristic_config)
    if rave != "":
        heuristic_config = {
            "name" : rave,
            "parameters" : rave_config.copy()
        }
        if rave == "RAVEMIX":
            heuristic_config["parameters"]["ravemix_threshold"] = ravemix_threshold
        heuristics.append(heuristic_config)
    return heuristics

def generate_agent(tree, simulation, is_nodal, split_strategy, mast, rave):
    ec = exploration_constant if rave == "" else rave_exploration_constant
    algorithm = {
        "name" : "MCTS",
        "tree_strategy" : tree,
        "simulation_strategy" : simulation,
        "parameters" : {
            "exploration_constant": ec
        }
    }
    if tree in ["semisplit", "rollup"] or simulation in ["semisplit"]:
        assert(split_strategy is not None)
        algorithm["split_strategy"] = split_strategy
        algorithm["parameters"]["max_semidepth"] = max_semidepth
    if tree in ["semisplit", "rollup"]:
        assert(is_nodal is not None)
        algorithm["parameters"]["is_nodal"] = is_nodal
    if tree == "semisplit":
        algorithm["parameters"]["greedy_choice"] = True
    if tree == "rollup":
        algorithm["parameters"]["min_simulations_factor"] = 1.0
        algorithm["parameters"]["min_score_diff"] = 0.0
    heuristics = get_heuristics_config_list(mast, rave)
    agent_config = {
        "general" : general,
        "algorithm" : algorithm,
        "heuristics" : heuristics
    }
    file_name = get_file_name(tree, simulation, is_nodal, split_strategy, mast, rave)
    generate_config_file(agent_config, file_name)
    generate_makefile_rule(tree, simulation, mast, rave)

##########

main_dir = os.path.realpath(sys.path[0] + "/..")
os.chdir(main_dir)
agents_dir = "agents"

if not os.path.isdir(agents_dir):
    os.mkdir(agents_dir)

os.chdir(agents_dir)

for tree in tree_strategies:
    for simulation in ["orthodox", "semisplit"]:
        for mast in ["", "MAST", "MASTSPLIT", "MASTCONTEXT", "MASTMIX"]:
            for rave in ["", "TGRAVE", "RAVECONTEXT", "RAVEMIX"]:
                if tree == "orthodox" and simulation == "orthodox":
                    if is_valid_agent("orthodox", "orthodox", None, mast, rave):
                        generate_agent("orthodox", "orthodox", None, None, mast, rave)
                else:
                    for split_strategy in split_strategies:
                        if is_valid_agent(tree, simulation, split_strategy, mast, rave):
                            if tree == "orthodox":
                                generate_agent(tree, simulation, None, split_strategy, mast, rave)
                            else:
                                generate_agent(tree, simulation, True, split_strategy, mast, rave)
                                generate_agent(tree, simulation, False, split_strategy, mast, rave)


os.chdir(main_dir)
makefile = os.path.realpath("mcts_agent/Makefile")

lines = []
with open(makefile, "r") as file:
    lines = file.readlines()

with open(makefile, "w") as file:
    reg = re.compile("(\$\(eval \$\(call PLAYER_KIND_RULES\,[A-Z_]+\,)([a-z_]+)")
    for line in lines:
        if reg.search(line):
            break
        else:
            file.write(line)
    for name, rules in makefile_rules.items():
        file.write("$(eval $(call PLAYER_KIND_RULES,{},{},\\\n\t\t\t{}))\n\n".format(
            name,
            name.lower(),
            ' '.join(map(lambda x : '$(' + x + ')', rules))
        ))
