"""
A script to obtain the Ashlock Fingerprints of all strategies in the Axelrod
library.

This writes a hash of the source code of each strategy to file: db.csv.

If the source code of a strategy changes **or** a new strategy is introduced
then the fingerprint is regenerated for that strategy.
"""

import inspect
import hashlib
import csv
import string

import numpy as np
import matplotlib.pyplot as plt

import axelrod as axl


def hash_strategy(strategy):
    """
    Hash the source code of a strategy
    """
    try:
        source_code = "".join(inspect.getsourcelines(strategy)[0])
    except OSError:  # Some classes are dynamically created
        source_code = "".join(inspect.getsourcelines(strategy.strategy)[0])
    hash_object = hashlib.md5(source_code.encode("utf-8"))
    hashed_source = hash_object.hexdigest()
    return hashed_source


def write_strategy_to_db(strategy, filename="db.csv", fingerprint="Ashlock"):
    """
    Write the hash of a strategy to the db
    """
    hashed_source = hash_strategy(strategy)
    with open(filename, "a") as db:
        try:
            db.write(
                "{},{},{}\n".format(
                    strategy.original_name, fingerprint, hashed_source
                )
            )
        except AttributeError:
            db.write(
                "{},{},{}\n".format(strategy.name, fingerprint, hashed_source)
            )


def read_db(filename="db.csv"):
    """
    Read filename and return a dictionary mapping string names to hash of source
    code of a strategy
    """
    with open(filename, "r") as db:
        csvreader = csv.reader(db)
        str_to_hash = {(row[0], row[1]): row[2] for row in csvreader}
    return str_to_hash


def create_db(filename="db.csv"):
    """
    Creates an empty db.csv file
    """
    with open(filename, "w"):
        pass


def write_data_to_file(fp, filename):
    """
    Write the fingerprint data to a file.
    """
    columns = ["x", "y", "score"]

    with open(filename, "w") as f:
        w = csv.writer(f)
        w.writerow(columns)
        for key, value in fp.data.items():
            w.writerow([key.x, key.y, value])


def obtain_fingerprint(
    strategy, turns, repetitions, probe=axl.TitForTat, processes=1
):
    """
    Obtain the fingerprint for a given strategy and save the figure to the
    assets dir
    """
    fp = axl.AshlockFingerprint(strategy, probe)
    fp.fingerprint(
        turns=turns,
        repetitions=repetitions,
        progress_bar=False,
        processes=processes,
    )
    plt.figure()
    fp.plot()
    try:
        name = strategy.original_name
    except AttributeError:
        name = strategy.name
    plt.tight_layout()
    plt.savefig(
        "assets/{}.png".format(format_filename(name)), bbox_inches="tight"
    )
    write_data_to_file(fp, "assets/{}.csv".format(format_filename(name)))


def obtain_transitive_fingerprint(strategy, turns, repetitions, processes=1):
    """
    Obtain the transitive fingerprint
    for a given strategy and save the figure to the assets dir
    """
    fp = axl.TransitiveFingerprint(strategy, number_of_opponents=30)
    fp.fingerprint(
        turns=turns,
        repetitions=repetitions,
        progress_bar=False,
        processes=processes,
    )
    plt.figure()
    fp.plot()
    try:
        name = strategy.original_name
    except AttributeError:
        name = strategy.name
    plt.tight_layout()
    plt.savefig(
        "assets/transitive_{}.png".format(format_filename(name)),
        bbox_inches="tight",
    )
    np.savetxt(
        "assets/transitive_{}.csv".format(format_filename(name)), fp.data
    )


def obtain_transitive_fingerprint_v_short(
    strategy, turns, repetitions, processes=1
):
    """
    Obtain the transitive fingerprint against short run time
    for a given strategy and save the figure to the assets dir
    """
    short_run_time = [s() for s in axl.short_run_time_strategies]
    fp = axl.TransitiveFingerprint(strategy, opponents=short_run_time)
    fp.fingerprint(
        turns=turns,
        repetitions=repetitions,
        progress_bar=False,
        processes=processes,
    )
    plt.figure()
    fp.plot(display_names=True)
    try:
        name = strategy.original_name
    except AttributeError:
        name = strategy.name
    plt.tight_layout()
    plt.savefig(
        "assets/transitive_v_short_{}.png".format(format_filename(name)),
        bbox_inches="tight",
    )
    np.savetxt(
        "assets/transitive_v_short_{}.csv".format(format_filename(name)),
        fp.data,
    )


