"""Data analyzation metrics

Each algorithm works on a set of handwritings. They have to be applied like
this:

>>> import hwrt.data_analyzation_metrics
>>> from hwrt.handwritten_data import HandwrittenData
>>> data_json = '[[{"time": 123, "x": 45, "y": 67}]]'
>>> a = [{'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "2953L"},
...   {'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "4037L"},
...   {'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "4056L"}]
>>> creator_metric = Creator('creator.csv')
>>> creator_metric(a)
100%

"""


# Core Library modules
import logging
import math
import os
import sys
import time
from collections import defaultdict

# Third party modules
import numpy

# Local modules
# HandwrittenData and preprocessing are needed because of pickle
from . import handwritten_data  # pylint: disable=W0611
from . import preprocessing  # pylint: disable=W0611
from . import utils

logger = logging.getLogger(__name__)
sys.modules["hwrt.HandwrittenData"] = handwritten_data


def get_metrics(metrics_description):
    """Get metrics from a list of dictionaries. """
    return utils.get_objectlist(
        metrics_description,
        config_key="data_analyzation_plugins",
        module=sys.modules[__name__],
    )


# Helper functions that are useful for some metrics


def prepare_file(filename):
    """Truncate the file and return the filename."""
    directory = os.path.join(utils.get_project_root(), "analyzation/")
    if not os.path.exists(directory):
        os.makedirs(directory)
    workfilename = os.path.join(directory, filename)
    with open(workfilename, "w") as fp:
        pass  # Truncate the file
    return workfilename


def sort_by_formula_id(raw_datasets):
    """
    Sort a list of formulas by `id`, where `id` represents the accepted
    formula id.

    Parameters
    ----------
    raw_datasets : list of dictionaries
        A list of raw datasets.

    Examples
    --------
    The parameter `raw_datasets` has to be of the format

    >>> from hwrt.handwritten_data import HandwrittenData
    >>> data = '[[{"time": 123, "x": 45, "y": 67}]]'
    >>> rd = [{'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 2953},
    ...       {'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 4037},
    ...       {'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 4056}]
    >>> _ = sort_by_formula_id(rd)
    """
    by_formula_id = defaultdict(list)
    for el in raw_datasets:
        by_formula_id[el["handwriting"].formula_id].append(el["handwriting"])
    return by_formula_id


# Only data analyzation calculation classes follow
# Every class must have a __str__, __repr__ and __call__ function where
# __call__ must take exactly one argument of type list of dictionaries
# Every class must have a constructor which takes the filename as a parameter.
# This filename has to be used to write the evaluation results
# (preferably in CSV format) to this file.
# prepare_file should be applied to every file in the constructor


