"""Classes that hold metamap output"""


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class BaseMetamap(object):
    """Holds the coordinate positions and allows for coordinate manipulations.

    Parameters
    ----------
    text : `str`
        The metamap processed text.
    start : `int`, optional, default: `0`
        The zero based start position of the text (so ``text`` might not be a
        whole phrase).
    """

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self, text, start=0):
        self._text = text
        self._start = int(start)
        self._end = (len(text) + self._start) - 1

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __eq__(self, other):
        """Compare ``BaseMetamap`` identity by text and start position.

        Parameters
        ----------
        other : `object`
            Object to compare with this instance.

        Returns
        -------
        result : `bool` or `NotImplementedType`
            ``True`` when both instances share the same ``text`` and
            ``start`` values. Returns ``NotImplemented`` for non
            ``BaseMetamap`` objects.
        """
        if not isinstance(other, BaseMetamap):
            return NotImplemented
        return self.text == other.text and self.start == other.start

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __hash__(self):
        """Hash ``BaseMetamap`` identity using text and start.

        Returns
        -------
        value : `int`
            Hash based on the ``(text, start)`` identity tuple used by ``__eq__``.
        """
        return hash((self.text, self.start))

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __len__(self):
        """The length of the text."""
        return (self._end - self.start) + 1

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @property
    def start(self):
        """The tag start position."""
        return self._start

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @property
    def end(self):
        """The tag end position."""
        return self._end

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @property
    def text(self):
        """The tag text position."""
        return self._text

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @property
    def len(self):
        """The length, kept for old code."""
        return len(self)


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class Utterance(BaseMetamap):
    """Representation of an utterance.

    Parameters
    ----------
    *args
        Arguments to ``umls_tools.metamap.objects.BaseMetamap``
    number : `int`, optional, default: `1`
        The number of the utterance.
    section : `str`, optional, default: `tx`
        The section type of the utterance.
    pmid : `int`, optional, default: `0`
        Any pubmed ID associated with the utterance.
    phrases : `list`, optional, default: `NoneType`
        Any phrases associated with the utterance.
    **kwargs
        Any keyword arguments passed to
        ``umls_tools.metamap.objects.BaseMetamap``
    """

    __hash__ = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(
        self, *args, number=1, section="tx", pmid=0, phrases=None, **kwargs
    ):
        super().__init__(*args, **kwargs)
        self.number = number
        self.section = section

        try:
            self.pmid = int(pmid)
        except ValueError:
            self.pmid = 0
        self.phrases = phrases or list()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def get_mapping_candidates(self):
        """A utility method for getting direct access to mapping candidates.

        Returns
        -------
        mapping_candidates : `list` of `umls_tools.metamap.objects.MappingCandidate`
            The mapping candidates, these will contain CUI objects.
        """
        mappings = []
        for p in self.phrases:
            for m in p.mappings:
                mappings.extend(m.candidates)
        return mappings

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __repr__(self):
        return (
            "<{0}(text='{1}',start='{2}',end='{3}', len='{4}',"
            " utt_number='{5}', utt_secion='{6}', pmid='{7}', "
            "no_of_phrases='{8}')>".format(
                self.__class__.__name__,
                self.text,
                self.start,
                self.end,
                self.len,
                self.number,
                self.section,
                self.pmid,
                len(self.phrases),
            )
        )


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class Phrase(BaseMetamap):
    """Representation of a phrase.

    Parameters
    ----------
    *args
        Arguments to ``umls_tools.metamap.objects.BaseMetamap``
    syntax_units : `list` or `NoneType`, optional, default: `NoneType`
        The syntax units of the phrase.
    mappings : `list` or `NoneType`, optional, default: `NoneType`
        The mappings of the phrase.
    candidates : `list` or `NoneType`, optional, default: `NoneType`
        The candidates of the phrase.
    **kwargs
        Any keyword arguments passed to
        ``umls_tools.metamap.objects.BaseMetamap``
    """

    __hash__ = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(
        self, *args, syntax_units=None, mappings=None, candidates=None, **kwargs
    ):
        super().__init__(*args, **kwargs)
        self.syntax_units = syntax_units or list()
        self.mappings = mappings or list()
        self.candidates = candidates or list()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __repr__(self):
        return (
            "<{0}(text='{1}',start='{2}',end='{3}', len='{4}',"
            " no_of_syntax_units='{5}', no_of_mappings='{6}', "
            "no_of_candidates='{7}')>".format(
                self.__class__.__name__,
                self.text,
                self.start,
                self.end,
                self.len,
                len(self.syntax_units),
                len(self.mappings),
                len(self.candidates),
            )
        )


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class Mapping(BaseMetamap):
    """Representation of a phrase.

    Parameters
    ----------
    *args
        Arguments to ``umls_tools.metamap.objects.BaseMetamap``
    syntax_units : `list` or `NoneType`, optional, default: `NoneType`
        The syntax units of the phrase.
    mappings : `list` or `NoneType`, optional, default: `NoneType`
        The mappings of the phrase.
    candidates : `list` of `umls_tools.metamap.objects.MappingCandidate` or `NoneType`, optional, default: `NoneType`
        The candidates of the mapping.
    **kwargs
        Any keyword arguments passed to
        ``umls_tools.metamap.objects.BaseMetamap``
    """

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    __hash__ = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(
        self, text, start, syntax_units=None, candidates=None, mapping_score=0
    ):
        """
        Representation of an utterance
        """

        # Initialise the coordinates
        super(Mapping, self).__init__(text, start)
        if syntax_units is None:
            syntax_units = []
        if candidates is None:
            candidates = []

        self.syntax_units = syntax_units
        self.candidates = candidates
        self.mapping_score = mapping_score

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __repr__(self):
        return (
            "<{0}(text='{1}',start='{2}',end='{3}', len='{4}',"
            " mapping_score='{5}', no_of_syntax_units='{6}', "
            "no_of_mapping_candidates='{7}')>".format(
                self.__class__.__name__,
                self.text,
                self.start,
                self.end,
                self.len,
                self.mapping_score,
                len(self.syntax_units),
                len(self.candidates),
            )
        )


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class MappingCandidate(BaseMetamap):
    """Representation of a mapping candidate.

    Parameters
    ----------
    *args
        Arguments to ``umls_tools.metamap.objects.BaseMetamap``
    cui : `umls_tools.objects.Cui`
        The UMLS concept identifier.
    mapping_score : `int`, optional, default: `0`
        The mapping score.
    is_head : `bool`, optional, default: `False`
        Not sure.
    is_overmatch : `bool`, optional, default: `False`
        Not sure.
    negated : `int`, optional, default: `0`
        Not sure.
    status : `int`, optional, default: `0`
        Not sure.
    match_words : `list` or `NoneType`, optional, default: `NoneType`
        The words that match in the mapping candidate.
    match_tokens : `list` or `NoneType`, optional, default: `NoneType`
        The matching tokens in the mapping candidate.
    **kwargs
        Any keyword arguments passed to
        ``umls_tools.metamap.objects.BaseMetamap``
    """

    __hash__ = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(
        self,
        text,
        cui,
        mapping_score=0,
        is_head=False,
        is_overmatch=False,
        negated=0,
        status=0,
        match_words=None,
        match_tokens=None,
        **kwargs,
    ):
        super().__init__(text, **kwargs)

        self.cui = cui
        self.mapping_score = mapping_score
        self.is_head = is_head
        self.is_overmatch = is_overmatch
        self.negated = negated
        self.status = status
        self.match_words = match_words or list()
        self.match_tokens = match_tokens or list()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __repr__(self):
        return (
            "<{0}(text='{1}',start='{2}',end='{3}', len='{4}',"
            " mapping_score='{5}', is_head='{6}', is_overmatch='{7}',"
            " negated='{8}', status='{9}' "
            "cuis='{10}')>".format(
                self.__class__.__name__,
                self.text,
                self.start,
                self.end,
                self.len,
                self.mapping_score,
                self.is_head,
                self.is_overmatch,
                self.negated,
                self.status,
                self.cui.cui,
            )
        )


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
class SyntaxUnit(BaseMetamap):
    """Representation of a syntax unit.

    Parameters
    ----------
    *args
        Arguments to ``umls_tools.metamap.objects.BaseMetamap``
    lex_match : `str` or `NoneType`, optional, default: `NoneType`
        The lexical matching text. if `NoneType`, then it defaults to the
        input text.
    syn_type : `str` or `NoneType`, optional, default: `NoneType`
        The syntax unit type.
    lex_cat : `str` or `NoneType`, optional, default: `NoneType`
        The lexical category type.
    **kwargs
        Any keyword arguments passed to
        ``umls_tools.metamap.objects.BaseMetamap``
    """

    __hash__ = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(
        self,
        *args,
        lex_match=None,
        syn_type=None,
        lex_cat=None,
        tokens=None,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)

        self.lex_match = lex_match or args[0]
        self.syn_type = syn_type
        self.lex_cat = lex_cat
        self.tokens = tokens or list()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __repr__(self):
        return (
            "<{0}(text='{1}',start='{2}',end='{3}', len='{4}',"
            " lex_match='{5}', syn_type='{6}', lex_cat='{7}', "
            "no_of_tokens='{8}')>".format(
                self.__class__.__name__,
                self.text,
                self.start,
                self.end,
                self.len,
                self.lex_match,
                self.syn_type,
                self.lex_cat,
                len(self.tokens),
            )
        )
