##
# @file    main.py
# @version 1.0
# @brief   Simple CAN Value Display for default VBOX Values
# @author  Jamie Bird
# @date    Jan 2020
# @copyright Racelogic Ltd., 2020
# 500000

'''Note: All dependencies moved to within this file so that only one file is needed to be placed on SD Card to run.'''

'''To add more CAN Channels:
    If CAN ID not already being monitored (check 'all_can_ids' tuple below):
        - Add the ID to the 'all_can-ids' tuple in global constants
        - Create a new Byte array to store the can frame in (see global constants)
        - Create a new MemoryView object in the '_Pointers' subclass (in 'VB') and point it to your new byte array
        - Witihin the 'handle_can' function, add a statement to move any new CAN frames on that ID to your new byte array

    - In the relevant subclass of the 'VB' class, create a list to store the value in with the first item being the
        value ready by the screen (screen will always read the first item) and the second being the CAN ID it is on
    - In the 'parse_data' function, add the logic for decoding the can message and store the result to your new list (above)
        - If you want a filtered onscreen version, add 'VB._Filters.*CHANNEL_NAME*' in the string format before storing it

'''

import can
import gui
import ft8xx as ft
import gui
import ustruct as us
import vts
import gc

# Global Constants ######################################################
hf = 31         # Heading font
vf = 32         # Value Font
baudrate = [500000] # baud rate in bits per second (1MBit not supported yet, only use 500kbit or less)
all_can_ids = (0x301, 0x302, 0x303, 0x304, 0x305, 0x306, 0x307, 0x308, 0x309,
                0x313, 0x314, 0x323, 0x30A, 0x30B) #CAN ID's being looked for
# timer = vts.MChrono() # Dev Tool

'''All CAN Frames stored here'''
_301 = bytearray(8)
_302 = bytearray(8)
_303 = bytearray(8)
_304 = bytearray(8)
_305 = bytearray(8)
_306 = bytearray(8)
_307 = bytearray(8)
_308 = bytearray(8)
_309 = bytearray(8)
_313 = bytearray(8)
_314 = bytearray(8)
_323 = bytearray(8)
_30A = bytearray(8)
_30B = bytearray(8)

# Screen and Timing ##################################################################

def vsync_cb(l):
    '''Called whenever the screen redraws and parses the curent CAN Data in the relevant Byte arrays'''
    try:
        parse_data(set(base_page.active_page.active_ids))
    except AttributeError as e: #caused by drawing screen before CAN fully initialised
        print("Data_Parse Error: {}".format(e))
    gui.redraw()

def press_cb(press):
    '''handles any registered touches on screen'''
    if (press[0] == 2) and (not selection_page.active):
        # print("Long Press: {}".format(press))
        if (press[3] < 400) and (press[4] < 220):
            base_page.active_page.selection = 1
            base_page.active_page.draw_options()
        elif (press[3] > 400) and (press[4] < 220):
            base_page.active_page.selection = 2
            base_page.active_page.draw_options()
        elif (press[3] < 400) and (press[4] > 220):
            base_page.active_page.selection = 3
            base_page.active_page.draw_options()
        elif (press[3] > 400) and (press[4] > 220):
            base_page.active_page.selection = 4
            base_page.active_page.draw_options()
        else:
            print("Error Selecting Option - Dected Press: {}".format(press))

def swipe_cb(gui_list, swiping):
    '''Called when swiping detected'''
    if swiping:
        pass
        # print('Starting to swipe')
    else:
        # print('Stopping swipe')
        si = gui.swipe_info()
        if selection_page.active:
            if si.dx <= -50:
                # print("Swiped Left")
                if (len(selection_page.available_channels)/10) > selection_page.act_page:
                    selection_page.act_page += 1
            elif si.dx >= 50:
                # print("Swiped Right")
                if selection_page.act_page > 1:
                    selection_page.act_page -= 1
        else:
            if si.dx <= -50:
                # print("Swiped Left")
                if base_page.active_page.page < len(base_page.created_pages): 
                    base_page.change_page(base_page.active_page.page+1)
            elif si.dx >= 50:
                # print("Swiped Right")
                if base_page.active_page.page > 1:
                    base_page.change_page(base_page.active_page.page - 1)
        # if si.dy <= -50:
        #     # print("Swiped Up")
        # elif si.dy >= 50:
        #     # print("Swiped Down")
        draw()
        # print('New Page:', base_page.active_page.page)
        gc.collect()

