import html
from collections import namedtuple
from pathlib import Path
from typing import List, Dict

import requests
from bs4 import BeautifulSoup
from lxml import etree
from lxml.etree import XPath

Emoji = namedtuple('Emoji', 'char name')


class EmojiExtractor(object):

    def __init__(self):
        self.all_emojis = self.fetch_emoji_list()
        self.annotations = self.fetch_annotations()
        self.base_emojis = self.fetch_base_emojis()

    def fetch_emoji_list(self: 'EmojiExtractor') -> List[Emoji]:
        print('Downloading list of all emojis')

        data = requests.get(
            'https://unicode.org/emoji/charts-14.0/full-emoji-list.html',
            timeout=120
        )  # type: requests.Response

        html = BeautifulSoup(data.text, 'lxml')

        emojis = []
        for row in html.find('table').find_all('tr'):
            if not row.th:
                emoji = row.find('td', {'class': 'chars'}).string
                description = row.find('td', {'class': 'name'}).string.replace('⊛ ', '')
                emojis.append(Emoji(emoji, description))

        return emojis

    def fetch_annotations(self: 'EmojiExtractor') -> Dict[chr, List[str]]:
        print('Downloading annotations')

        data = requests.get(
            'https://raw.githubusercontent.com/unicode-org/cldr/latest/common/annotations/en.xml',
            timeout=60
        )  # type: requests.Response

        xpath = XPath('./annotations/annotation[not(@type="tts")]')
        return {element.get('cp'): element.text.split(' | ')
                for element in xpath(etree.fromstring(data.content))}

    def fetch_base_emojis(self: 'EmojiExtractor') -> List[chr]:
        print('Downloading list of human emojis...')

        data = requests.get(
            'https://unicode.org/Public/14.0.0/ucd/emoji/emoji-data.txt',
            timeout=60
        )  # type: requests.Response

        started = False
        emojis = []
        for line in data.text.split('\n'):
            if not started and line != '# All omitted code points have Emoji_Modifier_Base=No ':
                continue
            started = True
            if line == '# Total elements: 132':
                break
            if line and not line.startswith('#'):
                emojis.extend(self.resolve_character_range(line.split(';')[0].strip()))

        return emojis

    def resolve_character_range(self, line: str) -> List[str]:
        try:
            (start, end) = line.split('..')
            return [chr(char) for char in range(int(start, 16), int(end, 16) + 1)]
        except ValueError:
            return [self.resolve_character(line)]

    def resolve_character(self, string: str) -> str:
        return "".join(chr(int(character, 16)) for character in string.split(' '))

    def write_symbol_file(self: 'EmojiExtractor'):
        print('Writing collected emojis to symbol file')
        with Path('../picker/data/emojis.csv').open('w') as symbol_file:
            for entry in self.compile_entries(self.all_emojis):
                symbol_file.write(entry + "\n")

    def compile_entries(self: 'EmojiExtractor', emojis: List[Emoji]) -> List[str]:
        annotated_emojis = []
        for emoji in emojis:
            entry = f"{emoji.char} {html.escape(emoji.name)}"
            if emoji.char in self.annotations:
                entry += f" <small>({html.escape(', '.join([annotation for annotation in self.annotations[emoji.char] if annotation != emoji.name]))})</small>"
            annotated_emojis.append(entry)
        return annotated_emojis

    def write_metadata_file(self: 'EmojiExtractor'):
        print('Writing metadata to metadata file')
        with Path('../picker/copyme.py').open('w') as metadata_file:
            metadata_file.write('skin_tone_selectable_emojis={\'')
            metadata_file.write('\', \''.join(self.base_emojis))
            metadata_file.write('\'}\n')

    def extract(self: 'EmojiExtractor'):
        self.write_symbol_file()
        self.write_metadata_file()

[((11, 8, 11, 40), 'collections.namedtuple', 'namedtuple', ({(11, 19, 11, 26): '"""Emoji"""', (11, 28, 11, 39): '"""char name"""'}, {}), "('Emoji', 'char name')", False, 'from collections import namedtuple\n'), ((24, 15, 27, 9), 'requests.get', 'requests.get', (), '', False, 'import requests\n'), ((29, 15, 29, 47), 'bs4.BeautifulSoup', 'BeautifulSoup', ({(29, 29, 29, 38): 'data.text', (29, 40, 29, 46): '"""lxml"""'}, {}), "(data.text, 'lxml')", False, 'from bs4 import BeautifulSoup\n'), ((43, 15, 46, 9), 'requests.get', 'requests.get', (), '', False, 'import requests\n'), ((48, 16, 48, 67), 'lxml.etree.XPath', 'XPath', ({(48, 22, 48, 66): '"""./annotations/annotation[not(@type="tts")]"""'}, {}), '(\'./annotations/annotation[not(@type="tts")]\')', False, 'from lxml.etree import XPath\n'), ((55, 15, 58, 9), 'requests.get', 'requests.get', (), '', False, 'import requests\n'), ((50, 37, 50, 67), 'lxml.etree.fromstring', 'etree.fromstring', ({(50, 54, 50, 66): 'data.content'}, {}), '(data.content)', False, 'from lxml import etree\n'), ((85, 13, 85, 46), 'pathlib.Path', 'Path', ({(85, 18, 85, 45): '"""../picker/data/emojis.csv"""'}, {}), "('../picker/data/emojis.csv')", False, 'from pathlib import Path\n'), ((100, 13, 100, 40), 'pathlib.Path', 'Path', ({(100, 18, 100, 39): '"""../picker/copyme.py"""'}, {}), "('../picker/copyme.py')", False, 'from pathlib import Path\n')]