from typing import Dict, List, cast

from .node import Node
from .operand_ import Operand
from .operator_ import Operator

from .utils.string_utils import concatenate_horizontally, simplify_expression
from .parser import Parser
from .lexer import Lexer
from .tokenizer import Tokenizer
import os
from copy import deepcopy
from time import sleep


class Scene:
    def __init__(self,
    parse_tree_class,
                 token_lookup: Dict,
                 implicit_operator: Operator,
                 seconds_per_frame: int = 1) -> None:
        self.parse_tree_class = parse_tree_class
        self.token_lookup = token_lookup
        self.implicit_operator = implicit_operator
        self.seconds_per_frame = seconds_per_frame

    def stencil(self, expression, tokens=None, lexed=None, parsed=None, tree=None) -> None:
        os.system('cls')
        if tokens is None:
            process = 'TOKENIZATION'
        elif lexed is None:
            process = 'LEXING'
        elif parsed is None:
            process = 'PARSING'
        elif tree is None:
            process = 'BUILDING PARSE TREE'
        else:
            process = 'REDUCING PARSE TREE'
        print(f'CURRENT PROCESS: {process}')
        print()
        print(f'Expression: {expression}')
        if tokens is not None:
            print()
            print('TOKENS >>>')
            print(tokens)
        if lexed is not None:
            print()
            print('LEXED TOKENS >>>')
            print(lexed)
            print()
        if parsed is not None:
            print('PARSED TOKENS >>>')
            print(parsed)
            print()
        if tree is not None:
            print('COMPLETE PARSE TREE >>>')
            print(tree)
            print()

    def animate_tokenization(self, expression):
        tokens: List[str] = []
        for i, j, t in Tokenizer(token_lookup=self.token_lookup).tokenize(expression=expression, kind='lazy'):
            self.stencil(expression)
            print(' ' * (i + 12) + '\u2191' + ' ' * (j - i - 1) + '\u2191')
            print(' ' * (i + 12) + 'i' + ' ' * (j - i - 1) + 'j')
            print(t)
            tokens = t
            sleep(self.seconds_per_frame)
        return tokens

    def animate_lexing(self, expression, tokens):
        lexed = []
        prefix = 1
        for o, c, l, t in Lexer(token_lookup=self.token_lookup, implicit_operator=self.implicit_operator).lex(tokens=tokens):
            self.stencil(expression, tokens)
            arrow = '\u2191'
            print(' ' * prefix + f'{arrow:^{len(t) + 2}}')
            print(f'(: {o}; ): {c}')
            print(l)
            lexed = l
            sleep(self.seconds_per_frame)
            prefix += len(t) + 4
        return lexed

    def animate_parsing(self, lexed):
        return Parser(token_lookup=self.token_lookup, implicit_operator=self.implicit_operator).parse(lexed)

    def animate_building(self, expression, tokens, lexed, parsed):
        tree = None
        for i in range(1, len(parsed) + 1):
            self.stencil(expression, tokens, lexed, parsed)
            tmp_parsed = deepcopy(parsed[:i])
            t = self.parse_tree_class()
            t.build_sub_tree(tmp_parsed)
            print(t)
            tree = t
            sleep(self.seconds_per_frame)
        return tree

    def animate_reduction(self, expression, tokens, lexed, parsed, tree, tree_repr):
        reduced_tree = tree
        prev_display = ''
        for t in self.dfs_reduce(node=tree.root, tree=tree):
            if str(t) == prev_display:
                continue
            self.stencil(expression, tokens, lexed, parsed, tree_repr)
            prev_display = str(t)
            print(prev_display)
            sleep(self.seconds_per_frame)
        self.stencil(expression, tokens, lexed, parsed, tree_repr)
        reduced_tree.root = Operand(value=reduced_tree.root())
        print(str(reduced_tree))
        return reduced_tree.root

    def dfs_reduce(self, node: Node, tree):
        for c in node.children:
            yield from self.dfs_reduce(c, tree)
        for i, c in enumerate(node.children):
            node.children[i] = Operand(value=c())
            yield tree

    def animate(self, expression):
        expression_stripped = simplify_expression(expression)
        tokens = self.animate_tokenization(expression_stripped)
        lexed = self.animate_lexing(expression_stripped, tokens)
        parsed = self.animate_parsing(lexed)
        t = self.animate_building(expression_stripped, tokens, lexed, parsed)
        complete_tree = str(t)
        result = self.animate_reduction(
            expression, tokens, lexed, parsed, t, complete_tree)
        self.stencil(expression_stripped, tokens, lexed, parsed, complete_tree)
        print('REDUCED PARSE TREE >>>')
        print(result)
        print()
        print('EVALUATION >>>')
        parse_tree_class = self.parse_tree_class
        print(expression, '=', round(t.evaluate(), 5))
