import abc

import mmh3

from sparclur._metaclass import Meta
from sparclur._parser import Parser, TRACER
from typing import List, Dict, Any


class Tracer(Parser, metaclass=Meta):
    """
        Abstract class for wrapping the process of getting error messages from a parser.

        This abstract class standardizes what's expected from a parser in order to be used with the Parser Trace
        Comparator module (PTC).
    """

    @abc.abstractmethod
    def __init__(self, doc, temp_folders_dir, skip_check, timeout, hash_exclude, *args, **kwargs):
        super().__init__(doc=doc,
                         temp_folders_dir=temp_folders_dir,
                         skip_check=skip_check,
                         timeout=timeout,
                         hash_exclude=hash_exclude,
                         *args,
                         **kwargs)
        trace_apis = {'can_trace': '(Property) Boolean for whether or not trace collection capability is present',
                      'validate_tracer': '(Property) Determines the PDF validity for the tracing process',
                      'messages': '(Property) The list of raw messages from the parser',
                      'cleaned': '(Property) A dictionary of normalized messages with their counts'}
        self._api.update(trace_apis)
        self._messages: List[str] = None
        self._cleaned: Dict[str, int] = None
        self._can_trace: bool = None

    @abc.abstractmethod
    def validate_tracer(self) -> Dict[str, Any]:
        """
        Performs a validity check for this tracer.

        Returns
        -------
        bool
        """
        pass

    @property
    def validity(self):
        if TRACER not in self._validity:
            _ = self.validate_tracer
        return super().validity

    @property
    def sparclur_hash(self):
        if TRACER not in self._sparclur_hash and TRACER not in self._sparclur_hash.excluded:
            cleaned_messages = self.cleaned
            hashes = set(mmh3.hash128(message) for message in cleaned_messages.keys())
            self._sparclur_hash._add_hash(TRACER, hashes)
        return super().sparclur_hash

    @property
    def can_trace(self):
        if self._can_trace is None:
            self._can_trace = self._check_for_tracer()
        return self._can_trace

    @can_trace.deleter
    def can_trace(self):
        self._can_trace = None

    @abc.abstractmethod
    def _check_for_tracer(self) -> bool:
        """
        Performs a check for the presence of the tracer.
        Returns
        -------
        bool
        """
        pass

    @abc.abstractmethod
    def _parse_document(self):
        """
        Parses the document to retrieve the error and warning messages generated by the parser, if any.
        """
        pass

    @property
    def messages(self):
        """
        Return the error and warnings for the document passed into the Parser instance.

        Returns
        -------
        List[str]
            The list of all raw messages from the parser over the given document
        """
        assert self._skip_check or self._check_for_tracer(), "%s not found" % self.get_name()

        if self._messages is None:
            self._parse_document()

        return self._messages

    @messages.deleter
    def messages(self):
        """Clear the parsed document messaging. Also clears the cleaned messages."""
        self._messages = None
        self._cleaned = None

    @property
    def cleaned(self):
        """
        Return a normalized collection of the warnings and errors with occurrence counts.

        Returns
        -------
        Dict[str, int]
            A dictionary with each normalized message as the key and the occurrence count as the value
        """
        if self._messages is None:
            self._parse_document()

        if self._cleaned is None:
            self._scrub_messages()

        return self._cleaned

    @cleaned.deleter
    def cleaned(self):
        """Clear the cleaned messages."""
        self._cleaned = None

    @abc.abstractmethod
    def _clean_message(self, err):
        """
        Normalizes a given message generated by the parser.

        Parameters
        ----------
        err : str
            An observed message from the parser to be normalized
        Returns
        -------
        str
            The normalized message
        """
        pass

    @abc.abstractmethod
    def _scrub_messages(self):
        """
        Normalizes all of the observed messages for the provided document.
        """
        pass
