import time
import signal
import html
import logging
import os
import sys
import re
from . import command
import subprocess
from subprocess import PIPE, STDOUT
import fcntl
import select

logger = logging.getLogger("haskell")

safe_interp = None

# inline impossible until further notice (parser needed; stuff like ":{" would break GHCi)
@command('^hs> (.+)', pass_groups=True, inline=False, prefix=False)
def runhaskell(bea, bot, update, groups):
    """ eval *args -> Evaluate *args as Haskell code. """

    inline = update.inline_query is not None

    def reply(t):
        if not inline:
            t = f'<a href="tg://user?id={uid}">{update.effective_user.first_name}</a>: {html.escape(t)}'
            return update.message.reply_text(t, quote=False, parse_mode="HTML")

        results = [InlineQueryResultArticle(
            id=hashlib.sha256(b"hs+"+code.encode('utf-8')).hexdigest(),
            title=f"Haskell result: {t.replace('`', '')}",
            input_message_content=InputTextMessageContent(f"`hs> {code}`\n⇒  `{t}`", parse_mode="Markdown")
        )]

        update.inline_query.answer(results=results, is_personal=True, cache_time=0)

    uid = update.effective_user.id

    code = ""

    if groups:
        code = groups[0]
    elif update.effective_message:
        code = update.effective_message.text

    if not code: return

    code = re.sub(r'(?m)^end$', '\n', code.strip())

    result = safe_interp.run(code).lstrip()
    if len(result) > 4096:
        result = f"<result too long to show, {len(result)} characters>"
    if not result: return

    return reply(result)


class SafeHSInterpreter:
    def __init__(self):
        self.process = None
        self.start_interpreter()

    def start_interpreter(self):
        if self.process is not None:
            self.process.kill()
        self.process = subprocess.Popen(["/usr/bin/ghci-8.6.5", "-ghci-script", "/home/bea/textfuckery/ghci-script"], bufsize=0,
                                        stdin=PIPE, stdout=PIPE, stderr=STDOUT)

        self.pollobj = select.epoll()

        fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, os.O_NONBLOCK)
        self.pollobj.register(self.process.stdout)
        self.pollobj.modify(self.process.stdout, select.EPOLLIN)

        startup_buf = b""
        while not startup_buf.endswith(b"gHcIpRoMpT "):
            self.wait_until_readable(10)
            time.sleep(0.1)
            startup_buf += self.read(1024)

    def write(self, text: bytes):
        self.process.stdin.write(text)
        self.process.stdin.flush()

    def read(self, max_bytes: int, timeout: float = 0) -> bytes:
        self.wait_until_readable(timeout)
        return self.process.stdout.read(max_bytes)

    def read_response(self) -> str:
        data = b""
        start = time.time()
        while not data.endswith(b"gHcIpRoMpT "):
            data += self.read(1024, 0.5)
            if start < time.time() - 5:
                self.start_interpreter()
                return "<timed out>"

        try:
            result = data.decode('utf-8')
        except UnicodeDecodeError:
            return "<undecodable text>"

        result = result.rsplit('\n', 1)[0]
        if result == "gHcIpRoMpT ":
            return ""

        length = len(result)

        if (length > 2**22):
            self.start_interpreter()
            return "<result far exceeds length, restarting...>"

        return result

    def wait_until_readable(self, timeout: float = None) -> bool:
        return bool(self.pollobj.poll(timeout))

    def run(self, code: str) -> str:
        if self.process.poll() is not None:
            logger.warning("GHCi process died? Restarting it...")
            self.start_interpreter()

        code += '\n'
        self.write(code.encode('utf-8'))
    
        return self.read_response()


def init(bea, config_):
    global safe_interp
    safe_interp = SafeHSInterpreter()

    return [runhaskell]
