# -*- coding: utf-8 -*-

"""
PyVerbiste.

A Python library for conjugating verbs in French, English, Spanish, Italian, Portuguese and Romanian (mores soon).
It contains conjugation data generated by machine learning models using the python library mlconjug.
More information about mlconjug at https://pypi.org/project/mlconjug/


The conjugation data conforms to the XML schema defined by Verbiste.
More information on Verbiste at https://perso.b2b2c.ca/~sarrazip/dev/verbiste.html

"""

__author__ = 'SekouD'
__author_email__ = 'sekoud.python@gmail.com'

import copy
import xml.etree.ElementTree as ET
import codecs
from collections import OrderedDict
import pkg_resources
from _io import BufferedReader

RESOURCE_PACKAGE = __name__

LANGUAGES = ('default', 'fr', 'en', 'es', 'it', 'pt', 'ro')

VERBS_RESOURCE_PATH = {'fr': '/'.join(('data', 'verbiste', 'verbs-fr.xml')),
                       'it': '/'.join(('data', 'verbiste', 'verbs-it.xml')),
                       'es': '/'.join(('data', 'verbiste', 'verbs-es.xml')),
                       'en': '/'.join(('data', 'verbiste', 'verbs-en.xml')),
                       'pt': '/'.join(('data', 'verbiste', 'verbs-pt.xml')),
                       'ro': '/'.join(('data', 'verbiste', 'verbs-ro.xml')),}

CONJUGATIONS_RESOURCE_PATH = {'fr': '/'.join(('data', 'verbiste', 'conjugation-fr.xml')),
                              'it': '/'.join(('data', 'verbiste', 'conjugation-it.xml')),
                              'es': '/'.join(('data', 'verbiste', 'conjugation-es.xml')),
                              'en': '/'.join(('data', 'verbiste', 'conjugation-en.xml')),
                              'pt': '/'.join(('data', 'verbiste', 'conjugation-pt.xml')),
                              'ro': '/'.join(('data', 'verbiste', 'conjugation-ro.xml')),}

ABBREVS = ("1s", "2s", "3s", "1p", "2p", "3p")

PRONOUNS = {'fr': {'abbrev': ABBREVS,
                   'pronoun': ("je", "tu", "il (elle, on)", "nous", "vous", "ils (elles)")},
            'it': {'abbrev': ABBREVS,
                   'pronoun': ('io', 'tu', 'egli/ella', 'noi', 'voi', 'essi/esse')},
            'es': {'abbrev': ABBREVS,
                   'pronoun': ('yo', 'tú', 'él', 'nosotros', 'vosotros', 'ellos')},
            'en': {'abbrev': ABBREVS,
                   'pronoun': ('I', 'you', 'he/she/it', 'you', 'we', 'they')},
            'pt': {'abbrev': ABBREVS,
                   'pronoun': ('eu', 'tu', 'ele', 'nós', 'vós', 'eles')},
            'ro': {'abbrev': ABBREVS,
                   'pronoun': ('eu', 'tu', 'el/ea', 'noi', 'voi', 'ei/ele')}}

IMPERATIVE_PRONOUNS = {'fr': {'abbrev': ("2s :", "1p :", "2p :"),
                              'pronoun': ("tu", "nous", "vous")},
                       'it': None,
                       'es': {'abbrev': ("2s :", "3s :", "1p :", "2p :", "3p :"),
                              'pronoun': ('tú', 'él', 'nosotros', 'vosotros', 'ellos')},
                       'en': {'abbrev': ("2s :", "1p :", "2p :"),
                              'pronoun': ("", "let's", "")},
                       'pt': None,
                       'ro': None}

GENDER = {'fr': {'abbrev': ("ms :", "mp :", "fs :", "fp :"),
                 'pronoun': ("masculin singulier", "masculin pluriel", "feminin singulier", "feminin pluriel")},
          'it': None,
          'es': None,
          'en': None,
          'pt': None,
          'ro': None}

NEGATION = {'fr': 'ne',
            'it': 'non',
            'es': 'no',
            'en': "don't",
            'pt': 'não',
            'ro': 'nu'}


