import copy
from . import mathhelper
from . import curves

cdef class SliderTick:
    cdef public float x, y, time

    def __init__(self, x, y, time):
        self.x = x
        self.y = y
        self.time = time

cdef class HitObject(object):
    cdef public float x, y, time, end_time, pixel_length, tick_distance, duration
    cdef public int type, repeat
    cdef public str slider_type
    cdef public list curve_points, ticks, end_ticks, path
    cdef public dict timing_point
    cdef public object difficulty, end

    def __init__(self, x, y, time, object_type, slider_type = None, curve_points = None, repeat = 1, pixel_length = 0, timing_point = None, difficulty = None, tick_distance = 1):
        """
        HitObject params for normal hitobject and sliders

        x -- x position
        y -- y position
        time -- timestamp
        object_type -- type of object (bitmask)

        [+] IF SLIDER
        slider_type -- type of slider (L, P, B, C)
        curve_points -- points in the curve path
        repeat -- amount of repeats for the slider (+1)
        pixel_length -- length of the slider
        timing_point -- ref of current timing point for the timestamp
        difficulty -- ref of beatmap difficulty
        tick_distance -- distance betwin each slidertick
        """
        self.x = x
        self.y = y
        self.time = time
        self.end_time = 0
        self.type = object_type

        #isSlider?
        if 2 & self.type:
            self.slider_type = slider_type
            self.curve_points = [mathhelper.Vec2(self.x, self.y)] + curve_points
            self.repeat = repeat
            self.pixel_length = pixel_length

            #For slider tick calculations
            self.timing_point = timing_point
            self.difficulty = difficulty
            self.tick_distance = tick_distance
            self.duration = (int(self.timing_point["raw_bpm"]) * (pixel_length / (self.difficulty["SliderMultiplier"] * self.timing_point["spm"])) / 100) * self.repeat

            self.ticks = []
            self.end_ticks = []
            self.path = []
            self.end = None

            self.calc_slider()

    def calc_slider(self, calc_path = False):
        #Fix broken objects
        if self.slider_type == "P" and len(self.curve_points) > 3:
            self.slider_type = "B"
        elif len(self.curve_points) == 2:
            self.slider_type = "L"

        #Make curve
        if self.slider_type == "P":     #Perfect
            try:
                curve = curves.Perfect(self.curve_points)
            except:
                curve = curves.Bezier(self.curve_points)
                self.slider_type = "B"
        elif self.slider_type == "B":   #Bezier
            curve = curves.Bezier(self.curve_points)
        elif self.slider_type == "C":   #Catmull
            curve = curves.Catmull(self.curve_points)

        #Quickest to skip this
        if calc_path: #Make path if requested (For drawing visual for testing)
            if self.slider_type == "L":     #Linear
                self.path = curves.Linear(self.curve_points).pos
            elif self.slider_type == "P":   #Perfect
                self.path = []
                l = 0
                step = 5
                while l <= self.pixel_length:
                    self.path.append(curve.point_at_distance(l))
                    l += step
            elif self.slider_type == "B":   #Bezier
                self.path = curve.pos
            elif self.slider_type == "C":   #Catmull
                self.path = curve.pos
            else:
                raise Exception("Slidertype not supported! ({})".format(self.slider_type))

        #Set slider ticks
        current_distance = self.tick_distance
        time_add = self.duration * (self.tick_distance / (self.pixel_length * self.repeat))

        while current_distance < self.pixel_length - self.tick_distance / 8:
            if self.slider_type == "L":     #Linear
                point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], current_distance)
            else:   #Perfect, Bezier & Catmull uses the same function
                point = curve.point_at_distance(current_distance)

            self.ticks.append(SliderTick(point.x, point.y, self.time + time_add * (len(self.ticks) + 1)))
            current_distance += self.tick_distance

        #Adds slider_ends / repeat_points
        repeat_id = 1
        repeat_bonus_ticks = []
        while repeat_id < self.repeat:
            dist = (1 & repeat_id) * self.pixel_length
            time_offset = (self.duration / self.repeat) * repeat_id

            if self.slider_type == "L":     #Linear
                point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist)
            else:   #Perfect, Bezier & Catmull uses the same function
                point = curve.point_at_distance(dist)

            self.end_ticks.append(SliderTick(point.x, point.y, self.time + time_offset))

            #Adds the ticks that already exists on the slider back (but reversed)
            repeat_ticks = copy.deepcopy(self.ticks)

            if 1 & repeat_id: #We have to reverse the timing normalizer
                repeat_ticks = list(reversed(repeat_ticks))
                normalize_time_value = self.time + (self.duration / self.repeat)
            else:
                normalize_time_value = self.time

            #Correct timing
            for tick in repeat_ticks:
                tick.time = self.time + time_offset + abs(tick.time - normalize_time_value)

            repeat_bonus_ticks += repeat_ticks

            repeat_id += 1

        self.ticks += repeat_bonus_ticks

        #Add endpoint for slider
        dist_end = (1 & self.repeat) * self.pixel_length
        if self.slider_type == "L":     #Linear
            point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist_end)
        else:   #Perfect, Bezier & Catmull uses the same function
            point = curve.point_at_distance(dist_end)

        self.end_ticks.append(SliderTick(point.x, point.y, self.time + self.duration))

    def get_combo(self):
        """
        Returns the combo given by this object
        1 if normal hitobject, 2+ if slider (adds sliderticks)
        """
        if 2 & self.type:   #Slider
            val = 1                     #Start of the slider
            val += len(self.ticks)      #The amount of sliderticks
            val += self.repeat          #Reverse slider
        else:   #Normal
            val = 1                     #Itself...

        return val
