import bs4
import logging
import re
import os
import jinja2

class ParsingFailedEception(Exception):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class CompetitionParser:

    def __init__(self):
        self._l = logging.getLogger(__name__)

        self._partner = ''
        self._partnerin = ''
        self._date = ''
        self._title = ''
        self._number = ''
        self._group = ''
        self._class = ''
        self._section = ''
        self._ort = ''
        self._verein = ''
        self._telefon = ''

        self._reName = re.compile('Neue Meldung für (.*) / (.*)!')
        self._reDate = re.compile('([0-9]+)\\.([0-9]+)\\.([0-9]+)')
        self._reNumber = re.compile('Turnier: ([0-9]+)')
        self._rePhone = re.compile('Telefon: (\\+?[0-9 /-]+)')
        self._rePlace = re.compile('Ort: (.*), (.*)')
        self._reCompetition = re.compile('(.*) ([A-ES]) ((?:Std)|(?:Lat)|(?:Kombi))')
        self._reWDSFCompetition = re.compile('WDSF Open ([a-zA-Z0-9 ]*) ((?:Standard)|(?:Latin))(?: *-.*)?')
        self._reWDSFCompetitionReversed = re.compile('WDSF Open ((?:Standard)|(?:Latin)) ([a-zA-Z0-9 ]*)(?: *-.*)?')

        self._reCleaningString = re.compile('[^a-z0-9-]')
        self._reDashes = re.compile('-+')
    
    def parseMail(self, body: str):
        parser = bs4.BeautifulSoup(body, 'html.parser')
        self._getNames(parser.h2)
        self._parseTable(parser.table)

    def _getNames(self, h2):
        matcher = self._reName.match(h2.string)
        if matcher is None:
            self._l.error('Parsing of header "%s" failed.', h2)
            raise ParsingFailedEception('Header could not be successfully parsed')
        self._partner = matcher.group(1)
        self._partnerin = matcher.group(2)
    
    def _parseTable(self, table):
        def parseDate(date):
            match = self._reDate.fullmatch(date)
            if match is None:
                raise ParsingFailedEception('Cannot parse date %s in mail' % date)
            self._date = f'{match.group(3)}-{match.group(2)}-{match.group(1)}'
        
        def parseNumber(content):
            match = self._reNumber.fullmatch(content)
            if match is None:
                raise ParsingFailedEception(f'Cannot parse the turnier number in field {content}')
            self._number = match.group(1)

        def parseCompetition(competition):
            def parseDTVCompetition():
                match = self._reCompetition.fullmatch(competition)
                if match is None:
                    raise ParsingFailedEception(f'Cannot parse the competition line {competition}')
                self._group = match.group(1)
                self._class = match.group(2)
                self._section = match.group(3)
            
            def parseWDSFCompetition():
                def checkMatch(match):
                    if match is None:
                        raise ParsingFailedEception(f'Cannot parse WDSF competition line')

                def parseForward():
                    match = self._reWDSFCompetition.fullmatch(competition.strip())
                    checkMatch(match)
                    return match.group(2), match.group(1)

                def parseReverse():
                    match = self._reWDSFCompetitionReversed.fullmatch(competition.strip())
                    checkMatch(match)
                    return match.group(1), match.group(2).strip()

                groupMap = {
                    'juvenile i': 'Kin',
                    'juvenile ii': 'Kin',
                    'junior i': 'Jun 1',
                    'junior ii': 'Jun 2',
                    'youth': 'Jug',
                    'adult': 'Hgr',
                    'senior i': 'Mas I',
                    'senior ii': 'Mas II',
                    'senior iii': 'Mas III',
                    'senior iv': 'Mas IV',
                    'senior v': 'Mas V',
                }
                sectionMap = {
                    'standard': 'Std',
                    'latin': 'Lat',
                }
                funs = [parseForward, parseReverse]
                for fun in funs:
                    try:
                        sec, grp = fun()
                        self._group = groupMap.get(grp.lower(), grp)
                        self._class = 'WDSF Open'
                        self._section = sectionMap.get(sec.lower(), sec)
                        return
                    except ParsingFailedEception:
                        pass
                
                raise ParsingFailedEception('Neither forward not reversed parsing worked')

            functions = [parseDTVCompetition, parseWDSFCompetition]
            for fun in functions:
                try:
                    fun()
                    return
                except ParsingFailedEception:
                    pass
            raise ParsingFailedEception(f'No more matchers for the competition line "{competition}" were left.')
        
        def parsePlace(place):
            match = self._rePlace.fullmatch(place)
            if match is None:
                raise ParsingFailedEception(f'Cannot parse the place entry {place}')
            self._verein = match.group(1)
            self._ort = match.group(2)
        
        def parsePhone(phone):
            match = self._rePhone.fullmatch(phone)
            if match is None:
                raise ParsingFailedEception(f'Cannot parse the phone line {phone}')
            self._telefon = match.group(1)
        
        tds = table('td')
        parseDate(tds[0].string.strip())
        self._title = tds[1].string.strip()
        parseNumber(tds[2].string.strip())
        parseCompetition(tds[3].string.strip())
        parsePlace(tds[4].string.strip())
        parsePhone(tds[5].string.strip())

    def _cleanName(self, name: str) -> str:
        cleanedName = name.lower()
        cleanedName = re.sub('ä', 'ae', cleanedName)
        cleanedName = re.sub('ö', 'oe', cleanedName)
        cleanedName = re.sub('ü', 'ue', cleanedName)
        cleanedName = re.sub('ß', 'ss', cleanedName)
        cleanedName = re.sub(self._reCleaningString, '-', cleanedName)
        cleanedName = re.sub(self._reDashes, '-', cleanedName)
        return cleanedName.lower()

    def getFilename(self, prefix: str) -> str:
        namePartner = self._cleanName(self._partner)
        namePartnerin = self._cleanName(self._partnerin)
        competition = f'{self._group} {self._class} {self._section}'
        competitionName = self._cleanName(competition)
        ort = self._cleanName(self._ort)

        filename = f'{self._date}-{ort}-{namePartner}-{namePartnerin}-{competitionName}.md'

        return os.path.join(
            prefix,
            self._date[0:4],
            re.sub(self._reDashes, '-', filename)
        )   
    
    def getContent(self) -> str:
        with open(os.path.join(os.path.dirname(__file__), 'contenttemplate.md.tmpl')) as fp:
            tpl = fp.read()
        j2 = jinja2.Template(tpl)
        vars = {
            'date': self._date,
            'partner': self._partner,
            'partnerin': self._partnerin,
            'verein': self._verein,
            'ort': self._ort,
            'telefon': self._telefon,
            'group': self._group,
            'class': self._class,
            'section': self._section,
            'title': self._title,
            'number': self._number,
        }
        return j2.render(**vars)