import gui
from screen_base import ScreenBase
import os
import vts
import vbox
import math

def load_fonts(): # loads in the fonts
    from font import load_font
    path = os.getcwd() + '/'
    start, end = load_font(path+'BebasNeue Bold-56.rft', 8)
    return start


class ScreenGraph(ScreenBase):
    def __init__(self, y_divisions, y_min, y_max, width = 600, height = 300, origin_coords = [100,380], page_title = '', y_title = ''):
        super().__init__()
        # ----- validity checks ----- #
        try: 
            if type(y_divisions) != int: raise ValueError
            if type(y_min) != int and type(y_min) != float: raise ValueError 
            if type(y_max) != int and type(y_max) != float: raise ValueError 
            if type(width) != int: raise ValueError
            if type(height) != int: raise ValueError
            if type(origin_coords[0]) != int: raise ValueError
            if type(origin_coords[1]) != int: raise ValueError
            if type(page_title) != str: raise ValueError
            if type(y_title) != str: raise ValueError
        except ValueError:
            raise Exception("Incorrect data type entered")
        
        if math.log2(y_divisions)%1 == 0 or y_divisions%5 == 0:
            self.y_markers = [y_divisions]
        else:
            raise Exception("y_divisions has to be a power of 2 or multiple of 5")
        
        # ----- assignments ----- #
        self.y_min = y_min
        self.y_autoscale_min = [y_min]
        self.y_max = y_max
        self.y_autoscale_max = [y_max]
        self.origin = origin_coords
        magnitude_of_y_max = self.magnitude(y_max)
        self.min_magnitude_of_scale = magnitude_of_y_max-1

        self.x_axis_title = "Time"
        self.y_axis_title = y_title
        self.title[0] = page_title

        self.width = width
        self.height = height

        self.lines_dict = {}

    def magnitude(self, val):
        if (val == 0): 
            return 0
        return int(math.floor(math.log10(abs(val))))

    def draw_graph_outline(self):
        graph = []
        outline = [gui.PRIM_LINE_STRIP,
            [gui.DL_VERTEX2F(self.origin[0], self.origin[1]-self.height),
            gui.DL_VERTEX2F(self.origin[0], self.origin[1]),
            gui.DL_VERTEX2F(self.origin[0]+self.width, self.origin[1])],
        ]
        self.markers = [gui.SUBLIST, self.generate_markers()]

        graph.append(outline)
        graph.append(self.markers)
        return graph
    
    def generate_markers(self):
        markers_lst = [gui.SUBLIST]
        for marker in range(0, self.y_markers[0]+1):
            marker_y = self.origin[1]-self.height + int((self.height/self.y_markers[0]) * marker)
            markers_lst.extend([[gui.PRIM_LINE_STRIP,
                [gui.DL_VERTEX2F(self.origin[0], marker_y),
                gui.DL_VERTEX2F(self.origin[0]-5, marker_y)]
            ]])
            marker_title = self.y_autoscale_min[0] + ((self.y_autoscale_max[0] - self.y_autoscale_min[0])/self.y_markers[0]) * (self.y_markers[0]-marker)
            marker_title = '{:.0f}'.format(marker_title) if abs(marker_title - round(marker_title)) < 0.0000000000001 else '{:.4f}'.format(marker_title).rstrip('0').rstrip('.')
            markers_lst.extend([[gui.CTRL_TEXT, self.origin[0]-25, marker_y, 26, 1536, marker_title]])
        
        return markers_lst

    def check_floats_divisible(self, x: float, y: float, scaling_factor: float = 1e6):
        if x == 0:
            return True
        scaled_x = int(x * scaling_factor)
        scaled_y = int(y * scaling_factor)
        while scaled_x < scaled_y:
            scaled_y = scaled_y/10
        return (scaled_x % scaled_y) == 0

    def round_val(self, val, num, round_type):
        if round_type.upper() == 'UP':
            return_val = ((val + (num-1))//num)*num
        else:
            return_val = ((val)//num)*num
        return return_val

    def get_autoscale(self):       
        # ---------- GET NEW MAX ---------- #
        for line in self.lines_dict.values():
            try:
                if temp_max < max(line.data):
                    temp_max = max(line.data) if max(line.data)  else self.y_max
            except NameError:
                temp_max = max(line.data) if max(line.data) < self.y_max else self.y_max

        if self.magnitude(temp_max) < self.min_magnitude_of_scale: # if the value is too small then set to minimum size.
            temp_max = 10**self.min_magnitude_of_scale
        else:
            scale_up = (10**(self.magnitude(temp_max)*-1))
            temp_max = temp_max*scale_up # will set the value between 0-10.
            if temp_max > 2.5:
                if temp_max > 5:
                    temp_max = self.round_val(temp_max, 10, 'UP')
                else:
                    temp_max = self.round_val(temp_max, 5, 'UP')
            else:
                temp_max = self.round_val(temp_max, 2.5, 'UP')
            
            temp_max = temp_max/scale_up # will set the value between 0-10.

        # ---------- GET NEW MIN ---------- #
        for line in self.lines_dict.values():
            try:
                if temp_min > min(line.data):
                    temp_min = min(line.data) if min(line.data) > self.y_min else self.y_min
            except NameError:
                temp_min = min(line.data) if min(line.data) > self.y_min else self.y_min

        if self.magnitude(temp_min) < self.min_magnitude_of_scale: # if the value is too small then set to minimum size.
            temp_min = 10**self.min_magnitude_of_scale
        else:
            scale_up = (10**(self.magnitude(temp_min)*-1))
            temp_min = temp_min*scale_up # will set the value between 0-10.
            if temp_min < 10:
                if temp_min < 5:
                    temp_min = self.round_val(temp_min, 2.5, 'DOWN')                    
                else:
                    temp_min = self.round_val(temp_min, 5, 'DOWN')
            else:
                temp_min = self.round_val(temp_min, 10, 'DOWN')
            
            temp_min = temp_min/scale_up # will set the value between 0-10.

        # ---------- SET THE NEW VALUES ---------- #
        # print("NEW MAX: {}, NEW MIN: {}".format(new_max, new_min))
        flag = False
        if temp_max > temp_min:
            if self.y_autoscale_max[0] != temp_max:
                self.y_autoscale_max[0] = temp_max if temp_max < self.y_max else self.y_max
                flag = True
            if self.y_autoscale_min[0] != temp_min:
                self.y_autoscale_min[0] = temp_min if temp_min > self.y_min else self.y_min
                flag = True
            
        return flag

    def set_marker_divisions(self, divisions):
        if math.log2(divisions)%1 == 0 or divisions%5 == 0:
            self.y_markers[0] = divisions
        else:
            raise Exception("divisions has to be a power of 2 or multiple of 5")
        self.update_markers()        

    def update_markers(self):
        self.markers[1] = self.generate_markers()
        for line in self.lines_dict.values():
            line.update_all_points()

    def create_Line_object(self, line_name, samples_per_line, line_thickness, line_colour):
        self.lines_dict[line_name] = Line(samples_per_line+1, line_thickness, line_colour, self.origin, self.width, self.height, self.y_autoscale_max, self.y_autoscale_min, self.y_markers)
    
    def add_data_to_Line(self, line_name, data):
        data = self.clamp(data)
        self.lines_dict[line_name].add_data(data)
        if self.get_autoscale():
            self.update_markers()

    def clamp(self, data):
        if data > self.y_max:
            data = self.y_max
        elif data < self.y_min:
            data = self.y_min
        return data
    
    def setup(self):
        super().setup()
        self.gui_list.extend([
            ScreenBase.font_colour,
            [gui.CTRL_TEXT, self.origin[0]+int(self.width/2), self.origin[1]+20, 30, 1536, self.x_axis_title],
            # [gui.CTRL_TEXT, self.origin[0]-20, self.origin[1]-int(self.height/2), 30, 0, self.y_axis_title],
        ])
        self.gui_list.extend(self.draw_graph_outline())
        for line in self.lines_dict.values():
            self.gui_list.extend([[gui.DL_COLOR(line.line_colour)]])
            self.gui_list.extend([[gui.DL_LINE_WIDTH(line.line_thickness)]])
            self.gui_list.extend([[gui.PRIM_LINE_STRIP,line.get_dl()]])

    def vsync_cb(self, l):
        super().vsync_cb(l)


class Line:
    def __init__(self, samples_per_line, thickness, colour, origin, width, height, y_max, y_min, y_div):
        self.samples_per_line = samples_per_line
        self.line_thickness = thickness
        self.line_colour = colour
        self.origin = origin
        self.data = [y_min[0]]
        self.points = [[origin[0],origin[1]]]
        self.line_lst = [gui.DL_NOP()]*(self.samples_per_line)
        self.x_length = int(width/(self.samples_per_line-1))
        self.height = height
        self.y_max = y_max
        self.y_min = y_min

    def update_all_points(self):
        for index in range(0,len(self.points)): # move all other points back a position
            self.points[index][1] = int(self.origin[1]-((self.height/(self.y_max[0]-self.y_min[0]))*(self.data[index]-self.y_min[0])))

        self.add_data()

    def add_to_points(self, data):
        if len(self.points) == self.samples_per_line: # if on the max number of points
            self.points.pop(0) # remove first point
            self.data.pop(0)
  
            for index in range(0,len(self.points)): # move all other points back a position
                self.points[index][0] -= self.x_length

        self.data.append(data)
        self.points.extend([[self.origin[0] + (len(self.points)*self.x_length), int(self.origin[1]-((self.height/(self.y_max[0]-self.y_min[0]))*(data-self.y_min[0])))]]) # add new point at end
    
    def add_data(self, data = None):
        if data != None: # if there is a new point to update
            self.add_to_points(data)

        for x in range(0,len(self.points)): # set the display list coordinates to equal the point list coordinates.
            self.line_lst[x] = gui.DL_VERTEX2F(self.points[x][0], self.points[x][1])

    def get_dl(self): # return the display list for the lines to be displayed by gui.show()
        return self.line_lst

# def sample_cb():
#     processed = 1
#     while processed:
#         sample = vbox.get_sample_hp(False)
#         if sample is not None:
#             graph.add_data_to_Line('Line1', sample.sats_used/10)
#         else:
#             processed = 0

# if __name__ == '__main__':
#     load_fonts()
#     vbox.init(vbox.VBOX_SRC_GNSS_BASIC)
#     graph = ScreenGraph(5, 0, 40, width=500, height=300, origin_coords=[100,380], page_title='GRAPH', y_title='SPEED')
#     vbox.set_new_data_callback(sample_cb)
#     graph.create_Line_object('Line1', 20, 2, gui.RGB(0,255,0))
#     graph.setup()
#     gui.show(graph.gui_list)

#     vts.delay_ms(2000)
#     graph.set_marker_divisions(10)