import kivy

from kivy.vector import Vector
from kivy.core.text import Label
from kivy.graphics import *
from kivy.properties import *

kivy.require('1.7.0')

"""
    Desc: Stores a Turing State and its graphical components. 
"""
class TuringState():

    instructionGroup = None
    state_circle_color = None
    state_circle = None
    state_highlight_color = None
    state_highlight = None
    state_label = None
    state_label_rect = None

    pos = (0, 0)
    radius = 25
    name = ''

    """
    Desc: Initialises the turing state.
    @param: new_pos - the world position to place the state at.
    @param: name - the name of the state (needs to be unique)
    @param: unique_id - An id generated by the tm_gui that uniquely identifies the state.
    """
    def __init__(self, new_pos, name, unique_id):
        self.pos = (new_pos[0], new_pos[1])

        self.type = 'state'
        
        # Centre the state around the new position.
        self.offset_pos = (new_pos[0] - self.radius, new_pos[1] - self.radius)
        self.name = name
        self.final_state = False
        self.start_state = False
        self.unique_id = unique_id
        self.setUpCanvas()

        self.out_transitions = []
        self.in_transitions = []

    """
    Desc: Sets up an instruction group for the turing state (pretty ugly but not a lot can be done)
    """
    def setUpCanvas(self):
        self.instructionGroup = InstructionGroup(group=self.get_unique_id())
        self.state_circle_color = Color(.5, .5, .5, 1)
        self.state_circle = Ellipse(group=self.get_unique_id(
        ), pos=self.offset_pos, size=(self.radius * 2, self.radius * 2))
        self.state_highlight_color = Color(.8, .8, .8, 1)
        self.state_highlight = Line(width=1.5, ellipse=(
            self.offset_pos[0], self.offset_pos[1], self.radius * 2, self.radius * 2))

        self.state_label = Label(
            text_size=(self.radius * 2 - 6, self.radius * 2), halign='center', valign='middle')
        self.state_label.text = self.name
        self.state_label.refresh()
        self.state_label_color = Color(1, 1, 1, 1)
        self.state_label_rect = Rectangle(pos=Vector(self.offset_pos) + Vector(3, 0), size=(self.radius * 2 - 6, self.radius * 2),
                                          texture=self.state_label.texture)

        # These can be added to the group later, no need to add unused
        # instructions.
        self.line_color = Color(1, 1, 1, 1)
        self.final_state_circle = Line(width=1.2, ellipse=(
            self.offset_pos[0] + 5, self.offset_pos[1] + 5, (self.radius - 5) * 2, (self.radius - 5) * 2))

        self.initial_state_arrow = Line(width=1.8, cap='round', joint='round')
        self.initial_state_arrow.points = (self.pos[0] - 7, self.pos[1] + self.radius + 10, self.pos[0], self.pos[1] + self.radius + 2,
                                           self.pos[0] + 7, self.pos[1] + self.radius + 10)

        self.initial_state_tail = Line(width=1.8, cap='round')
        self.initial_state_tail.points = (self.pos[0], self.pos[
                                          1] + self.radius + 25, self.pos[0], self.pos[1] + self.radius + 2)

        self.instructionGroup.add(self.state_circle_color)
        self.instructionGroup.add(self.state_circle)
        self.instructionGroup.add(self.state_highlight_color)
        self.instructionGroup.add(self.state_highlight)
        self.instructionGroup.add(self.state_label_color)
        self.instructionGroup.add(self.state_label_rect)
        self.instructionGroup.add(self.line_color)

    def set_position(self, new_pos):
        self.pos = (new_pos[0], new_pos[1])
        self.offset_pos = (new_pos[0] - self.radius, new_pos[1] - self.radius)

        self.state_circle.pos = self.offset_pos
        self.state_label_rect.pos = Vector(self.offset_pos) + Vector(3, 0)
        self.state_highlight.ellipse = (self.offset_pos[0],
                                        self.offset_pos[1], self.radius * 2, self.radius * 2)

        self.final_state_circle.ellipse = (
            self.offset_pos[0] + 5, self.offset_pos[1] + 5, (self.radius - 5) * 2, (self.radius - 5) * 2)

        self.initial_state_arrow.points = (self.pos[0] - 7, self.pos[1] + self.radius + 10, self.pos[0], self.pos[1] + self.radius + 2,
                                           self.pos[0] + 7, self.pos[1] + self.radius + 10)
        self.initial_state_tail.points = (self.pos[0], self.pos[
                                          1] + self.radius + 25, self.pos[0], self.pos[1] + self.radius + 2)

        # Update all connected transition positions
        for transition in self.out_transitions:
            transition.update_positions()

        for transition in self.in_transitions:
            transition.update_positions()

    def get_position(self):
        return (self.pos[0], self.pos[1])

    def set_highlight(self, r, g, b):
        self.state_highlight_color.r = r
        self.state_highlight_color.g = g
        self.state_highlight_color.b = b

    def add_out_transition(self, transition):
        if transition not in self.out_transitions:
            self.out_transitions.append(transition)

    def remove_out_transition(self, transition):
        if transition in self.out_transitions:
            self.out_transitions.remove(transition)
            return
        print('Warning: Transition not in state')

    def add_in_transition(self, transition):
        if transition not in self.in_transitions:
            self.in_transitions.append(transition)

    def remove_in_transition(self, transition):
        if transition in self.in_transitions:
            self.in_transitions.remove(transition)
            return
        print('Warning: Transition not in state')

    # Used to find the next transition when simulating. 
    # Finds the transition based on the given read symbol.
    def get_transition(self, read_sym):
        for transition in self.out_transitions:
            if transition.read_sym == read_sym:
                return transition
        return None

    """
    Desc: Used when undoing/redoing the creation of transitions. (allows us to find
        non-unique property-wise transitions)
    """
    def get_transition_by_id(self, unique_id):
        for transition in self.out_transitions:
            if transition.unique_id == unique_id:
                return transition
        return None

    # Call this when deleting the state. Removes connected transition references.
    # Returns the invalid transitions to be deleted.
    def delete(self):
        invalid_transitions = self.out_transitions + self.in_transitions

        # Each invalid transition needs to remove itself from the other state.
        for transition in invalid_transitions:
            transition.delete()

        return invalid_transitions

    def set_start_state(self, start_state):
        self.start_state = start_state
        if self.start_state:
            self.instructionGroup.add(self.initial_state_arrow)
            self.instructionGroup.add(self.initial_state_tail)
        else:
            self.instructionGroup.remove(self.initial_state_arrow)
            self.instructionGroup.remove(self.initial_state_tail)

    def set_final_state(self, final_state):
        self.final_state = final_state
        if self.final_state:
            self.instructionGroup.add(self.final_state_circle)
        else:
            self.instructionGroup.remove(self.final_state_circle)

    def change_name(self, new_name):
        self.name = new_name[0:7]
        if self.name != '':
            self.state_label.label = self.name
            self.state_label.refresh()

    def get_instruction_group(self):
        return self.instructionGroup

    def get_unique_id(self):
        return str(self.unique_id)