class Verbiste:
    """
    This is the class handling the Verbiste xml files.

    :param language: string.
        The language of the conjugator. The default value is fr for French.
        The allowed values are: fr, en, es, it, pt, ro

    """

    def __init__(self, language='default'):
        if language not in LANGUAGES:
            raise ValueError('Unsupported language.\nThe allowed languages are fr, en, es, it, pt, ro')
        if language == 'default':
            self.language = 'fr'
        else:
            self.language = language
        self.verbs = {}
        self.conjugations = OrderedDict()
        verbs_file = pkg_resources.resource_stream(RESOURCE_PACKAGE, VERBS_RESOURCE_PATH[self.language])
        self._load_verbs(verbs_file)
        conjugations_file = pkg_resources.resource_stream(RESOURCE_PACKAGE, CONJUGATIONS_RESOURCE_PATH[self.language])
        self._load_conjugations(conjugations_file)
        self.templates = sorted(self.conjugations.keys())
        self.model = None

    def _load_verbs(self, verbs_file):
        """
        Load and parses the verbs from xml file.

        :param verbs_file: string or path object.
            Path to the verbs xml file.
        """
        if isinstance(verbs_file, BufferedReader):
            verbs_dic = self._parse_verbs(verbs_file)
        else:
            with codecs.open(verbs_file, "r", encoding='utf-8') as file:
                verbs_dic = self._parse_verbs(file)
        self.verbs = verbs_dic

    def _parse_verbs(self, file):
        """
        Parses XML file

        :param file: FileObject.
            XML file containing the verbs.
        :return: OrderedDict.
            An OrderedDict containing the verb and its template for all verbs in the file.
        """
        verbs_dic = {}
        xml = ET.parse(file)
        for verb in xml.findall("v"):
            verb_name = verb.find("i").text
            template = verb.find("t").text
            index = - len(template[template.index(":") + 1:])
            root = verb_name[:index]
            verbs_dic[verb_name] = {"template": template, "root": root}
        return verbs_dic

    def _load_conjugations(self, conjugations_file):
        """
        Load and parses the conjugations from xml file.

        :param conjugations_file: string or path object.
            Path to the conjugation xml file.
        """
        if isinstance(conjugations_file, BufferedReader):
            conjugations_dic = self._parse_conjugations(conjugations_file)
        else:
            with codecs.open(conjugations_file, "r", encoding='utf-8') as file:
                conjugations_dic = self._parse_conjugations(file)
        self.conjugations = conjugations_dic

    def _parse_conjugations(self, file):
        """
        Parses XML file

        :param file: FileObject.
            XML file containing the conjugation templates
        :return: OrderedDict
            An OrderedDict containing all the conjugation templates in the file.
        """
        conjugations_dic = {}
        xml = ET.parse(file)
        for template in xml.findall("template"):
            template_name = template.get("name")
            conjugations_dic[template_name] = OrderedDict()
            for mood in list(template):
                conjugations_dic[template_name][mood.tag] = OrderedDict()
                for tense in list(mood):
                    conjugations_dic[template_name][mood.tag][tense.tag] = self._load_tense(tense)
        return conjugations_dic

    def _load_tense(self, tense):
        """

        :param tense: string.
            The current tense being processed.
        :return: list.
            List of conjugated suffixes.
        """
        persons = list(tense)
        if not persons:
            conjug = None
        elif len(persons) == 1:
            if persons[0].find("i") is not None:
                conjug = persons[0].find("i").text
            else:
                conjug = None
        else:
            conjug = [(pers, term.find("i").text if term.find("i") is not None else None) for pers, term in
                      enumerate(persons)]
        return conjug

    def get_verb_info(self, verb):
        """
        Gets verb information and returns a VerbInfo instance.

        :param verb: string.
            Verb to conjugate.
        :return: VerbInfo object or None.
        """
        if verb not in self.verbs.keys():
            return None
        else:
            infinitive = verb
            root = self.verbs[verb]['root']
            template = self.verbs[verb]['template']
            verb_info = VerbInfo(infinitive, root, template)
            return verb_info

    def get_conjug_info(self, template):
        """
        Gets conjugation information corresponding to the given template.

        :param template: string.
            Name of the verb ending pattern.
        :return: OrderedDict or None.
        """
        if template not in self.conjugations.keys():
            return None
        else:
            info = copy.deepcopy(self.conjugations[template])
            return info


class VerbInfo(object):
    """
    This class defines the Verbiste verb information structure.

    :param infinitive: string.
        Infinitive form of the verb.
    :param root: string.
        Lexical root of the verb.
    :param template: string.
        Name of the verb ending pattern.

    """
    __slots__ = ('infinitive', 'root', 'template')

    def __init__(self, infinitive, root, template):
        self.infinitive = infinitive
        self.root = root
        self.template = template

    def __eq__(self, other):
        if self.infinitive == other.infinitive and self.root == other.root and self.root == other.root:
            return True
        else:
            return False


