from uuid import UUID
from mumle.state.base import State
from mumle.services.bottom.V0 import Bottom
from mumle.services.primitives.float_type import Float

import math


class PointPolar:
    """
    Implements services for the point polar LTM.
    Implementation is done in terms of Python data structures
    """
    def __init__(self, model: UUID, state: State):
        type_model_id = state.read_dict(state.read_root(), "PointPolar")
        self.type_model = UUID(state.read_value(type_model_id))
        self.model = model
        self.state = state

        self.point = None

    def create_point(self, r: float, theta: float):
        """
        Creates a point.

        Args:
            r: distance from pole
            theta: angle from polar axis

        Returns:
            Nothing.
        """
        if self.point == None:
            self.point = (r, theta)
        else:
            raise RuntimeError("A PointPolar model can contain at most 1 point.")

    def read_point(self):
        """
        Reads point.

        Returns:
            Textual representation of the point data.
        """
        if self.point == None:
            raise RuntimeError("No point found in model.")
        else:
            return f"(r = {self.point[0]}, \u03B8 = {self.point[1]})"

    def delete_point(self):
        """
        Deletes point.

        Returns:
            Nothing.
        """
        self.point = None

    def apply_movement(self, delta_r: float, delta_theta: float):
        """
        Moves point.

        Args:
            delta_r: change in distance from pole
            delta_theta: change in angle from polar axis

        Returns:
            Nothing.
        """
        if self.point != None:
            self.point = (self.point[0] + delta_r, self.point[1] + delta_theta)
        else:
            raise RuntimeError("No point found in model.")

    def to_bottom(self):
        """
        Converts implementation specific model representation to
        canonical representation.

        Returns:
            Nothing.
        """
        x = self.point[0]*math.cos(self.point[1])  # x = r * cos(theta)
        y = self.point[0]*math.sin(self.point[1])  # y = r * sin(theta)
        bottom = Bottom(self.state)
        # clear residual model
        for element in bottom.read_outgoing_elements(self.model):
            bottom.delete_element(element)
        # create primitive models
        c1_model = bottom.create_node()
        c2_model = bottom.create_node()
        Float(c1_model, self.state).create(x)
        Float(c2_model, self.state).create(y)
        # instantiate Point class
        point_node = bottom.create_node()  # create point node
        bottom.create_edge(self.model, point_node, "point")  # attach to model
        morph_node, = bottom.read_outgoing_elements(self.type_model, "PointPolar")  # retrieve type
        bottom.create_edge(point_node, morph_node, "Morphism")  # create morphism link
        # instantiate c1 attribute
        c1_node = bottom.create_node(str(c1_model))
        bottom.create_edge(self.model, c1_node, "point.c1")
        c1_link = bottom.create_edge(point_node, c1_node)
        bottom.create_edge(self.model, c1_link, "point.c1_link")
        ltm_point_node, = bottom.read_outgoing_elements(self.type_model, "Float")
        ltm_point_link, = bottom.read_outgoing_elements(self.type_model, "PointPolar_c1")
        bottom.create_edge(c1_node, ltm_point_node, "Morphism")
        bottom.create_edge(c1_link, ltm_point_link, "Morphism")
        # instantiate c2 attribute
        c2_node = bottom.create_node(str(c2_model))
        bottom.create_edge(self.model, c2_node, "point.c2")
        c2_link = bottom.create_edge(point_node, c2_node)
        bottom.create_edge(self.model, c2_link, "point.c2_link")
        ltm_point_node, = bottom.read_outgoing_elements(self.type_model, "Float")
        ltm_point_link, = bottom.read_outgoing_elements(self.type_model, "PointPolar_c2")
        bottom.create_edge(c2_node, ltm_point_node, "Morphism")
        bottom.create_edge(c2_link, ltm_point_link, "Morphism")

    def from_bottom(self):
        """
        Converts canonical representation to
        implementation specific model representation.

        Returns:
            Nothing.
        """
        bottom = Bottom(self.state)
        keys = bottom.read_keys(self.model)
        x_key, = filter(lambda k: k.endswith(".c1"), keys)
        y_key, = filter(lambda k: k.endswith(".c2"), keys)
        x_ref_node, = bottom.read_outgoing_elements(self.model, x_key)
        y_ref_node, = bottom.read_outgoing_elements(self.model, y_key)
        x_model = UUID(bottom.read_value(x_ref_node))
        y_model = UUID(bottom.read_value(y_ref_node))
        x_val_node, = bottom.read_outgoing_elements(x_model)
        y_val_node, = bottom.read_outgoing_elements(y_model)
        x = bottom.read_value(x_val_node)
        y = bottom.read_value(y_val_node)
        r = math.sqrt(math.pow(x, 2) + math.pow(y, 2))
        theta = math.atan2(y, x)
        self.point = (r, theta)
