from evoplotter import utils
from evoplotter import plotter
from evoplotter import printer
from evoplotter import reporting
from evoplotter.dims import *
from gecco19_utils import *



def simplify_benchmark_name(name):
    """Shortens or modifies the path of the benchmark in order to make the table more readable."""
    i = name.rfind("/")
    name = name if i == -1 else name[i+1:]
    name = name[:name.rfind(".")]
    return name.replace("_3", "_03").replace("_5", "_05").replace("resistance_par", "res").replace("gravity", "gr")


def benchmark_get_num_tests(name):
    i_dot = name.rfind(".")
    i_us = name.rfind("_")
    return name[i_us+1:i_dot]


def benchmark_shorter(name):
    name = simplify_benchmark_name(name)
    i_us = name.rfind("_")
    x = name[:i_us]
    return x


def sort_benchmark_dim(d):
    assert isinstance(d, Dim)
    def s(config1):
        name1 = config1[0][0] # name of the first filter
        name1 = name1[name1.rfind("/")+1:]
        # name1 = name1[:name1.rfind(".")]
        bfamily1 = name1[:name1.find("_")]
        if name1.count('_') == 2:
            b_constr1 = name1[name1.find("_")+1:name1.rfind("_")]
        elif name1.count('_') == 1:
            b_constr1 = name1[name1.find("_")+1:]
        else:
            raise Exception("Unexpected number of '_' in benchmark name.")

        constr_precedence = ["", "b", "c1", "c2", "m", "s", "c", "bm", "bs", "ms", "ms", "bms", "sc"]
        benchValues = {"gravity":100, "gr":100, "res2":200, "res3":300}

        value = benchValues.get(bfamily1, 0)
        return value + constr_precedence.index(b_constr1)

    return Dim(sorted(d.configs, key=s))


def get_benchmarks_from_props(props, simple_names=True, ignoreNumTests=False):
    if ignoreNumTests:
        dim_benchmarks = Dim.from_dict_postprocess(props, "benchmark", fun=benchmark_shorter)
    else:
        dim_benchmarks = Dim.from_dict(props, "benchmark")
        if simple_names:
            configs = [Config(simplify_benchmark_name(c.get_caption()), c.filters[0][1],
                              benchmark=c.get_caption()) for c in dim_benchmarks.configs]
            dim_benchmarks = Dim(configs)
            # dim_benchmarks.sort()
    return sort_benchmark_dim(dim_benchmarks)




def p_method_for(name):
    return lambda p, name=name: p["method"] == name
def p_matches_dict(p, d):
    for k, v in d.items():
        if p[k] != v:
            return False
    return True
def p_dict_matcher(d):
    assert isinstance(d, dict)
    d = d.copy()
    return lambda p, d=d: p_matches_dict(p, d)
def p_generational(p):
    return p["evolutionMode"] == "generational"
def p_steadyState(p):
    return p["evolutionMode"] == "steadyState"
def p_sel_lexicase(p):
    return p["selection"] == "lexicase"
def p_sel_tourn(p):
    return p["selection"] == "tournament"
def p_testsRatio_equalTo(ratio):
    return lambda p, ratio=ratio: p["testsRatio"] == ratio
def p_benchmarkNumTests_equalTo(x):
    return lambda p, x=x: benchmark_get_num_tests(p["benchmark"]) == x
def p_true(p):
    return True



dim_true = Dim(Config("mean", lambda p: True, method=None))
# dim_methodCDGP = Dim([Config("CDGP", p_method_for("CDGP"), method="CDGP")])
# dim_methodGP = Dim([Config("GP", p_method_for("GP"), method="GP")])
dim_methodCDGP = Dim([
    Config("CDGP", p_dict_matcher({"method": "CDGP", "partialConstraintsInFitness": "false"}), method="CDGP"),
    Config("$CDGP_{props}$", p_dict_matcher({"method": "CDGP", "partialConstraintsInFitness": "true"}), method="CDGPprops"),
])
dim_methodGP = Dim([
    Config("$GP$", p_dict_matcher({"method": "GP", "populationSize": "500"}), method="GP500"),
    # Config("$GP_{1000}$", p_dict_matcher({"method": "GP", "populationSize": "1000"}), method="GP1000"),
    # Config("$GP_{5000}$", p_dict_matcher({"method": "GP", "populationSize": "5000"}), method="GP5000"),
])
dim_method = dim_methodCDGP + dim_methodGP
dim_sel = Dim([#Config("$Tour$", p_sel_tourn, selection="tournament"),
               Config("$Lex$", p_sel_lexicase, selection="lexicase")])
# dim_evoMode = Dim([Config("$steadyState$", p_steadyState, evolutionMode="steadyState"),
#                    Config("$generational$", p_generational, evolutionMode="generational")])
dim_evoMode = Dim([Config("$generational$", p_generational, evolutionMode="generational")])
dim_testsRatio = Dim([
    # Config("$0.8$", p_testsRatio_equalTo("0.8"), testsRatio="0.8"),
    Config("$1.0$", p_testsRatio_equalTo("1.0"), testsRatio="1.0"),
])
dim_optThreshold = Dim([
    Config("$0.01$", p_dict_matcher({"optThresholdC": "0.01"}), optThreshold="0.01"),
    Config("$0.1$", p_dict_matcher({"optThresholdC": "0.1"}), optThreshold="0.1"),
])
dim_benchmarkNumTests = Dim([
    Config("$3$ tests", p_benchmarkNumTests_equalTo("3"), benchmarkNumTests="3"),
    Config("$5$ tests", p_benchmarkNumTests_equalTo("5"), benchmarkNumTests="5"),
    Config("$10$ tests", p_benchmarkNumTests_equalTo("10"), benchmarkNumTests="10"),
])



def plot_figures(props, exp_prefix):
    # We want to consider CDGP only
    props = [p for p in props]
    if len(props) == 0:
        print("No props: plots were not generated.")
        return

    getter_mse = lambda p: float(p["result.best.mse"])
    predicate = lambda v, v_xaxis: v <= v_xaxis
    N = 50  # number of points per plot line
    r = (0.0, 1e0)
    xs = np.linspace(r[0], r[1], N)
    xticks = np.arange(r[0], r[1], r[1] / 10)
    plotter.plot_ratio_meeting_predicate(props, getter_mse, predicate, xs=xs, xticks=xticks,