def time_it(test): # Dev Tool
    global timer
    if test =='Start':
        timer.start()
        return 0
    elif test == 'End':
        return timer.restart()

def check_name(length=16): #Dev Tool
    for i in dir(VB):
        if not i.startswith('_'):
            print("---{}---".format(i))
            for j in dir(getattr(VB, i)):
                if not (j.startswith('_')):
                    if len(str(j)) > length:
                        print(j)

# CAN ##################################################################################
class VB():
    ''' Stores all the parsed CAN Data'''

    class _Buffers():
        #Doesn't work, can't find 'VB' object at start-up, use global variables
        # id_301 = bytearray(8)
        # id_302 = bytearray(8)
        # id_303 = bytearray(8)
        # id_304 = bytearray(8)
        # id_305 = bytearray(8)
        # id_306 = bytearray(8)
        # id_307 = bytearray(8)
        # id_308 = bytearray(8)
        # id_309 = bytearray(8)
        # id_313 = bytearray(8)
        # id_314 = bytearray(8)
        # id_323 = bytearray(8)
        # id_30A = bytearray(8)
        # id_30B = bytearray(8)
        pass

    class _Pointers(): #not really but closet thing in python!
        ''' Contains objects which 'point' to the relevant byte array
        where CAN frames are stored'''
        id_301 = memoryview(_301)
        id_302 = memoryview(_302)
        id_303 = memoryview(_303)
        id_304 = memoryview(_304)
        id_305 = memoryview(_305)
        id_306 = memoryview(_306)
        id_307 = memoryview(_307)
        id_308 = memoryview(_308)
        id_309 = memoryview(_309)
        id_313 = memoryview(_313)
        id_314 = memoryview(_314)
        id_323 = memoryview(_323)
        id_30A = memoryview(_30A)
        id_30B = memoryview(_30B)

    class _Filters():
        '''Stores filter class for each channel, each attribute is created
            at startup using the 'create_filters' classmethod'''

        @classmethod
        def create_filters(cls):
            for section in dir(VB):
                if (not section.startswith('_')):
                    for channel in dir(getattr(VB, section)):
                        if (not channel.startswith('_')):
                            setattr(VB._Filters, channel, Filter())

    class Standard():
        #301
        Satellites = ["{:02}".format(0), 0x301]
        Time_Since_Mid = ["{:08.2f}".format(0.0), 0x301]
        Lattitude = ["{:08.4f}".format(0.0), 0x301]
        #302
        Longitude = ["{:08.4f}".format(0.0), 0x302]
        Speed = ["{:06.2f}".format(0.0), 0x302]
        Heading = ["{:06.2f}".format(0.0), 0x302]
        #303
        Altitude = ["{:06.2f}".format(0.0), 0x303]
        Vert_Velocity = ["{:06.2f}".format(0.0), 0x303]
        Log_Status = ["{}".format("Unknown"), 0x303] #change
        Trigger_Active = ["{:06.2f}".format(0.0), 0x303]
        #304
        Trigger_Dist = ["{:06.2f}".format(0.0), 0x304]
        Long_Acc = ["{:06.2f}".format(0.0), 0x304]
        Lat_Acc = ["{:06.2f}".format(0.0), 0x304]
        #305
        Distance = ["{:08.2f}".format(0.0), 0x305]
        Trigger_Time = ["{:06.2f}".format(0.0), 0x305]
        Trigger_Speed = ["{:06.2f}".format(0.0), 0x305]
        #306
        Speed_Qual = ["{:06.2f}".format(0.0), 0x306]
        #308
        Position_Qual = ["{:06.2f}".format(0.0), 0x308]
        Solution = ["{:1}".format(0.0), 0x308]

    class Dual_Antenna():
        # #306
        True_Heading = ["{:06.2f}".format(0.0), 0x306]
        Slip_Angle = ["{:06.2f}".format(0.0), 0x306]
        Pitch_Angle = ["{:06.2f}".format(0.0), 0x306]
        # #307
        Lat_Velocity = ["{:06.2f}".format(0.0), 0x307]
        Yaw_Rate = ["{:06.2f}".format(0.0), 0x307]
        Roll_Angle = ["{:06.2f}".format(0.0), 0x307]
        Long_Velocity = ["{:06.2f}".format(0.0), 0x307]
        # Trial = ["{:06.2f}".format(0.0)]
        #313
        Slip_FL = ["{:06.2f}".format(0.0), 0x313]
        Slip_FR = ["{:06.2f}".format(0.0), 0x313]
        Slip_RL = ["{:06.2f}".format(0.0), 0x313]
        Slip_RR = ["{:06.2f}".format(0.0), 0x313]
        Slip_COG = ["{:06.2f}".format(0.0), 0x313]

    class Robot():
        #309
        Robot_Speed = ["{:06.2f}".format(0.0), 0x309]
        #314
        Robot_Sats = ["{:06.2f}".format(0.0), 0x314]
        Robot_TSM = ["{:06.2f}".format(0.0), 0x314]
        Robot_Heading = ["{:06.2f}".format(0.0), 0x314]

    class IMU():
        #323
        Head_IMU = ["{:06.2f}".format(0.0), 0x323]
        Roll_IMU = ["{:06.2f}".format(0.0), 0x323]
        Pitch_IMU = ["{:06.2f}".format(0.0), 0x323]
        KF_Status = ["{:06.2f}".format(0.0), 0x323]
        #601-#604

    class ADAS():
        Range = ["{:06.2f}".format(0.0), 0x30A]
        Lng_Range = ["{:06.2f}".format(0.0), 0x30B]
        Lat_Range = ["{:06.2f}".format(0.0), 0x30B]
        Link_Time = ["{:06.2f}".format(0.0), 0x30D]