def format_filename(s):
    """
    Take a string and return a valid filename constructed from the string.
    Uses a whitelist approach: any characters not present in valid_chars are
    removed. Also spaces are replaced with underscores.

    Note: this method may produce invalid filenames such as ``, `.` or `..`
    When I use this method I prepend a date string like '2009_01_15_19_46_32_'
    and append a file extension like '.txt', so I avoid the potential of using
    an invalid filename.

    Borrowed from https://gist.github.com/seanh/93666
    """
    valid_chars = "-_.() {}{}".format(string.ascii_letters, string.digits)
    filename = "".join(c for c in s if c in valid_chars)
    filename = filename.replace(" ", "_")
    return filename


def write_markdown(strategy):
    """
    Write a markdown section of a strategy.

    """
    try:
        name = strategy.original_name
    except AttributeError:
        name = strategy.name
    markdown = """

## {0}

![fingerprint of {0}](./assets/{1}.png)

[data (csv)](./assets/{1}.csv)

![Transitive fingerprint of {0}](./assets/transitive_{1}.png)

[data (csv)](./assets/transitive_{1}.csv)

![Transitive fingerprint of {0} against short run time](./assets/transitive_v_short_{1}.png)

[data (csv)](./assets/transitive_v_short_{1}.csv)
    """.format(
        name, format_filename(name)
    )
    return markdown


def main(
    turns,
    repetitions,
    transitive_turns,
    transitive_repetitions,
    transitive_v_short_turns,
    transitive_v_short_repetitions,
    processes,
):
    """
    Fingerprint all strategies, if a strategy has already been fingerprinted it
    does not get rerun.
    """
    version = axl.__version__

    markdown = """# Ashlock and transitive fingerprints

See:
[axelrod.readthedocs.io/en/latest/tutorials/further_topics/fingerprinting.html#fingerprinting](http://axelrod.readthedocs.io/en/latest/tutorials/further_topics/fingerprinting.html#fingerprinting)

All strategies included from Axelrod version {}.

This README.md file is autogenerated by running:

```
$ python update_fingerprints.py
```

Each individual fingerprint can be obtained by running:

```python
import axelrod as axl
fp = axl.AshlockFingerprint(strategy, probe)
fp.fingerprint(turns={}, repetitions={})
fp.plot()
```

# Axelrod library fingerprints

    """.format(
        version, turns, repetitions
    )
    try:
        db = read_db()
    except FileNotFoundError:
        create_db()
        db = read_db()

    for strategy in axl.short_run_time_strategies:
        name = strategy.name
        signature = hash_strategy(strategy)
        fp = "Ashlock"
        if (name, fp) not in db or db[name, fp] != signature:
            obtain_fingerprint(
                strategy, turns, repetitions, processes=processes
            )
            write_strategy_to_db(strategy, fingerprint=fp)

        fp = "Transitive"
        if (name, fp) not in db or db[name, fp] != signature:
            obtain_transitive_fingerprint(
                strategy,
                transitive_turns,
                transitive_repetitions,
                processes=processes,
            )
            write_strategy_to_db(strategy, fingerprint=fp)

        fp = "Transitive_v_short"
        if (name, fp) not in db or db[name, fp] != signature:
            obtain_transitive_fingerprint_v_short(
                strategy,
                transitive_v_short_turns,
                transitive_v_short_repetitions,
                processes=processes,
            )
            write_strategy_to_db(strategy, fingerprint=fp)

        markdown += write_markdown(strategy)

    with open("README.md", "w") as outfile:
        outfile.write(markdown)


if __name__ == "__main__":
    turns, repetitions = 200, 20
    transitive_turns, transitive_repetitions = 200, 20
    transitive_v_short_turns, transitive_v_short_repetitions = 200, 20
    processes = 20
    main(
        turns=turns,
        repetitions=repetitions,
        transitive_turns=transitive_turns,
        transitive_repetitions=transitive_repetitions,
        transitive_v_short_turns=transitive_v_short_turns,
        transitive_v_short_repetitions=transitive_v_short_repetitions,
        processes=processes,
    )