class Creator:
    """Analyze who created most of the data."""

    def __init__(self, filename="creator.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "AnalyzeCreator(%s)" % self.filename

    def __str__(self):
        return "AnalyzeCreator(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("creatorid,nr of recordings\n")  # heading

            print_data = defaultdict(int)
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                print_data[raw_dataset["handwriting"].user_id] += 1
            print("100%")

            # Sort the data by highest value, descending
            print_data = sorted(print_data.items(), key=lambda n: n[1], reverse=True)

            # Write data to file
            write_file.write(f"total,{sum(value for _, value in print_data)}\n")
            for userid, value in print_data:
                write_file.write(f"{userid},{value}\n")


class InstrokeSpeed:
    """Analyze how fast the points were in pixel/ms."""

    def __init__(self, filename="instroke_speed.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "InstrokeSpeed(%s)" % self.filename

    def __str__(self):
        return "InstrokeSpeed(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("speed\n")  # heading

            print_data = []
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                pointlist = raw_dataset["handwriting"].get_sorted_pointlist()

                for stroke in pointlist:
                    for last_point, point in zip(stroke, stroke[1:]):
                        space_dist = math.hypot(
                            last_point["x"] - point["x"], last_point["y"] - point["y"]
                        )
                        time_delta = point["time"] - last_point["time"]
                        if time_delta == 0:
                            continue
                        print_data.append(space_dist / time_delta)
            print("100%")
            # Sort the data by highest value, descending
            print_data = sorted(print_data, reverse=True)
            # Write data to file
            for value in print_data:
                write_file.write("%0.8f\n" % (value))

            logger.info("instroke speed mean: %0.8f", numpy.mean(print_data))
            logger.info("instroke speed std: %0.8f", numpy.std(print_data))


class InterStrokeDistance:
    """Analyze how much distance in px is between strokes."""

    def __init__(self, filename="dist_between_strokes.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "InterStrokeDistance(%s)" % self.filename

    def __str__(self):
        return "InterStrokeDistance(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("speed\n")  # heading

            print_data = []
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                pointlist = raw_dataset["handwriting"].get_sorted_pointlist()

                for last_stroke, stroke in zip(pointlist, pointlist[1:]):
                    point1 = last_stroke[-1]
                    point2 = stroke[0]
                    space_dist = math.hypot(
                        point1["x"] - point2["x"], point1["y"] - point2["y"]
                    )
                    print_data.append(space_dist)
            print("100%")
            # Sort the data by highest value, descending
            print_data = sorted(print_data, reverse=True)
            # Write data to file
            for value in print_data:
                write_file.write("%0.8f\n" % (value))

            logger.info("dist_between_strokes mean:\t%0.8fpx", numpy.mean(print_data))
            logger.info("dist_between_strokes std: \t%0.8fpx", numpy.std(print_data))


class TimeBetweenPointsAndStrokes:
    """For each recording: Store the average time between controll points of
    one stroke / controll points of two different strokes.
    """

    def __init__(
        self,
        filename="average_time_between_points.txt",
        filename_strokes="average_time_between_strokes.txt",
    ):
        self.filename_points = prepare_file(filename)
        self.filename_strokes = prepare_file(filename_strokes)

    def __repr__(self):
        return "TimeBetweenPointsAndStrokes({points}, {strokes})".format(
            points=self.filename_points,
            strokes=self.filename_strokes,
        )

    __str__ = __repr__

    def __call__(self, raw_datasets):
        average_between_points = open(self.filename_points, "a")  # noqa
        average_between_strokes = open(self.filename_strokes, "a")  # noqa
        start_time = time.time()
        for i, raw_dataset in enumerate(raw_datasets):
            if i % 100 == 0 and i > 0:
                utils.print_status(len(raw_datasets), i, start_time)

            # Do the work
            times_between_points, times_between_strokes = [], []
            last_stroke_end = None
            for stroke in raw_dataset["handwriting"].get_sorted_pointlist():
                if last_stroke_end is not None:
                    times_between_strokes.append(stroke[-1]["time"] - last_stroke_end)
                last_stroke_end = stroke[-1]["time"]
                for point1, point2 in zip(stroke, stroke[1:]):
                    delta = point2["time"] - point1["time"]
                    times_between_points.append(delta)
            # The recording might only have one point
            if len(times_between_points) > 0:
                tmp = times_between_points
                average_between_points.write("%0.2f\n" % numpy.average(tmp))
            # The recording might only have one stroke
            if len(times_between_strokes) > 0:
                tmp = times_between_strokes
                average_between_strokes.write("%0.2f\n" % numpy.average(tmp))
        print("100%")
        average_between_points.close()
        average_between_strokes.close()


class AnalyzeErrors:
    """Analyze the number of errors in the dataset."""

    def __init__(self, filename="errors.txt", time_max_threshold=30 * 1000):
        self.filename = prepare_file(filename)
        self.time_max_threshold = time_max_threshold  # in ms
        self.dot_symbols = [
            "i",
            "j",
            r"\cdot",
            r"\div",
            "\\because",
            "\\therefore",
        ]  # TODO: Use the tags!

    def __repr__(self):
        return "AnalyzeErrors"

    def __str__(self):
        return "AnalyzeErrors"

    def _write_data(
        self,
        symbols,
        err_recs,
        nr_recordings,
        total_error_count,
        percentages,
        time_max_list,
    ):
        """Write all obtained data to a file.

        Parameters
        ----------
        symbols : list of tuples (String, non-negative int)
            List of all symbols with the count of recordings
        err_recs : dictionary
            count of recordings by error type
        nr_recordings : non-negative int
            number of recordings
        total_error_count : dictionary
            Count of all error that have happened by type
        percentages : list
            List of all recordings where removing the dots changed the size of
            the bounding box.
        time_max_list : list
            List of all recordings where the recording time is above a
            threshold.
        """
        write_file = open(self.filename, "a")  # noqa
        s = ""
        for symbol, count in sorted(symbols.items(), key=lambda n: n[0]):
            if symbol in ["a", "0", "A"]:
                s += "\n%s (%i), " % (symbol, count)
            elif symbol in ["z", "9", "Z"]:
                s += "%s (%i) \n" % (symbol, count)
            else:
                s += "%s (%i), " % (symbol, count)
        print("## Data", file=write_file)
        print("Symbols: %i" % len(symbols), file=write_file)
        print("Recordings: %i" % sum(symbols.values()), file=write_file)
        print("```", file=write_file)
        print(s[:-1], file=write_file)
        print("```", file=write_file)

        # Show errors
        print(
            "Recordings with wild points: %i (%0.2f%%)"
            % (
                err_recs["wild_points"],
                float(err_recs["wild_points"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print("wild points: %i" % total_error_count["wild_points"], file=write_file)
        print(
            "Recordings with missing stroke: %i (%0.2f%%)"
            % (
                err_recs["missing_stroke"],
                float(err_recs["missing_stroke"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print(
            "Recordings with errors: %i (%0.2f%%)"
            % (err_recs["total"], float(err_recs["total"]) / nr_recordings * 100),
            file=write_file,
        )
        print(
            "Recordings with dots: %i (%0.2f%%)"
            % (
                err_recs["single_dots"],
                float(err_recs["single_dots"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print("dots: %i" % total_error_count["single_dots"], file=write_file)
        print(
            "size changing removal: %i (%0.2f%%)"
            % (len(percentages), float(len(percentages)) / nr_recordings * 100),
            file=write_file,
        )
        print(
            "%i recordings took more than %i ms. That were: "
            % (len(time_max_list), self.time_max_threshold),
            file=write_file,
        )
        for recording in time_max_list:
            print(
                "* %ims: %s: %s"
                % (
                    recording.get_time(),
                    utils.get_readable_time(recording.get_time()),
                    recording,
                ),
                file=write_file,
            )
        write_file.close()

    def __call__(self, raw_datasets):
        # Initialize variables
        symbols = defaultdict(int)

        # Count errornous recordings
        err_recs = {
            "wild_points": 0,
            "missing_stroke": 0,
            "single_dots": 0,  # except symbols_with_dots
            "total": 0,
        }

        # Count errors (one type of error might occur multiple times in
        # a single recording)
        total_error_count = {"wild_points": 0, "single_dots": 0}

        percentages = []

        # List with recordings that are over the time maximum
        time_max_list = []

        for raw_dataset in raw_datasets:
            recording = raw_dataset["handwriting"]
            symbols[recording.formula_in_latex] += 1
            if recording.get_time() > self.time_max_threshold:
                time_max_list.append(recording)
            if recording.wild_point_count > 0:
                err_recs["wild_points"] += 1
                total_error_count["wild_points"] += recording.wild_point_count
            err_recs["missing_stroke"] += recording.missing_stroke
            if recording.wild_point_count > 0 or recording.missing_stroke:
                err_recs["total"] += 1
            if (
                recording.count_single_dots() > 0
                and raw_dataset["formula_in_latex"] not in self.dot_symbols
                and "dots" not in raw_dataset["formula_in_latex"]
            ):
                err_recs["single_dots"] += 1
                old_area = recording.get_area()
                tmp = [preprocessing.RemoveDots()]
                recording.preprocessing(tmp)
                new_area = recording.get_area()
                percentage = float(new_area) / float(old_area)
                if percentage < 1.0:
                    percentages.append(percentage)
            total_error_count["single_dots"] += recording.count_single_dots()

        time_max_list = sorted(time_max_list, key=lambda n: n.get_time(), reverse=True)

        self._write_data(
            symbols,
            err_recs,
            len(raw_datasets),
            total_error_count,
            percentages,
            time_max_list,
        )

"""Data analyzation metrics

Each algorithm works on a set of handwritings. They have to be applied like
this:

>>> import hwrt.data_analyzation_metrics
>>> from hwrt.handwritten_data import HandwrittenData
>>> data_json = '[[{"time": 123, "x": 45, "y": 67}]]'
>>> a = [{'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "2953L"},
...   {'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "4037L"},
...   {'is_in_testset': 0,
...    'formula_id': "31L",
...    'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data_json),
...    'formula_in_latex': 'A',
...    'id': "4056L"}]
>>> creator_metric = Creator('creator.csv')
>>> creator_metric(a)
100%

"""


# Core Library modules
import logging
import math
import os
import sys
import time
from collections import defaultdict

# Third party modules
import numpy

# Local modules
# HandwrittenData and preprocessing are needed because of pickle
from . import handwritten_data  # pylint: disable=W0611
from . import preprocessing  # pylint: disable=W0611
from . import utils

logger = logging.getLogger(__name__)
sys.modules["hwrt.HandwrittenData"] = handwritten_data


def get_metrics(metrics_description):
    """Get metrics from a list of dictionaries. """
    return utils.get_objectlist(
        metrics_description,
        config_key="data_analyzation_plugins",
        module=sys.modules[__name__],
    )


# Helper functions that are useful for some metrics


def prepare_file(filename):
    """Truncate the file and return the filename."""
    directory = os.path.join(utils.get_project_root(), "analyzation/")
    if not os.path.exists(directory):
        os.makedirs(directory)
    workfilename = os.path.join(directory, filename)
    with open(workfilename, "w") as fp:
        pass  # Truncate the file
    return workfilename


def sort_by_formula_id(raw_datasets):
    """
    Sort a list of formulas by `id`, where `id` represents the accepted
    formula id.

    Parameters
    ----------
    raw_datasets : list of dictionaries
        A list of raw datasets.

    Examples
    --------
    The parameter `raw_datasets` has to be of the format

    >>> from hwrt.handwritten_data import HandwrittenData
    >>> data = '[[{"time": 123, "x": 45, "y": 67}]]'
    >>> rd = [{'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 2953},
    ...       {'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 4037},
    ...       {'is_in_testset': 0,
    ...        'formula_id': 31,
    ...        'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data),
    ...        'formula_in_latex': 'A',
    ...        'id': 4056}]
    >>> _ = sort_by_formula_id(rd)
    """
    by_formula_id = defaultdict(list)
    for el in raw_datasets:
        by_formula_id[el["handwriting"].formula_id].append(el["handwriting"])
    return by_formula_id


# Only data analyzation calculation classes follow
# Every class must have a __str__, __repr__ and __call__ function where
# __call__ must take exactly one argument of type list of dictionaries
# Every class must have a constructor which takes the filename as a parameter.
# This filename has to be used to write the evaluation results
# (preferably in CSV format) to this file.
# prepare_file should be applied to every file in the constructor


class Creator:
    """Analyze who created most of the data."""

    def __init__(self, filename="creator.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "AnalyzeCreator(%s)" % self.filename

    def __str__(self):
        return "AnalyzeCreator(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("creatorid,nr of recordings\n")  # heading

            print_data = defaultdict(int)
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                print_data[raw_dataset["handwriting"].user_id] += 1
            print("100%")

            # Sort the data by highest value, descending
            print_data = sorted(print_data.items(), key=lambda n: n[1], reverse=True)

            # Write data to file
            write_file.write(f"total,{sum(value for _, value in print_data)}\n")
            for userid, value in print_data:
                write_file.write(f"{userid},{value}\n")


class InstrokeSpeed:
    """Analyze how fast the points were in pixel/ms."""

    def __init__(self, filename="instroke_speed.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "InstrokeSpeed(%s)" % self.filename

    def __str__(self):
        return "InstrokeSpeed(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("speed\n")  # heading

            print_data = []
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                pointlist = raw_dataset["handwriting"].get_sorted_pointlist()

                for stroke in pointlist:
                    for last_point, point in zip(stroke, stroke[1:]):
                        space_dist = math.hypot(
                            last_point["x"] - point["x"], last_point["y"] - point["y"]
                        )
                        time_delta = point["time"] - last_point["time"]
                        if time_delta == 0:
                            continue
                        print_data.append(space_dist / time_delta)
            print("100%")
            # Sort the data by highest value, descending
            print_data = sorted(print_data, reverse=True)
            # Write data to file
            for value in print_data:
                write_file.write("%0.8f\n" % (value))

            logger.info("instroke speed mean: %0.8f", numpy.mean(print_data))
            logger.info("instroke speed std: %0.8f", numpy.std(print_data))


class InterStrokeDistance:
    """Analyze how much distance in px is between strokes."""

    def __init__(self, filename="dist_between_strokes.csv"):
        self.filename = prepare_file(filename)

    def __repr__(self):
        return "InterStrokeDistance(%s)" % self.filename

    def __str__(self):
        return "InterStrokeDistance(%s)" % self.filename

    def __call__(self, raw_datasets):
        with open(self.filename, "a") as write_file:
            write_file.write("speed\n")  # heading

            print_data = []
            start_time = time.time()
            for i, raw_dataset in enumerate(raw_datasets):
                if i % 100 == 0 and i > 0:
                    utils.print_status(len(raw_datasets), i, start_time)
                pointlist = raw_dataset["handwriting"].get_sorted_pointlist()

                for last_stroke, stroke in zip(pointlist, pointlist[1:]):
                    point1 = last_stroke[-1]
                    point2 = stroke[0]
                    space_dist = math.hypot(
                        point1["x"] - point2["x"], point1["y"] - point2["y"]
                    )
                    print_data.append(space_dist)
            print("100%")
            # Sort the data by highest value, descending
            print_data = sorted(print_data, reverse=True)
            # Write data to file
            for value in print_data:
                write_file.write("%0.8f\n" % (value))

            logger.info("dist_between_strokes mean:\t%0.8fpx", numpy.mean(print_data))
            logger.info("dist_between_strokes std: \t%0.8fpx", numpy.std(print_data))


class TimeBetweenPointsAndStrokes:
    """For each recording: Store the average time between controll points of
    one stroke / controll points of two different strokes.
    """

    def __init__(
        self,
        filename="average_time_between_points.txt",
        filename_strokes="average_time_between_strokes.txt",
    ):
        self.filename_points = prepare_file(filename)
        self.filename_strokes = prepare_file(filename_strokes)

    def __repr__(self):
        return "TimeBetweenPointsAndStrokes({points}, {strokes})".format(
            points=self.filename_points,
            strokes=self.filename_strokes,
        )

    __str__ = __repr__

    def __call__(self, raw_datasets):
        average_between_points = open(self.filename_points, "a")  # noqa
        average_between_strokes = open(self.filename_strokes, "a")  # noqa
        start_time = time.time()
        for i, raw_dataset in enumerate(raw_datasets):
            if i % 100 == 0 and i > 0:
                utils.print_status(len(raw_datasets), i, start_time)

            # Do the work
            times_between_points, times_between_strokes = [], []
            last_stroke_end = None
            for stroke in raw_dataset["handwriting"].get_sorted_pointlist():
                if last_stroke_end is not None:
                    times_between_strokes.append(stroke[-1]["time"] - last_stroke_end)
                last_stroke_end = stroke[-1]["time"]
                for point1, point2 in zip(stroke, stroke[1:]):
                    delta = point2["time"] - point1["time"]
                    times_between_points.append(delta)
            # The recording might only have one point
            if len(times_between_points) > 0:
                tmp = times_between_points
                average_between_points.write("%0.2f\n" % numpy.average(tmp))
            # The recording might only have one stroke
            if len(times_between_strokes) > 0:
                tmp = times_between_strokes
                average_between_strokes.write("%0.2f\n" % numpy.average(tmp))
        print("100%")
        average_between_points.close()
        average_between_strokes.close()


class AnalyzeErrors:
    """Analyze the number of errors in the dataset."""

    def __init__(self, filename="errors.txt", time_max_threshold=30 * 1000):
        self.filename = prepare_file(filename)
        self.time_max_threshold = time_max_threshold  # in ms
        self.dot_symbols = [
            "i",
            "j",
            r"\cdot",
            r"\div",
            "\\because",
            "\\therefore",
        ]  # TODO: Use the tags!

    def __repr__(self):
        return "AnalyzeErrors"

    def __str__(self):
        return "AnalyzeErrors"

    def _write_data(
        self,
        symbols,
        err_recs,
        nr_recordings,
        total_error_count,
        percentages,
        time_max_list,
    ):
        """Write all obtained data to a file.

        Parameters
        ----------
        symbols : list of tuples (String, non-negative int)
            List of all symbols with the count of recordings
        err_recs : dictionary
            count of recordings by error type
        nr_recordings : non-negative int
            number of recordings
        total_error_count : dictionary
            Count of all error that have happened by type
        percentages : list
            List of all recordings where removing the dots changed the size of
            the bounding box.
        time_max_list : list
            List of all recordings where the recording time is above a
            threshold.
        """
        write_file = open(self.filename, "a")  # noqa
        s = ""
        for symbol, count in sorted(symbols.items(), key=lambda n: n[0]):
            if symbol in ["a", "0", "A"]:
                s += "\n%s (%i), " % (symbol, count)
            elif symbol in ["z", "9", "Z"]:
                s += "%s (%i) \n" % (symbol, count)
            else:
                s += "%s (%i), " % (symbol, count)
        print("## Data", file=write_file)
        print("Symbols: %i" % len(symbols), file=write_file)
        print("Recordings: %i" % sum(symbols.values()), file=write_file)
        print("```", file=write_file)
        print(s[:-1], file=write_file)
        print("```", file=write_file)

        # Show errors
        print(
            "Recordings with wild points: %i (%0.2f%%)"
            % (
                err_recs["wild_points"],
                float(err_recs["wild_points"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print("wild points: %i" % total_error_count["wild_points"], file=write_file)
        print(
            "Recordings with missing stroke: %i (%0.2f%%)"
            % (
                err_recs["missing_stroke"],
                float(err_recs["missing_stroke"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print(
            "Recordings with errors: %i (%0.2f%%)"
            % (err_recs["total"], float(err_recs["total"]) / nr_recordings * 100),
            file=write_file,
        )
        print(
            "Recordings with dots: %i (%0.2f%%)"
            % (
                err_recs["single_dots"],
                float(err_recs["single_dots"]) / nr_recordings * 100,
            ),
            file=write_file,
        )
        print("dots: %i" % total_error_count["single_dots"], file=write_file)
        print(
            "size changing removal: %i (%0.2f%%)"
            % (len(percentages), float(len(percentages)) / nr_recordings * 100),
            file=write_file,
        )
        print(
            "%i recordings took more than %i ms. That were: "
            % (len(time_max_list), self.time_max_threshold),
            file=write_file,
        )
        for recording in time_max_list:
            print(
                "* %ims: %s: %s"
                % (
                    recording.get_time(),
                    utils.get_readable_time(recording.get_time()),
                    recording,
                ),
                file=write_file,
            )
        write_file.close()

    def __call__(self, raw_datasets):
        # Initialize variables
        symbols = defaultdict(int)

        # Count errornous recordings
        err_recs = {
            "wild_points": 0,
            "missing_stroke": 0,
            "single_dots": 0,  # except symbols_with_dots
            "total": 0,
        }

        # Count errors (one type of error might occur multiple times in
        # a single recording)
        total_error_count = {"wild_points": 0, "single_dots": 0}

        percentages = []

        # List with recordings that are over the time maximum
        time_max_list = []

        for raw_dataset in raw_datasets:
            recording = raw_dataset["handwriting"]
            symbols[recording.formula_in_latex] += 1
            if recording.get_time() > self.time_max_threshold:
                time_max_list.append(recording)
            if recording.wild_point_count > 0:
                err_recs["wild_points"] += 1
                total_error_count["wild_points"] += recording.wild_point_count
            err_recs["missing_stroke"] += recording.missing_stroke
            if recording.wild_point_count > 0 or recording.missing_stroke:
                err_recs["total"] += 1
            if (
                recording.count_single_dots() > 0
                and raw_dataset["formula_in_latex"] not in self.dot_symbols
                and "dots" not in raw_dataset["formula_in_latex"]
            ):
                err_recs["single_dots"] += 1
                old_area = recording.get_area()
                tmp = [preprocessing.RemoveDots()]
                recording.preprocessing(tmp)
                new_area = recording.get_area()
                percentage = float(new_area) / float(old_area)
                if percentage < 1.0:
                    percentages.append(percentage)
            total_error_count["single_dots"] += recording.count_single_dots()

        time_max_list = sorted(time_max_list, key=lambda n: n.get_time(), reverse=True)

        self._write_data(
            symbols,
            err_recs,
            len(raw_datasets),
            total_error_count,
            percentages,
            time_max_list,
        )

Data analyzation metrics  Each algorithm works on a set of handwritings. They have to be applied like this:  >>> import hwrt.data_analyzation_metrics >>> from hwrt.handwritten_data import HandwrittenData >>> data_json = '[[{"time": 123, "x": 45, "y": 67}]]' >>> a = [{'is_in_testset': 0, ...    'formula_id': "31L", ...    'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data_json), ...    'formula_in_latex': 'A', ...    'id': "2953L"}, ...   {'is_in_testset': 0, ...    'formula_id': "31L", ...    'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data_json), ...    'formula_in_latex': 'A', ...    'id': "4037L"}, ...   {'is_in_testset': 0, ...    'formula_id': "31L", ...    'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data_json), ...    'formula_in_latex': 'A', ...    'id': "4056L"}] >>> creator_metric = Creator('creator.csv') >>> creator_metric(a) 100% # Core Library modules # Third party modules # Local modules # HandwrittenData and preprocessing are needed because of pickle # pylint: disable=W0611 # pylint: disable=W0611 Get metrics from a list of dictionaries. # Helper functions that are useful for some metrics Truncate the file and return the filename. # Truncate the file Sort a list of formulas by `id`, where `id` represents the accepted     formula id.      Parameters     ----------     raw_datasets : list of dictionaries         A list of raw datasets.      Examples     --------     The parameter `raw_datasets` has to be of the format      >>> from hwrt.handwritten_data import HandwrittenData     >>> data = '[[{"time": 123, "x": 45, "y": 67}]]'     >>> rd = [{'is_in_testset': 0,     ...        'formula_id': 31,     ...        'handwriting': HandwrittenData(raw_data_id=2953, raw_data_json=data),     ...        'formula_in_latex': 'A',     ...        'id': 2953},     ...       {'is_in_testset': 0,     ...        'formula_id': 31,     ...        'handwriting': HandwrittenData(raw_data_id=4037, raw_data_json=data),     ...        'formula_in_latex': 'A',     ...        'id': 4037},     ...       {'is_in_testset': 0,     ...        'formula_id': 31,     ...        'handwriting': HandwrittenData(raw_data_id=4056, raw_data_json=data),     ...        'formula_in_latex': 'A',     ...        'id': 4056}]     >>> _ = sort_by_formula_id(rd) # Only data analyzation calculation classes follow # Every class must have a __str__, __repr__ and __call__ function where # __call__ must take exactly one argument of type list of dictionaries # Every class must have a constructor which takes the filename as a parameter. # This filename has to be used to write the evaluation results # (preferably in CSV format) to this file. # prepare_file should be applied to every file in the constructor Analyze who created most of the data. # heading # Sort the data by highest value, descending # Write data to file Analyze how fast the points were in pixel/ms. # heading # Sort the data by highest value, descending # Write data to file Analyze how much distance in px is between strokes. # heading # Sort the data by highest value, descending # Write data to file For each recording: Store the average time between controll points of     one stroke / controll points of two different strokes. # noqa # noqa # Do the work # The recording might only have one point # The recording might only have one stroke Analyze the number of errors in the dataset. # in ms # TODO: Use the tags! Write all obtained data to a file.          Parameters         ----------         symbols : list of tuples (String, non-negative int)             List of all symbols with the count of recordings         err_recs : dictionary             count of recordings by error type         nr_recordings : non-negative int             number of recordings         total_error_count : dictionary             Count of all error that have happened by type         percentages : list             List of all recordings where removing the dots changed the size of             the bounding box.         time_max_list : list             List of all recordings where the recording time is above a             threshold. # noqa # Data", file=write_file) # Show errors # Initialize variables # Count errornous recordings # except symbols_with_dots # Count errors (one type of error might occur multiple times in # a single recording) # List with recordings that are over the time maximum