class Filter:
    '''Filters on screen values, instance created for each channel'''
    def __init__(self):
        # 2nd order IIR Gauss LP filter, 2.1 Hz BW, 3 Hz wc, fs = 60 Hz
        self.a = (1.0000000000, -1.4950558121,  0.5668457092)
        self.b = (0.0179474742,  0.0358949485,  0.0179474742)
        # Filter state
        self.w = [0, 0]

    def __call__(self, x):
        # Biquad transposed direct form 2
        w = x - self.a[1] * self.w[0] - self.a[2] * self.w[1]
        y = self.b[0] * w + self.b[1] * self.w[0] + self.b[2] * self.w[1]
        self.w[1] = self.w[0]
        self.w[0] = w
        return y

def init_can(baud):
    '''Initialises CAN'''
    for channel in (can.CAN0, can.CAN1):
        can.reset(channel, baud[0])
        can.termination(channel, True)
        can.test_mode(channel, 0)
        for id in all_can_ids:
            can.add_rx_id(channel, id)

    can.set_callback(can.CAN0, can0_cb)
    can.set_callback(can.CAN1, can1_cb)

def bytes_to_int(bytes):
    result = 0
    for b in bytes:
        result = result * 256 + int(b)
    return result

def parse_data(channels):
    '''All CAN Data is decoded here'''
    #Only parse data for displayed channels - increases performance
    for id in channels:
        if id == 0x301:
            VB.Standard.Satellites[0] = "{:02}".format(_301[0])
            VB.Standard.Time_Since_Mid[0] = "{:08.1f}".format(bytes_to_int(_301[1:4])*0.01)
            VB.Standard.Lattitude[0] = "{:09.5f}".format(VB._Filters.Lattitude(us.unpack('>i', bytes(_301[4:]))[0]/100000))

        elif id == 0x302:
            VB.Standard.Longitude[0] = "{:08.5f}".format(VB._Filters.Longitude(us.unpack('>i', bytes(_302[:4]))[0]/100000))
            VB.Standard.Speed[0] = "{:06.2f}Km/h".format(VB._Filters.Speed(bytes_to_int(_302[4:6])*0.0182))
            VB.Standard.Heading[0] = "{:06.2f}".format(VB._Filters.Heading(bytes_to_int(_302[6:])*0.01))

        elif id == 0x303:
            VB.Standard.Altitude[0] = "{:06.2f}m".format(VB._Filters.Altitude(int.from_bytes(bytes(_303[:3]), "big", True)*0.01))
            VB.Standard.Vert_Velocity[0] = "{:06.2f}m/s".format(VB._Filters.Vert_Velocity(us.unpack('>h', bytes(_303[3:5]))[0]*0.01))
            log_stat = "Active" if bit_check(_303[6],3) else "Inactive"
            VB.Standard.Log_Status[0] = "{}".format(log_stat)

        elif id == 0x304:
            VB.Standard.Trigger_Dist[0] = "{:07.2f}m".format(bytes_to_int(_304[:4])*0.000078125)
            VB.Standard.Long_Acc[0] = "{:04.2f}g".format(VB._Filters.Long_Acc(us.unpack('>h', bytes(_304[4:6]))[0]*0.01))
            VB.Standard.Lat_Acc[0] = "{:04.2f}g".format(VB._Filters.Lat_Acc(us.unpack('>h', bytes(_304[6:]))[0]*0.01))

        elif id == 0x305:
            VB.Standard.Distance[0] = "{:07.2f}m".format(bytes_to_int(_305[:4])*0.000078125)
            VB.Standard.Trigger_Time[0] = "{:06.2f}s".format(bytes_to_int(_305[4:6])*0.01)
            VB.Standard.Trigger_Speed[0] = "{:05.2f}km/h".format(VB._Filters.Trigger_Speed(bytes_to_int(_305[6:])*0.0182))
        elif id == 0x306:
            VB.Standard.Speed_Qual[0] = "{:05.2f}".format(bytes_to_int(_306[:2])*0.0182)
            VB.Dual_Antenna.True_Heading[0] = "{:05.2f}".format(VB._Filters.True_Heading(us.unpack('>h', bytes(_306[2:4]))[0]*0.01))
            VB.Dual_Antenna.Slip_Angle[0] = "{:05.2f}".format(VB._Filters.Slip_Angle(us.unpack('>h', bytes(_306[4:6]))[0]*0.01))
            VB.Dual_Antenna.Pitch_Angle[0] = "{:05.2f}".format(VB._Filters.Pitch_Angle(us.unpack('>h', bytes(_306[6:]))[0]*0.01))
        elif id == 0x307:
            VB.Dual_Antenna.Lat_Velocity[0] = "{:05.2f}km/h".format(VB._Filters.Lat_Velocity(us.unpack('>h', bytes(_307[:2]))[0]*0.01))
            VB.Dual_Antenna.Yaw_Rate[0] = "{:05.2f} d/s".format(VB._Filters.Yaw_Rate(us.unpack('>h', bytes(_307[2:4]))[0]*0.01))
            VB.Dual_Antenna.Roll_Angle[0] = "{:05.2f}".format(VB._Filters.Roll_Angle(us.unpack('>h', bytes(_307[4:6]))[0]*0.01))
            VB.Dual_Antenna.Long_Velocity[0] = "{:05.2f}km/h".format(VB._Filters.Long_Velocity(us.unpack('>h', bytes(_307[6:]))[0]*0.01))
        elif id == 0x308:
            VB.Standard.Position_Qual[0] = "{:02}".format(_308[6])
            VB.Standard.Solution[0] = "{:01}".format(_308[7])
        elif id == 0x309:
            VB.Robot.Robot_Speed[0] = "{:06.2f}km/h".format(us.unpack('>h', bytes(_309[6:]))[0]*0.0182)
        elif id == 0x313:
            VB.Dual_Antenna.Slip_FL[0] = "{:06.2f}".format(us.unpack('>h', bytes(_313[:2]))[0]*0.01)
            VB.Dual_Antenna.Slip_FR[0] = "{:06.2f}".format(us.unpack('>h', bytes(_313[2:4]))[0]*0.01)
            VB.Dual_Antenna.Slip_RL[0] = "{:06.2f}".format(us.unpack('>h', bytes(_313[4:6]))[0]*0.01)
            VB.Dual_Antenna.Slip_RR[0] = "{:06.2f}".format(us.unpack('>h', bytes(_313[6:]))[0]*0.01)
        elif id == 0x314:
            VB.Dual_Antenna.Slip_COG[0] = "{:06.2f}".format(us.unpack('>h', bytes(_314[:2]))[0]*0.01)
            VB.Robot.Robot_Sats[0] = "{:02}".format(_314[2])
            VB.Robot.Robot_TSM[0] = "{:08.1f}".format(bytes_to_int(_314[3:6])*0.01)
            VB.Robot.Robot_Heading[0] = "{:06.2f}".format(VB._Filters.Robot_Heading(bytes_to_int(_314[6:])*0.01))
        elif id == 0x323:
            VB.IMU.Head_IMU[0] = "{:06.2f}".format(VB._Filters.Head_IMU(bytes_to_int(_323[:2])*0.01))
            VB.IMU.Roll_IMU[0] = "{:06.2f}".format(VB._Filters.Roll_IMU(us.unpack('>h', bytes(_323[2:4]))[0]*0.01))
            VB.IMU.Pitch_IMU[0] = "{:06.2f}".format(VB._Filters.Pitch_IMU(us.unpack('>h', bytes(_323[4:6]))[0]*0.01))
            VB.IMU.KF_Status[0] = "{:04}".format(bytes_to_int(_323[6:])) # 12 bit number so probably needs shifting and mask, but works for now!

        elif id == 0x30A:
            VB.ADAS.Range[0] = "{:06.2f}".format(VB._Filters.Range(us.unpack('>f', bytes(_30A[0:4]))[0]))
        elif id == 0x30B:
            VB.ADAS.Lng_Range[0] = "{:06.2f}".format(VB._Filters.Range(us.unpack('>f', bytes(_30B[0:4]))[0]))
            VB.ADAS.Lat_Range[0] = "{:06.2f}".format(VB._Filters.Range(us.unpack('>f', bytes(_30B[4:]))[0]))