class Verb(object):
    """
    This class defines the Verb Object.

    :param verb_info: VerbInfo Object.
    :param conjug_info: OrderedDict.
    :param subject: srting.
        Toggles abbreviated or full pronouns.
        The default value is 'abbrev'.
        Select 'pronoun' for full pronouns.

    """

    def __init__(self, verb_info, conjug_info, subject='abbrev'):
        self.name = verb_info.infinitive
        self.verb_info = verb_info
        self.conjug_info = conjug_info
        self.subject = subject
        self._load_conjug()
        self.language = 'default'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.
        This is the generic version of this method.
        It does not add personal pronouns to the conjugated forms.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6:
                            key = ABBREVS[pers]
                        else:
                            key = pers
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    self.conjug_info[mood][tense_name] = self.verb_info.root + persons
        return


class VerbFr(Verb):
    """
    This class defines the French Verb Object.

    """

    language = 'fr'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6:
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'past-participle':
                            key = GENDER[self.language][self.subject][pers]
                        elif tense_name == 'imperative-present':
                            key = IMPERATIVE_PRONOUNS[self.language][self.subject][pers]
                        else:
                            key = term
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    self.conjug_info[mood][tense_name] = self.verb_info.root + persons
        return


class VerbEn(Verb):
    """
    This class defines the English Verb Object.

    """

    language = 'en'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6:
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'infinitive-present':
                            key = 'to'
                        elif tense_name == 'imperative-present':
                            key = IMPERATIVE_PRONOUNS[self.language][self.subject][pers]
                        else:
                            key = term
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    if tense_name == 'infinitive-present':
                        prefix = 'to '
                    else:
                        prefix = ''
                    self.conjug_info[mood][tense_name] = prefix + self.verb_info.root + persons
        return


class VerbEs(Verb):
    """
    This class defines the Spanish Verb Object.

    """

    language = 'es'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6:
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'Imperativo':
                            key = IMPERATIVE_PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'Imperativo-non':
                            key = IMPERATIVE_PRONOUNS[self.language][self.subject][pers] + ' ' + NEGATION[self.language]
                        else:
                            key = pers
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    self.conjug_info[mood][tense_name] = self.verb_info.root + persons
        return


class VerbIt(Verb):
    """
    This class defines the Italian Verb Object.

    """

    language = 'it'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6 and not tense_name.startswith('Imperativo'):
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'Imperativo':
                            key = pers
                        elif tense_name == 'Imperativo-non':
                            key = NEGATION[self.language]
                        else:
                            key = pers
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    self.conjug_info[mood][tense_name] = self.verb_info.root + persons
        return


class VerbPt(Verb):
    """
    This class defines the Portuguese Verb Object.

    """

    language = 'pt'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6 and not tense_name.startswith('Imperativo'):
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'Imperativo-Afirmativo':
                            key = pers
                        elif tense_name == 'Imperativo-Negativo':
                            key = NEGATION[self.language]
                        else:
                            key = pers
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    self.conjug_info[mood][tense_name] = self.verb_info.root + persons
        return


class VerbRo(Verb):
    """
    This class defines the Romanian Verb Object.

    """

    language = 'ro'

    def _load_conjug(self):
        """
        Populates the inflected forms of the verb.

        """
        for mood, tense in self.conjug_info.items():
            for tense_name, persons in tense.items():
                if isinstance(persons, list):
                    persons_dict = OrderedDict()
                    for pers, term in persons:
                        if len(persons) == 6:
                            key = PRONOUNS[self.language][self.subject][pers]
                        elif tense_name == 'Imperativ':
                            key = pers
                        elif tense_name == 'Imperativ-Negativ':
                            key = NEGATION[self.language]
                        else:
                            key = pers
                        if term is not None:
                            persons_dict[key] = self.verb_info.root + term
                        else:
                            persons_dict[key] = None
                    self.conjug_info[mood][tense_name] = persons_dict
                elif isinstance(persons, str):
                    if tense_name == 'Infinitiv':
                        prefix = 'a '
                    else:
                        prefix = ''
                    self.conjug_info[mood][tense_name] = prefix + self.verb_info.root + persons
        return


if __name__ == "__main__":
    pass
