from ursina import *
from collections import namedtuple
from copy import copy


class Node:
    __slots__ = ['index', 'indent_level', 'content', 'code', 'children', 'is_answer']

    def __str__(self):
        return 'Node:\n    ' + '\n    '.join([f'{e} = {getattr(self, e)}' for e in Node.__slots__])


class Conversation(Entity):

    def __init__(self, variables_object=None, **kwargs):
        super().__init__(parent=camera.ui, y=-.1)

        self.question = Button(parent=self, text_origin=(-.5,0), scale=(1,.1), model=Quad(radius=.5,aspect=1/.1), text='Question')
        self.question.text_entity.line_height = 1.25
        self.question.text_entity.position = (-.45, -.05)
        self.question.highlight_color = self.question.color
        self.more_indicator = Entity(parent=self.question, model=Circle(3), position=(.45,-.4,-1), rotation_z=180, color=color.azure, world_scale=.5, z=-1, enabled=False)
        self.more_indicator.blink(duration=1, loop=True)
        self.spacing = 4 * .02
        self.wordwrap = 65
        self.button_model = Quad(radius=.5, aspect=1/.075)
        self.variables_object = variables_object

        for key, value in kwargs.items():
            setattr(self, key ,value)

        self.answer_0 = Button(parent=self, text='answer_0', y=self.question.y-self.spacing-.025, scale=(1,.075), text_origin=(-.5,0), model=copy(self.button_model))
        self.answer_1 = Button(parent=self, text='answer_1', y=self.answer_0.y-self.spacing, scale=(1,.075), text_origin=(-.5,0), model=copy(self.button_model))
        self.answer_2 = Button(parent=self, text='answer_2', y=self.answer_1.y-self.spacing, scale=(1,.075), text_origin=(-.5,0), model=copy(self.button_model))

        self.buttons = (self.answer_0, self.answer_1, self.answer_2)
        for b in self.buttons:
            b.text_entity.line_height = 1.15
            b.text_entity.position = (-.45, 0)

        self.question_appear_sequence = None
        self.button_appear_sequence = None
        self.started = False



    def ask(self, node, question_part=0):
        # print(node)
        self.current_node = node
        self.question_part = question_part
        self.question.text = node.content[question_part]
        self.question.text_entity.wordwrap = self.wordwrap
        self.more_indicator.enabled = False
        self.question_appear_sequence = self.question.text_entity.appear()

        for b in self.buttons:
            b.enabled = False

        answers = []
        for i, child in enumerate(node.children):
            if self.variables_object and child.code and child.code.startswith('if'):
                try:
                    if not getattr(self.variables_object, child.code[3:]):
                        continue
                except Exception as e:
                    print('failed parsing conversation if statement:', e)

            answers.append(child)

        # multi page question
        if len(node.content) > 1 and self.question_part < len(node.content)-1:
            if self.question_part < len(node.content): # question not finished, so don't show answer buttons
                # print('question not finished')
                self.question_appear_sequence.append(Func(setattr, self.more_indicator, 'enabled', True))
                return

        self.button_appear_sequence = Sequence()
        invoke(self.button_appear_sequence.start, delay=self.question_appear_sequence.duration)

        if not node.children:
            self.buttons[0].text = '*leave*'
            self.buttons[0].on_click = Func(setattr, self, 'enabled', False)
            self.button_appear_sequence.append(Func(setattr, self.buttons[0], 'enabled', True))

        for i, child in enumerate(answers):
            self.button_appear_sequence.append(Wait(i*.15))
            self.button_appear_sequence.append(Func(setattr, self.buttons[i], 'enabled', True))
            self.buttons[i].text = child.content[0]
            self.buttons[i].text_entity.wordwrap = self.wordwrap

            def on_click(node=child):
                if not node.children:
                    print('end conversation')
                    self.enabled = False
                    return

                if node.code and not node.code.startswith('if '):
                    try:
                        if '+=' in node.code or '-=' in node.code or '*=' in node.code or '/=' in node.code:
                            var, operator, value = node.code.split()
                            original_value = getattr(self.variables_object, var)
                            data_type = type(original_value)
                            value = data_type(value)
                            if operator == '+=':    new_value = original_value + value
                            if operator == '-=':    new_value = original_value - value
                            if operator == '*=':    new_value = original_value * value
                            if operator == '/=':    new_value = original_value / value

                            setattr(self.variables_object, var, new_value)
                        print('executed code:', node.code)
                    except Exception as e:
                        print('failed executing code on node:', node, 'code:', node.code, 'error:', e)

                invoke(self.ask, node.children[0], 0, delay=.1)
                if len(node.children) > 1:
                    print('error at node:', n, '. node has multiple children, but should only have one (a question)')

            self.buttons[i].on_click = on_click



    def input(self, key):
        if key == 'left mouse down' or key == 'space' and not mouse.hovered_entity in self.buttons:
            self.next()


    def next(self):
        if not self.started:
            return

        if not self.question_appear_sequence.finished and self.question_appear_sequence.t > .1:
            self.question_appear_sequence.finish()
            if self.button_appear_sequence:
                self.button_appear_sequence.start()
            return

        if self.question_part < len(self.current_node.content)-1:
            self.ask(self.current_node, self.question_part+1)


    def start_conversation(self, conversation):
        self.conversation_nodes = self.parse_conversation(conversation)
        self.ask(self.conversation_nodes[0])
        self.started = True



    def parse_conversation(self, convo):
        convo = convo.strip()
        nodes = list()
        prev_node = None
        node_index = 0

        for i, l in enumerate(convo.split('\n')):
            if not l:
                continue

            indent_level = len(l) - len(l.lstrip())
            indent_level //= 4
            content, code = l.strip(), None
            if '(' in l:
                content, code = l.split('(')
                content = content.strip()
                code = code[:-1]
                is_answer = content.startswith('* ')
                # print('code:', codeK)

            if prev_node and prev_node.indent_level == indent_level:
                prev_node.content.append(content)
                prev_node = n
                continue

            n = Node()
            n.index = node_index
            n.indent_level = indent_level
            n.is_answer = content.startswith('* ')
            if n.is_answer:
                content = content[2:]
            n.content = [content, ]
            n.children = list()
            n.code = code
            nodes.append(n)
            prev_node = n
            node_index += 1

            # look backwards through nodes to find current node's parent
            for j in range(node_index-1, -1, -1):
                if nodes[j].indent_level == n.indent_level-1:
                    nodes[j].children.append(n)
                    break


        return nodes