def handle_can(bus):
    '''Moves any new messages to relevant byte arrays via MemoryView objects'''
    # Don't use this to process CAN Data - Causes Performance Issues
    global VB
    # time_it('start')
    while True:
        can_msg = can.get_msg(bus)
        if can_msg is None:
            return
            # time_it('End')
        if can_msg.id == 0x301:
            for i in range(8):
                VB._Pointers.id_301[i] = can_msg.data[i]

        elif can_msg.id == 0x302:
            for i in range(8):
                VB._Pointers.id_302[i] = can_msg.data[i]

        elif can_msg.id == 0x303:
            for i in range(8):
                VB._Pointers.id_303[i] = can_msg.data[i]

        elif can_msg.id == 0x304:
            for i in range(8):
                VB._Pointers.id_304[i] = can_msg.data[i]

        elif can_msg.id == 0x305:
            for i in range(8):
                VB._Pointers.id_305[i] = can_msg.data[i]

        elif can_msg.id == 0x306:
            for i in range(8):
                VB._Pointers.id_306[i] = can_msg.data[i]

        elif can_msg.id == 0x307:
            for i in range(8):
                VB._Pointers.id_307[i] = can_msg.data[i]

        elif can_msg.id == 0x308:
            for i in range(8):
                VB._Pointers.id_308[i] = can_msg.data[i]

        elif can_msg.id == 0x309:
            for i in range(8):
                VB._Pointers.id_309[i] = can_msg.data[i]

        elif can_msg.id == 0x313:
            for i in range(8):
                VB._Pointers.id_313[i] = can_msg.data[i]

        elif can_msg.id == 0x314:
            for i in range(8):
                VB._Pointers.id_314[i] = can_msg.data[i]

        elif can_msg.id == 0x323:
            for i in range(8):
                VB._Pointers.id_323[i] = can_msg.data[i]

        elif can_msg.id == 0x30A:
            for i in range(8):
                VB._Pointers.id_30B[i] = can_msg.data[i]

        elif can_msg.id == 0x30B:
            for i in range(8):
                VB._Pointers.id_30A[i] = can_msg.data[i]
        # # can_t = time_it('End')
        # # print(can_t)
        # # if can_t > 10:
        #     # print("CAN Time Exceeded: {}ms".format(can_t))
        # return

