#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = "Christian Heider Nielsen"
__doc__ = r"""

           Created on 25/03/2020
           """

import collections
import hashlib
import re
import sys
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Iterable, OrderedDict, Sequence

import numpy

__all__ = ["SplitEnum", "SplitIndexer", "train_valid_test_split", "select_split"]

from sorcery import assigned_names


class SplitEnum(Enum):
    """
    Split Enum class for selecting splits"""

    (training, validation, testing) = assigned_names()


class SplitIndexer:
    """Splits dataset in to 3 parts based on percentages, returns indices for the data set sequence"""

    default_split_names = {i: i.value for i in SplitEnum}

    def __init__(
        self,
        dataset_length: int,
        training: float = 0.7,
        validation: float = 0.2,
        testing: float = 0.1,
    ):
        self.total_num = dataset_length
        splits = numpy.array([training, validation, testing])
        self.normalised_split = splits / sum(splits)
        (
            self.training_percentage,
            self.validation_percentage,
            self.testing_percentage,
        ) = self.normalised_split
        self.training_num, self.validation_num, self.testing_num = self.unnormalised(
            dataset_length
        )

    def shuffled_indices(self) -> dict:
        """description"""
        split_indices = numpy.random.permutation(self.total_num).tolist()

        return {
            SplitEnum.training: self.select_train_indices(split_indices),
            SplitEnum.validation: self.select_validation_indices(split_indices),
            SplitEnum.testing: self.select_testing_indices(split_indices),
        }

    def select_train_indices(self, ind: Sequence) -> Sequence:
        """description"""
        return ind[: self.training_num]

    def select_validation_indices(self, ind: Sequence) -> Sequence:
        """description"""
        if self.validation_num:
            if self.testing_num:
                return ind[self.training_num : -self.testing_num]
            return ind[self.training_num :]
        return []

    def select_testing_indices(self, ind: Sequence) -> Sequence:
        """description"""
        if self.testing_num:
            return ind[-self.testing_num :]
        return []

    def unnormalised(self, num: int, floored: bool = True) -> numpy.ndarray:
        """

        :param num:
        :type num:
        :param floored:
        :type floored:
        :return:
        :rtype:"""
        unnorm = self.normalised_split * num
        if floored:
            unnorm = numpy.floor(unnorm)
        return unnorm.astype(int)

    def __repr__(self) -> str:
        return str(
            {k: n for k, n in zip(self.default_split_names, self.normalised_split)}
        )

    def select_shuffled_split_indices(self, split: SplitEnum, seed: int = 0) -> object:
        """description"""
        numpy.random.seed(seed)
        split_indices = numpy.random.permutation(self.total_num).tolist()

        if split == SplitEnum.training:
            return self.select_train_indices(split_indices)
        elif split == SplitEnum.validation:
            return self.select_validation_indices(split_indices)
        elif split == SplitEnum.testing:
            return self.select_testing_indices(split_indices)
        elif split is None:
            return split_indices
        raise NotImplementedError


def train_valid_test_split(
    categories: Dict[str, Iterable[Path]],
    *,
    validation_percentage: float = 15,  # TODO: ACCEPT AND SQUEEZE ZERO-HUNDRED TO ZERO-ONE range!
    testing_percentage: float = 0,
    verbose: bool = False,
) -> OrderedDict:
    """
    Magic hashing

    :param verbose:
    :type verbose:
    :param categories:
    :param testing_percentage:
    :param validation_percentage:
    :return:"""
    result = collections.OrderedDict()

    if verbose:
        print(categories)

    for c, vs in categories.items():
        training_images = []
        testing_images = []
        validation_images = []

        for file_name in vs:
            b_rep = bytes(re.sub(r"_nohash_.*$", "", f"{c}{file_name.name}"), "utf8")
            percentage_hash = (
                int(hashlib.sha1(b_rep).hexdigest(), 16) % (sys.maxsize + 1)
            ) * (100.0 / sys.maxsize)
            if percentage_hash < validation_percentage + testing_percentage:
                if percentage_hash < testing_percentage:
                    testing_images.append(file_name)
                else:
                    validation_images.append(file_name)
            else:
                training_images.append(file_name)

        result[c] = {
            SplitEnum.training: training_images,
            SplitEnum.validation: validation_images,
            SplitEnum.testing: testing_images,
        }

    return result


def select_split(
    data_cat_split: Dict[Any, Dict[SplitEnum, Sequence]],
    split: SplitEnum,
    verbose: bool = False,
) -> Dict[Any, Sequence]:
    """

    :param verbose:
    :type verbose:
    :param data_cat_split:
    :type data_cat_split:
    :param split:
    :type split:
    :return:
    :rtype:"""
    data = {k: [] for k in data_cat_split.keys()}
    if verbose:
        print(data_cat_split)
    for k, v in data_cat_split.items():
        if verbose:
            print(v[split])
        for item in v[split]:
            data[k].append(item)
    return data


if __name__ == "__main__":

    def asd():
        """description"""
        split_by_p = SplitIndexer(100)
        print(split_by_p.default_split_names)
        print(split_by_p.shuffled_indices())
        print(split_by_p.select_shuffled_split_indices(SplitEnum.training))
        a = split_by_p.select_shuffled_split_indices(None)
        print(a, len(a))

    def uihsad():
        """description"""
        a = None
        if a:
            a = SplitEnum(a)
        print(a)

    uihsad()

    def iajsd():
        print(SplitIndexer(521))
        print(SplitIndexer(2512).unnormalised(123))