if __name__ == '__main__':
    app = Ursina()

    variables = Empty(
        evil=0,
        chaos=0,
        bar_mission_solved=False,
    )
    conversation = Conversation(variables_object=variables)
    # conversation.question.model = 'quad'
    # for b in conversation.buttons:
    #     b.model = 'quad'

    convo = dedent('''
    I'm looking for my sister. Can you help me find her, please? I haven't seen her in days! Who know what could've happened!?
    I'm worried. Will you help me?
        * Yes, of course. This can be a dangerous city.
            Oh no! Do you think something happened to her?
            What should I do?!
                * She's probably fine. She can handle herself.
                    You're right. I'm still worried though.
                        * Don't worry, I'll look for her.
                * Maybe. (stats.chaos += 1)
                    Help me look for her, please! *runs off*
        * I'm sorry, but I don't have time right now. (evil += 1)
            A true friend wouldn't say that.
        * I know where she is! (if bar_mission_solved)
            Really? Where?
                * I saw her on a ship by the docks, it looked like they were ready to set off.
                    Thank you! *runs off*
    ''')
    conversation.start_conversation(convo)
    # conversation.parse_conversation(convo)
    # def input(key):
    #     if key == 'left mouse down' and mouse.hovered_entity in conversation.buttons:
    #         print('add sound here')




    def input(key):
        if key == 'space':
            print(variables.evil)
            # conversation.start_conversation()

    # window.color = color._16
    # window.size = window.fullscreen_size * .5
    Sprite('shore', z=1)
    app.run()
