from typing import Dict, List, Optional, Type
import time
import os, os.path
import multiprocessing
from functools import wraps
from time import time

from plotly import graph_objects
from pytablewriter import MarkdownTableWriter
from pytablewriter.style import Style

from framework import DeckList, Game, Manager
from invoked_dragoon import InvokedDragoonManager
from lyna import LynaManager


def measure(func):
    @wraps(func)
    def _time_it(*args, **kwargs):
        start = int(round(time() * 1000))
        try:
            return func(*args, **kwargs)
        finally:
            end_ = int(round(time() * 1000)) - start
            print(f"Total execution time: {end_ if end_ > 0 else 0} ms")

    return _time_it


def generate_sankey(
    filename: str,
    label: List[str],
    color: List[str],
    source: List[int],
    target: List[int],
    value: List[int],
):
    fig = graph_objects.Figure(
        data=[
            graph_objects.Sankey(
                node=dict(
                    pad=15,
                    thickness=20,
                    line=dict(color="black", width=0.5),
                    label=label,
                    color=color,
                ),
                link=dict(source=source, target=target, value=value,),
            )
        ]
    )

    fig.write_html(os.path.join("output", f"{filename}.html"))


def generate_table(name: str, data: List[List[str]]) -> str:
    writer = MarkdownTableWriter(
        table_name=name,
        headers=["Label", "Frequency"],
        column_styles=[Style(align="center"), Style(align="right")],
        value_matrix=data,
        margin=1,
    )
    return writer.dumps()


def generate_overall_table(name: str, headers: List[str], data: List[List[str]]) -> str:
    writer = MarkdownTableWriter()
    writer.table_name = name
    writer.headers = headers
    writer.column_styles = [Style(align="center")] + [
        Style(align="right") for _ in headers[1:]
    ]
    writer.value_matrix = data
    writer.margin = 1
    return writer.dumps()


# needed for multiprocessing.pool
def run_one(manager: Manager) -> Game:
    return manager.run()


def run_in_parallel(
    n: int, manager_class: Type[Manager], decklist: Optional[DeckList] = None
) -> List[Game]:
    managers = [manager_class(decklist) for _ in range(n)]
    with multiprocessing.Pool(3) as pool:
        return pool.map(run_one, managers)


def run_many(
    n: int, manager_class: Type[Manager], decklist: Optional[DeckList] = None
) -> Dict[str, float]:
    return run_in_parallel(n, manager_class, decklist)
    # return [run_one(manager_class()) for _ in range(n)]


def test_default_decklist(manager_class: Type[Manager], name: str = "Stats", n=5):
    end_games = run_many(n, manager_class)
    stats = manager_class.generate_stats(end_games)
    table = generate_table(name, stats)
    print(table)


def debug(manager_class: Type[Manager], name: str = "Stats"):
    end_games = [run_one(manager := manager_class())]
    print(manager.initial_game)
    print("\n")
    print(end_games[0])
    print("->".join(end_games[0].actions))
    print("\n")
    # print(end_games[0].flags)
    # stats = manager_class.generate_stats(end_games)
    # table = generate_table(name, stats)
    # print(table)


def compare_decklists(
    filename: str,
    title: str,
    manager_class: Type[Manager],
    decklists: Dict[str, DeckList],
    n=5000,
) -> None:
    overall_data = []
    headers = []
    with open(os.path.join("output", f"{filename}.md"), "w") as outfile:
        for (decklist_title, decklist) in decklists.items():
            end_games = run_many(n, manager_class, decklist)
            decklist_data = manager_class.generate_stats(end_games)
            overall_data.append(
                [decklist_title] + [datapoint[1] for datapoint in decklist_data]
            )
            headers = ["Decklist"] + [datapoint[0] for datapoint in decklist_data]
        if not overall_data:
            return
        table = generate_overall_table(title, headers, overall_data)
        print(table, file=outfile)

def lyna_or_not():
    compare_decklists(
        "lyna_v_no_lyna",
        "Lyna Or Not",
        LynaManager,
        {"Lyna": LynaManager.lyna_decklist, "No Lyna": LynaManager.no_lyna_decklist},
        300,
    )


@measure
def main():
    # debug(InvokedDragoonManager)
    test_default_decklist(InvokedDragoonManager, name="Goon", n=1000)

if __name__ == "__main__":
    main()

from typing import Dict, List, Optional, Type
import time
import os, os.path
import multiprocessing
from functools import wraps
from time import time