def can0_cb():
    handle_can(can.CAN0)

def can1_cb():
    handle_can(can.CAN1)

def bit_check(message , bit):
    '''Checks if specific bits in bytes are set'''
    if message & (1<<bit):
        return True
    else:
        return False

#####Page Display######

class base_page():
    ''' Class object which each main page is created from'''
    created_pages = []
    active_page = None
    previous_page = 1
    new_parameter = None
    new_name = None

    @classmethod
    def change_page(cls, p_no):
        '''Changes active page'''
        for p in cls.created_pages:
            if p.page == p_no:
                p.set_active()

    def __init__(self, page_no = 0):
        self.page = page_no
        self.h1 = ['SATS']
        self.v1 = VB.Standard.Satellites
        self.h2 = ['SPEED']
        self.v2 = VB.Standard.Speed
        self.h3 = ['HEADING']
        self.v3 = VB.Standard.Heading
        self.h4 = ['SOLUTION']
        self.v4 = VB.Standard.Solution
        self.active_ids = [0x301,0x302,0x303,0x308]
        self.selection = 0
        self.created_pages.append(self)

    def set_active(self):
        base_page.active_page = self

    def draw(self):
        '''Instructions for drawing page'''
        page_list = [
            gui.SUBLIST,
            [gui.CTRL_TEXT, 680, 423, hf, gui.OPT_CENTERX, 'Page {} of {}'.format(self.page, len(base_page.created_pages))],
            # [gui.CTRL_FLATBUTTON, 20, 420, 200,50,29,'Baud = {}'.format(baudrate[0]), self.change_baud_cb], #Don't use, can't run at 1MB/s yet!
            [gui.DL_COLOR_RGB(0, 0, 0)],
            [gui.DL_LINE_WIDTH(2)],
            [gui.DL_BEGIN(gui.PRIM_LINES)],
            [gui.DL_VERTEX2F(400, 65)],
            [gui.DL_VERTEX2F(400, 415)],
            [gui.DL_VERTEX2F(0, 240)],
            [gui.DL_VERTEX2F(800, 240)],
            [gui.DL_END()],

            [gui.CTRL_HOT_TEXT, 200, 90, hf, gui.OPT_CENTERX, self.h1, self.set_value_cb],
            [gui.CTRL_TEXT, 200, 160, vf, gui.OPT_CENTERX, self.v1],

		    [gui.CTRL_HOT_TEXT, 600, 90, hf, gui.OPT_CENTERX, self.h2, self.set_value_cb],
            [gui.CTRL_TEXT, 600, 160, vf, gui.OPT_CENTERX, self.v2],

		    [gui.CTRL_HOT_TEXT, 200, 270, hf, gui.OPT_CENTERX, self.h3, self.set_value_cb],
            [gui.CTRL_TEXT, 200, 340, vf, gui.OPT_CENTERX, self.v3],

		    [gui.CTRL_HOT_TEXT, 600, 270, hf, gui.OPT_CENTERX, self.h4, self.set_value_cb],
            [gui.CTRL_TEXT, 600, 340, vf, gui.OPT_CENTERX,  self.v4],

            # [gui.DL_COLOR_RGB(255, 0, 0)],
            # [gui.CTRL_TEXT, 20, 423, vf, gui.OPT_LEFTX, log_status]
        ]

        return page_list

    def set_value_cb(self, button):
        '''called when user presses on channel heading, starts
            the channel selection for relevant section'''
        if self.selection is 0:
            if button[1] == 200:
                if button[2] == 90:
                    self.selection = 1
                    self.draw_options()
                else:
                    self.selection = 3
                    self.draw_options()
            elif button[1] == 600:
                if button[2]== 270:
                    self.selection = 4
                    self.draw_options()
                else:
                    self.selection = 2
                    self.draw_options()
            else:
                raise Exception("Unknown Button Press")
        else:
            raise Exception("Selection has not been reset")

    def draw_options(self):
        '''Activates channel selection mode and starts screen redraw'''
        # print('Option:{}'.format(self.selection))
        selection_page.active = True
        selection_page.page = self
        draw()         # temporary

    def set_value(self, section, name, option=0):
        '''Sets the on-screen heading and value to the chosen channel
            for the relevant section'''
        if option == 0:
            option = self.selection
        p_val = "v" + str(self.selection)
        h_val = "h" + str(self.selection)
        setattr(self, h_val, name.upper())
        setattr(self, p_val, getattr(section, name))
        self.active_ids.clear()
        for opt in (self.v1,self.v2,self.v3,self.v4):
            self.active_ids.append(opt[1])
        # print("New Active IDs: {}".format(self.active_ids))
        selection_page.teardown()
        self.selection = 0

    def change_baud_cb(self, button):
        #Don't use, can't run at 1MB/s yet!
        global baudrate
        if baudrate[0] == 500000:
            baudrate[0] = 1000000
        else:
            baudrate[0] = 500000
        init_can(baudrate)

