#! /usr/bin/env python3
from polyglot.text import Text
from polyglot.downloader import downloader
import iso639
import re 
from typing import List, Optional, Set, Dict
import sys, os
import subprocess
import unidecode
import emotions

SENTIMENT_COEFF = 0.5
URL_FILTER = re.compile('<[^>]*>')
EMOJI_FILTER = re.compile(':[a-z\u00C0-\u017F_\+\-]*:')
BLANK_LINE_FILTER = re.compile('^\\s*$')
JOIN_FILTER = re.compile('(<.*>)* joined the channel')
NUMERIC_FILTER = re.compile("[0-9]+")
PLUS_FILTER = re.compile('\+')

def normalize_emojis(text: str) -> str:
    emojis = EMOJI_FILTER.findall(text)
    translit = map(unidecode.unidecode, emojis)
    for emoji, trans in zip(emojis, translit):
        text = text.replace(emoji, trans)
    return text

def fill_accent(text):
    p1 = subprocess.Popen(['echo', text.encode('iso8859-2', errors='ignore')], stdout=subprocess.PIPE)
    p2 = subprocess.Popen(['./cz_accent'],
            stdin=p1.stdout, stdout=subprocess.PIPE)
    p1.stdout.close()
    stdout, _ = p2.communicate()
    result = stdout.decode('iso8859-2')
    return normalize_emojis(result)


class Preprocessor:
    def __init__(self):
        self.reglist = [ URL_FILTER, EMOJI_FILTER, JOIN_FILTER, BLANK_LINE_FILTER,
                NUMERIC_FILTER, PLUS_FILTER]
        self.truncate_short_text = True
        self.underline_for_space = False

    def preprocess(self, text, clear_punctuation=False, clear_emojis=True):
        for regex in self.reglist:
            if not clear_emojis and regex == EMOJI_FILTER:
                continue
            while True:
                newText = regex.sub(" ", text)
                if newText == text:
                    text = newText
                    break;
                text = newText
        if self.underline_for_space:
            text = text.replace('_', " ")
        if clear_punctuation:
            text = text.translate(text.maketrans(' ', ' ', string.punctuation))
        text = text.strip()
        if len(text) <= 5 and self.truncate_short_text:
            return ""
        return text.lower() if [i for i in text if i.isalnum()] else ""

class SentimentAnalyzer:
    def __init__(self):
        self._preprocessor : "Preprocessor" = Preprocessor()
        self._preprocessor.truncate_short_text = False
        self._preprocessor.underline_for_space = True

    @staticmethod
    def __to_iso639(lang: str) -> Optional[str]:
        if not lang or lang == "???":
            return None
        if lang in {"csd", "skd"}:
            return lang[:-1]
        return lang


    def set_params(self, param_list):
        assert len(param_list) == 4
        emotions.NEG_EMOJI_SCORE = param_list[0]
        emotions.NEG_EMO_COEFF = param_list[1]
        emotions.RAW_COEFF = param_list[2]
        SENTIMENT_COEFF = param_list[3]

    @staticmethod
    def __is_neutral(positive_score: float, negative_score: float) -> bool:
        return abs(positive_score - negative_score) < SENTIMENT_COEFF

    def __get_sentiment_czech(self, text: str, debug=False, text_obj=[]) -> float:
        self._preprocessor.underline_for_space = False
        text = self._preprocessor.preprocess(text, clear_emojis=False)
        self._preprocessor.underline_for_space = True
        text = fill_accent(text)
        emot = emotions.Text(text)
        text_obj.append(emot)
        result = dict(emot.possibleEmotions.items())
        sentiment_map = {'p' : 1.0, 'n' : -1.0, 'z' : 0.0}
        if self.__is_neutral(result["p"], result["n"]):
            return 0.0, text
        result_sentiment = max(result.items(), key=lambda x: x[1])[0]
        if debug:
            return sentiment_map[result_sentiment], text
        return sentiment_map[result_sentiment], None

    def detect_from_message(self, text: str, language: Optional[str] = None,
            debug=False, text_obj=[]) -> float:
        if text.strip() == "":
            return 0.0, None if not debug else 0.0, None
        if language in ["cs", "csd"]:
            return self.__get_sentiment_czech(text, debug, text_obj)
        text = self._preprocessor.preprocess(text, clear_emojis=False)
        lang_code = SentimentAnalyzer.__to_iso639(language)
        text_obj: "Text" = Text(text, lang_code)
        try:
            result: float = text_obj.polarity
        except ValueError as verr:
            module: str = str(verr).split()[-1].strip('\'')
            if module == "sentiment2.un" or module == "index":
                return -100.0, None if not debug else -100.0, None
            print(f"Missing polyglot module: {module}")
            print(f"On text: {text}")
            downloader.download(module)
            return self.detect_from_message(text)
        except ZeroDivisionError as e:
            return 0.0, None if not debug else 0.0, text_obj
        return result if not debug else result, text_obj
            

    def detect_from_channel(self, channel: "Channel", language: Optional[str] = None) -> float:
        if len(channel.history) == 0:
            return 0.0
        return sum(map(lambda x: self.detect_from_message(x.get("text"), language), \
                channel.history)) / len(channel.history)


    def detect_from_channel_verbose(self, channel: "Channel", language: Optional[str] = None, output=sys.stdout):
        if len(channel) == 0:
            return 0.0, None
        vals = []
        for message in channel:
            sent_val, text_obj = self.detect_from_message(message.get("text"), language, debug=True)
            vals.append(sent_val)
            output.write(f"{message.get('text')}: {sent_val} {language}\n")
            output.write("{:<16}{}".format("Word", "Polarity")+"\n"+"-"*30+"\n")
            if text_obj is not None:
                for word in text_obj.words:
                    output.write("{:<16}{:>2}\n".format(word, word.polarity))
            
        if (len(vals) > 0):
            output.write(f"Total sentiment: {sum(vals) / len(vals)}\n")
        else:
            output.write("Total sentiment: 0.0\n")

if __name__ == "__main__":
    analyzer = SentimentAnalyzer()
    while True:
        try:
            text = input(">>>")
            print(analyzer.detect_from_message(text)[0])
        except (KeyboardInterrupt, EOFError):
            break