from plotly import graph_objects
from pytablewriter import MarkdownTableWriter
from pytablewriter.style import Style

from framework import DeckList, Game, Manager
from invoked_dragoon import InvokedDragoonManager
from lyna import LynaManager


def measure(func):
    @wraps(func)
    def _time_it(*args, **kwargs):
        start = int(round(time() * 1000))
        try:
            return func(*args, **kwargs)
        finally:
            end_ = int(round(time() * 1000)) - start
            print(f"Total execution time: {end_ if end_ > 0 else 0} ms")

    return _time_it


def generate_sankey(
    filename: str,
    label: List[str],
    color: List[str],
    source: List[int],
    target: List[int],
    value: List[int],
):
    fig = graph_objects.Figure(
        data=[
            graph_objects.Sankey(
                node=dict(
                    pad=15,
                    thickness=20,
                    line=dict(color="black", width=0.5),
                    label=label,
                    color=color,
                ),
                link=dict(source=source, target=target, value=value,),
            )
        ]
    )

    fig.write_html(os.path.join("output", f"{filename}.html"))


def generate_table(name: str, data: List[List[str]]) -> str:
    writer = MarkdownTableWriter(
        table_name=name,
        headers=["Label", "Frequency"],
        column_styles=[Style(align="center"), Style(align="right")],
        value_matrix=data,
        margin=1,
    )
    return writer.dumps()


def generate_overall_table(name: str, headers: List[str], data: List[List[str]]) -> str:
    writer = MarkdownTableWriter()
    writer.table_name = name
    writer.headers = headers
    writer.column_styles = [Style(align="center")] + [
        Style(align="right") for _ in headers[1:]
    ]
    writer.value_matrix = data
    writer.margin = 1
    return writer.dumps()


# needed for multiprocessing.pool
def run_one(manager: Manager) -> Game:
    return manager.run()


def run_in_parallel(
    n: int, manager_class: Type[Manager], decklist: Optional[DeckList] = None
) -> List[Game]:
    managers = [manager_class(decklist) for _ in range(n)]
    with multiprocessing.Pool(3) as pool:
        return pool.map(run_one, managers)


def run_many(
    n: int, manager_class: Type[Manager], decklist: Optional[DeckList] = None
) -> Dict[str, float]:
    return run_in_parallel(n, manager_class, decklist)
    # return [run_one(manager_class()) for _ in range(n)]


def test_default_decklist(manager_class: Type[Manager], name: str = "Stats", n=5):
    end_games = run_many(n, manager_class)
    stats = manager_class.generate_stats(end_games)
    table = generate_table(name, stats)
    print(table)


def debug(manager_class: Type[Manager], name: str = "Stats"):
    end_games = [run_one(manager := manager_class())]
    print(manager.initial_game)
    print("\n")
    print(end_games[0])
    print("->".join(end_games[0].actions))
    print("\n")
    # print(end_games[0].flags)
    # stats = manager_class.generate_stats(end_games)
    # table = generate_table(name, stats)
    # print(table)


def compare_decklists(
    filename: str,
    title: str,
    manager_class: Type[Manager],
    decklists: Dict[str, DeckList],
    n=5000,
) -> None:
    overall_data = []
    headers = []
    with open(os.path.join("output", f"{filename}.md"), "w") as outfile:
        for (decklist_title, decklist) in decklists.items():
            end_games = run_many(n, manager_class, decklist)
            decklist_data = manager_class.generate_stats(end_games)
            overall_data.append(
                [decklist_title] + [datapoint[1] for datapoint in decklist_data]
            )
            headers = ["Decklist"] + [datapoint[0] for datapoint in decklist_data]
        if not overall_data:
            return
        table = generate_overall_table(title, headers, overall_data)
        print(table, file=outfile)

def lyna_or_not():
    compare_decklists(
        "lyna_v_no_lyna",
        "Lyna Or Not",
        LynaManager,
        {"Lyna": LynaManager.lyna_decklist, "No Lyna": LynaManager.no_lyna_decklist},
        300,
    )


@measure
def main():
    # debug(InvokedDragoonManager)
    test_default_decklist(InvokedDragoonManager, name="Goon", n=1000)

if __name__ == "__main__":
    main()

# needed for multiprocessing.pool # return [run_one(manager_class()) for _ in range(n)] # print(end_games[0].flags) # stats = manager_class.generate_stats(end_games) # table = generate_table(name, stats) # print(table) # debug(InvokedDragoonManager)