class selection_page():
    '''Class to store selection page mode options'''

    active = False
    available_channels = []
    act_page = 1
    page = None
    section = None

    @classmethod
    def set_section(cls, button):
        selection_page.section = button[8]
        draw()

    @classmethod
    def draw_sections(cls):
        '''Instructions for drawing CAN Section options'''
        gui_input_list = [
            gui.SUBLIST,
            [gui.CTRL_FLATBUTTON, 630, 422, 150, 50, 30,'Back', selection_page.teardown]
        ]

        results = [x for x in dir(VB) if not x.startswith('_')]
        results.sort(reverse=True)

        for i, section in enumerate(results):
            gui_input_list.append([gui.DL_COLOR_RGB(0, 0, 0)])
            gui_input_list.append([gui.CTRL_TEXT, 150, 75 + (i%5) *70, hf,0,str(section)])
            gui_input_list.append([gui.DL_COLOR_RGB(255, 255, 255)])
            gui_input_list.append([gui.CTRL_FLATBUTTON, 500, 75 + (i%5) *70, 150,50,29,'Select', selection_page.set_section, str(results[i])])

        return gui_input_list

    @classmethod
    def draw_channels(cls, page=0):
        ''' Instructions for drawing available channels in that section'''
        if page == 0:
            page = selection_page.act_page
        gui_input_list = [
            gui.SUBLIST,
            [gui.CTRL_FLATBUTTON, 630, 422, 150, 50, 30,'Back', selection_page.back_to_sections],
        ]

        selection_page.check_available(selection_page.section)

        if len(selection_page.available_channels) > 10:
            results = selection_page.available_channels[10*(selection_page.act_page-1):10*(selection_page.act_page-1)+10]
        else:
            results = selection_page.available_channels
            page = 1

        impl_channels = ('Standard', 'Dual_Antenna')
        #Temporary until all CAN Channels Implimented
        if selection_page.section not in impl_channels:
            gui_input_list.append([gui.CTRL_TEXT, 10, 435, 23, 0, 'NOTE: REMAINING CHANNELS AWAITING IMPLIMENTATION'])
        #Amend when other sections implimented


        t_pages = ((len(selection_page.available_channels)-1)//10 + 1)
        if t_pages < page:
            t_pages = page

        page_no = "{} OF {}".format(page,t_pages)

        gui_input_list.append([gui.DL_COLOR_RGB(255, 255, 255)])
        gui_input_list.append([gui.CTRL_TEXT, 690, 10, 30, 0, str(page_no)])

        for i, storedValue in enumerate(results):
            gui_input_list.append([gui.DL_COLOR_RGB(0, 0, 0)])
            gui_input_list.append([gui.CTRL_TEXT, 20+390* (i // 5), 80 + (i%5) *70, 29,0,str(storedValue)])
            gui_input_list.append([gui.DL_COLOR_RGB(255, 255, 255)])
            gui_input_list.append([gui.CTRL_FLATBUTTON, 220+390* (i // 5), 75 + (i%5) *70, 150,50,29,'Select',selection_page.select_channel, str(10*(page-1)+i)])

        return gui_input_list

    @classmethod
    def check_available(cls, section):
        '''fetches available channels for that section from VB class'''
        try:
            selection_page.available_channels = [x for x in dir(getattr(VB, section)) if (not x.startswith('_'))]
        except Exception as e:
            print("Error Collecting Available Channels: {}".format(e))
        selection_page.available_channels.sort() # sorts alphabetically

    @classmethod
    def select_channel(cls, button):
        '''Called when channel is chosen, passes chosen channel back to base page'''
        name = selection_page.available_channels[int(button[8])]
        # print("Selected Channel: {}".format(name))
        section = getattr(VB, selection_page.section)
        selection_page.page.set_value(section, name)

    @classmethod
    def back_to_sections(cls,button):
        '''called if user presses 'back' button, moves back to section page'''
        selection_page.section = None
        selection_page.active = 1
        draw()

    @classmethod
    def teardown(cls, button = None):
        '''sets page back to default when leaving selection page'''
        selection_page.act_page = 1
        selection_page.active = False
        selection_page.available_channels.clear()
        selection_page.section = None
        selection_page.page.selection = 0
        selection_page.page = None
        draw()
        gc.collect()

def drawPage():
    '''chooses which page to draw when 'draw' function called'''
    if selection_page.active:
        if selection_page.section is not None:
            return selection_page.draw_channels()
        else:
            return selection_page.draw_sections()
    else:
        return base_page.active_page.draw()

def draw():
    '''Main page drawing function, contains background, header/footer and
        all screen interaction instructions (swipe, press, vsync)'''
    swipe_r = 50
    gui_l = [
        [gui.EVT_VSYNC, vsync_cb],
        [gui.EVT_SWIPE, swipe_r, swipe_cb],
        [gui.EVT_PRESS, press_cb],
        [gui.DL_SAVE_CONTEXT()],
        [gui.DL_CLEAR_COLOR_RGB(0, 41, 66)],
        [gui.DL_CLEAR(1, 1, 1)],
        [gui.DL_SCISSOR_SIZE(800, 350)],
        [gui.DL_SCISSOR_XY(0, 65)],
        [gui.DL_CLEAR_COLOR_RGB(255, 255, 255)],
        [gui.DL_CLEAR(1, 1, 1)],
        [gui.DL_RESTORE_CONTEXT()],
        [gui.DL_COLOR_RGB(255, 255, 255)],
        [gui.CTRL_TEXT, 400, 3, 32, gui.OPT_CENTERX, 'VBOX CAN DISPLAY'],
    ]

    gui_l.append(drawPage())

    gui.show(gui_l)

# Main #########################################################################
def main():
    init_can(baudrate)
    global page_1, page_2, page_3
    page_1, page_2, page_3 = base_page(1), base_page(2), base_page(3)
    page_1.set_active()
    VB._Filters.create_filters()
    draw()
    # vts.gc_verbose(True)


if __name__ == '__main__':
    main()