"""
Main script of the spaceship generator.  run this to launch the OpenGL window.
Contains high-level code pertaining creation and drawing of:
Model
GUI
Skybox
As well as OpenGL callback functions
"""


import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import pyrr
from TextureLoader import load_texture
from Camera import Camera, SimulationCamera
from math import pi
import random as r
import glm
from PIL import Image, ImageOps
import PointLightCube as plc
from ProceduralMesh import Spaceship3x3
import FPSCounter
from GUI import GUI
import Skybox as CubeMapSkybox
from tkinter import filedialog
import gc

DEV_BUILD = True
images = []

follow_cam = SimulationCamera(camera_pos=[6.0, 247.0, 242.0])
cam = Camera(camera_pos=[0.0, 20.0, 20.0])
use_follow_cam = False


def capture_screenshot(filename='screenshot.jpg'):
    print(f'saving screenshot as {filename}')
    fbo_array = glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE)
    screenshot = Image.frombytes("RGB", (WIDTH, HEIGHT), fbo_array)
    screenshot = ImageOps.flip(screenshot)
    screenshot.save(
            filename,
    )



WIDTH, HEIGHT = 1728, 972
lastX, lastY = WIDTH / 2, HEIGHT / 2
first_mouse = True
left, right, forward, backward, make_new_surface = False, False, False, False, False
player_left, player_right, player_forward, player_backward = False, False, False, False
yaw_counterclockwise, yaw_clockwise = False, False
up, down = False, False
write_to_gif = False
wrote_to_gif = False
make_new_ship = False
pause = False
ship_texture_cycle_id = 0

# the keyboard input callback
def key_input_clb(window, key, scancode, action, mode):
    global left, right, forward, backward, make_new_surface, player_left, player_right, player_forward, \
        player_backward, yaw_counterclockwise, yaw_clockwise, write_to_gif, make_new_ship,\
        pause, ship_texture_cycle_id, up, down, wrote_to_gif

    if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
        glfw.set_window_should_close(window, True)
    if key == glfw.KEY_W and action == glfw.PRESS:
        forward = True
    elif key == glfw.KEY_W and action == glfw.RELEASE:
        forward = False
    if key == glfw.KEY_S and action == glfw.PRESS:
        backward = True
    elif key == glfw.KEY_S and action == glfw.RELEASE:
        backward = False
    if key == glfw.KEY_A and action == glfw.PRESS:
        left = True
    elif key == glfw.KEY_A and action == glfw.RELEASE:
        left = False
    if key == glfw.KEY_D and action == glfw.PRESS:
        right = True
    elif key == glfw.KEY_D and action == glfw.RELEASE:
        right = False
    if key == glfw.KEY_Q and action == glfw.PRESS:
        yaw_clockwise = True
    elif key == glfw.KEY_Q and action == glfw.RELEASE:
        yaw_clockwise = False
    if key == glfw.KEY_E and action == glfw.PRESS:
        yaw_counterclockwise = True
    elif key == glfw.KEY_E and action == glfw.RELEASE:
        yaw_counterclockwise = False
    if key == glfw.KEY_TAB and action == glfw.PRESS:
        up = True
    elif key == glfw.KEY_TAB and action == glfw.RELEASE:
        up = False
    if key == glfw.KEY_LEFT_SHIFT and action == glfw.PRESS:
        down = True
    elif key == glfw.KEY_LEFT_SHIFT and action == glfw.RELEASE:
        down = False
    if key == glfw.KEY_1 and action == glfw.PRESS:
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    if key == glfw.KEY_2 and action == glfw.PRESS:
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    if key == glfw.KEY_9 and action == glfw.PRESS:
        if DEV_BUILD:
            write_to_gif = not write_to_gif
            wrote_to_gif = True
    if key == glfw.KEY_SPACE and action == glfw.PRESS:
        generate_next_ship()
    if key == glfw.KEY_P and action == glfw.PRESS:
        pause = not pause
    if key == glfw.KEY_T and action == glfw.PRESS:
        cycle_ship_texture()
    if key == glfw.KEY_V and action == glfw.PRESS:
        switch_camera_mode()
    if key == glfw.KEY_K and action == glfw.PRESS:
        if DEV_BUILD:
            print_camera_position()
    if key == glfw.KEY_F12 and action == glfw.PRESS:
        capture_screenshot()


def cycle_ship_texture(forward=True):
    global ship_texture_cycle_id
    MIN = 0
    MAX = 4

    if forward:
        ship_texture_cycle_id = min(ship_texture_cycle_id + 1, MAX)
    else:
        ship_texture_cycle_id = max(ship_texture_cycle_id - 1, MIN)

    if ship_texture_cycle_id == 0:
        spaceship_parameters['diffuse'] = texture_dictionary['penguin_diffuse']
        spaceship_parameters['specular'] = texture_dictionary['penguin_specular']
        spaceship_parameters['emission'] = texture_dictionary['penguin_emission']
        update_spaceship_texture()
    elif ship_texture_cycle_id == 1:
        spaceship_parameters['diffuse'] = texture_dictionary['ship_a_diffuse']
        spaceship_parameters['specular'] = texture_dictionary['ship_a_specular']
        spaceship_parameters['emission'] = texture_dictionary['ship_a_emission']
        update_spaceship_texture()
    elif ship_texture_cycle_id == 2:
        spaceship_parameters['diffuse'] = texture_dictionary['whoa_diffuse']
        spaceship_parameters['specular'] = texture_dictionary['whoa_specular']
        spaceship_parameters['emission'] = texture_dictionary['atlas_debug_emission']
        update_spaceship_texture()
    elif ship_texture_cycle_id == 3:
        spaceship_parameters['diffuse'] = texture_dictionary['blue_metal_diffuse']
        spaceship_parameters['specular'] = texture_dictionary['blue_metal_specular']
        spaceship_parameters['emission'] = texture_dictionary['blue_metal_emission']
        update_spaceship_texture()
    elif ship_texture_cycle_id == 4:
        spaceship_parameters['diffuse'] = texture_dictionary['atlas_debug_diffuse']
        spaceship_parameters['specular'] = texture_dictionary['atlas_debug_specular']
        spaceship_parameters['emission'] = texture_dictionary['atlas_debug_emission']
        update_spaceship_texture()


def mouse_button_callback(window, button, action, mods):
    global make_new_ship
    left_click = button == glfw.MOUSE_BUTTON_LEFT and action == glfw.PRESS
    right_click = button == glfw.MOUSE_BUTTON_RIGHT and action == glfw.PRESS
    if button == left_click:
       mpos = glfw.get_cursor_pos(window)
    gui.button_update(position_mouse=glfw.get_cursor_pos(window), left_click=left_click, right_click=right_click)


def update_spaceship_texture():
    meshes[0].set_diffuse(spaceship_parameters['diffuse'])
    meshes[0].set_specular(spaceship_parameters['specular'])
    meshes[0].set_emission(spaceship_parameters['emission'])


def do_movement(speed=1.0):
    """
    do the camera movement, call this function in the main loop
    :param speed:
    :return:
    """
    if left:
        active_camera.process_keyboard("LEFT", speed)
    if right:
        active_camera.process_keyboard("RIGHT", speed)
    if forward:
        active_camera.process_keyboard("FORWARD", speed)
    if backward:
        active_camera.process_keyboard("BACKWARD", speed)
    if yaw_clockwise:
        follow_cam.process_keyboard("YAW_CLOCKWISE", speed)
    if yaw_counterclockwise:
        follow_cam.process_keyboard("YAW_COUNTERCLOCKWISE", speed)
    if up:
        active_camera.process_keyboard("UP", speed)
    if down:
        active_camera.process_keyboard("DOWN", speed)




def mouse_look_clb(window, xpos, ypos):
    global first_mouse, lastX, lastY
    if first_mouse:
        lastX = xpos
        lastY = ypos
        first_mouse = False
    xoffset = xpos - lastX
    yoffset = lastY - ypos
    lastX = xpos
    lastY = ypos
    cam.process_mouse_movement(xoffset, yoffset)

def scroll_callback(window, xoffset, yoffset):
    follow_cam.process_mouse_scroll(xoffset, yoffset)


vertex_src = """
# version 330

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_texture;
layout(location = 2) in vec3 a_normal;

uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;

out vec2 tex_coords; //texture coordinates
out vec3 normal;
out vec3 frag_pos;


void main()
{
    tex_coords = a_texture;
    frag_pos = vec3(model * vec4(a_position, 1.0));
    normal = mat3(transpose(inverse(model))) * a_normal;
    gl_Position = projection * view * vec4(frag_pos, 1.0);
}
"""

fragment_src = """
#version 330 core
out vec4 frag_color;

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    sampler2D emission;
    float shininess;
}; 

struct DirLight {
    vec3 direction;	
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct PointLight {
    vec3 position; 
    float constant;
    float linear;
    float quadratic;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct SpotLight {
    vec3 position;
    vec3 direction;
    float cut_off;
    float outer_cut_off;
    float constant;
    float linear;
    float quadratic;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;       
};

#define NR_POINT_LIGHTS 2

in vec3 frag_pos;
in vec3 normal;
in vec2 tex_coords;

uniform vec3 view_pos;
uniform DirLight dir_light;
uniform PointLight point_lights[NR_POINT_LIGHTS];
uniform SpotLight spot_light;
uniform Material material;

// function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 view_dir, vec2 tex_coords);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 frag_pos, vec3 view_dir, vec2 tex_coords);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 frag_pos, vec3 view_dir);

void main()
{    
    vec2 tex_coords_alternate =  tex_coords;
    
    /*
    float q_coord = 0.1f;
    if (tex_coords_alternate.x > 0.9f && tex_coords_alternate.y > 0.9f){
        tex_coords_alternate = tex_coords_alternate * q_coord;
    }
    if (tex_coords_alternate == vec2(0.f, 1.f)){
        tex_coords_alternate = tex_coords_alternate * q_coord;
    }
    */
        
    // properties
    vec3 norm = normalize(normal);
    vec3 view_dir = normalize(view_pos - frag_pos);

    // == =====================================================
    // Our lighting is set up in 3 phases: directional, point lights and an optional flashlight
    // For each phase, a calculate function is defined that calculates the corresponding color
    // per lamp. In the main() function we take all the calculated colors and sum them up for
    // this fragment's final color.
    // == =====================================================
    vec3 result;
    // phase 1: directional lighting
    result = CalcDirLight(dir_light, norm, view_dir, tex_coords_alternate);
    // phase 2: point lights
    for(int i = 0; i < NR_POINT_LIGHTS; i++)
        result += CalcPointLight(point_lights[i], norm, frag_pos, view_dir, tex_coords_alternate);    
    // phase 3: spotlight
    //result += CalcSpotLight(spot_light, norm, frag_pos, view_dir);    
    // emission
    vec3 emission = texture(material.emission, tex_coords_alternate).rgb;
    result = result + emission;
    frag_color = vec4(result, 1.0);
}

// calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 view_dir, vec2 tex_coords)
{
    vec3 light_dir = normalize(-light.direction);

    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0), material.shininess);
    // combine results
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, tex_coords));
    return (ambient + diffuse + specular);
}

// calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 frag_pos, vec3 view_dir, vec2 tex_coords)
{
    vec3 light_dir = normalize(light.position - frag_pos);
    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0), material.shininess);
    // attenuation
    float distance = length(light.position - frag_pos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // combine results
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, tex_coords));
    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
}

// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 frag_pos, vec3 view_dir)
{
    vec3 light_dir = normalize(light.position - frag_pos);
    //view_dir = vec3(0.0, -1.0, 0.0); //tp

    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);

    // specular shading
    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0), material.shininess);

    // attenuation
    float distance = length(light.position - frag_pos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // spotlight intensity
    float theta = dot(light_dir, normalize(-light.direction)); 
    float epsilon = light.cut_off - light.outer_cut_off;
    float intensity = clamp((theta - light.outer_cut_off) / epsilon, 0.0, 1.0);
    // combine results
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, tex_coords));
    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}
"""

# the window resize callback function
def window_resize_clb(window, width, height):
    glViewport(0, 0, width, height)
    projection = pyrr.matrix44.create_perspective_projection_matrix(45, width / height, 0.1, 2000)
    glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection)
    gui.set_screen_size(screen_size=(width, height))


# initializing glfw library
if not glfw.init():
    raise Exception("glfw can not be initialized!")

# creating the window
window = glfw.create_window(WIDTH, HEIGHT, "Spaceship Generator", None, None)

# check if window was created
if not window:
    glfw.terminate()
    raise Exception("glfw window can not be created!")

# set window's position
glfw.set_window_pos(window, 40, 40)

# set the callback function for window resize
glfw.set_window_size_callback(window, window_resize_clb)
# set the mouse position callback
glfw.set_cursor_pos_callback(window, mouse_look_clb)
# set the keyboard input callback
glfw.set_key_callback(window, key_input_clb)
#set the scroll-wheel input callback
glfw.set_scroll_callback(window, scroll_callback)
#set the mouse callback
glfw.set_mouse_button_callback(window, mouse_button_callback)
# capture the mouse cursor
# glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
# glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_CAPTURED)

# make the context current
glfw.make_context_current(window)

def switch_camera_mode():
    global use_follow_cam, active_camera
    use_follow_cam = not use_follow_cam
    if use_follow_cam:
        glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_NORMAL) #glfw.CURSOR_CAPTURED,
        active_camera = follow_cam
    else:
        glfw.set_input_mode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
        active_camera = cam
switch_camera_mode()

def print_camera_position():
    global active_cam
    print(f'Camera Position: {active_camera.camera_pos}')

shader = compileProgram(compileShader(vertex_src, GL_VERTEX_SHADER), compileShader(fragment_src, GL_FRAGMENT_SHADER))


#uncomment to enable backfase culling
glEnable(GL_CULL_FACE)
#uncomment to see that cull is working by culling front faces rather than back
# glCullFace(GL_BACK)
# glFrontFace(GL_CW)

glUseProgram(shader)

# set the texture unit (integer) of each sampler2D
glUniform1i(glGetUniformLocation(shader, "material.diffuse"), 0)
glUniform1i(glGetUniformLocation(shader, "material.specular"), 1)
glUniform1i(glGetUniformLocation(shader, "material.emission"), 2)

glClearColor(0, 0.1, 0.1, 1)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

projection_2 = pyrr.matrix44.create_perspective_projection_matrix(45, WIDTH / HEIGHT, 0.1, 2000)

model_loc = glGetUniformLocation(shader, "model")
proj_loc = glGetUniformLocation(shader, "projection")
view_loc = glGetUniformLocation(shader, "view")

view_pos_loc = glGetUniformLocation(shader, "view_pos")  # cam pos for specular lighting

glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection_2)


"""direction light settings"""
direction_diffuse = [0.0]*3
direction_ambient = [0.0]*3
direction_specular = [0.0]*3
# create the light cube
my_plc = plc.PointLightCube(pos=[0.0, 40.0, 0.0], ambient=[0.1]*3, diffuse=[0.9]*3,
                            specular=[0.9]*3, linear=0.0014, quadratic=.000007)
debug_plcs = [my_plc]
move_whole_compass_z = -80
debug_plcs.append(plc.PointLightCube(
    pos=[0.0, 10.0, move_whole_compass_z+5.0],
    ambient=[0.0]*3,
    diffuse=[0.0]*3,
    specular=[0.0]*3
))
debug_plcs.append(plc.PointLightCube(
    pos=[0.0, 5.0, move_whole_compass_z+10.0],
    ambient=[0.0, 0.0, 1.0],
    diffuse=[0.0, 0.0, 1.0],
    specular=[0.0, 0.0, 1.0],
))
debug_plcs.append(plc.PointLightCube(
    pos=[0.0, 5.0, move_whole_compass_z+0.0],
    ambient=[1.0, 0.0, 0.0],
    diffuse=[1.0, 0.0, 0.0],
    specular=[1.0, 0.0, 0.0],
))
debug_plcs.append(plc.PointLightCube(
    pos=[5.0, 5.0, move_whole_compass_z+5.0],
    ambient=[0.0, 1.0, 0.0],
    diffuse=[0.0, 1.0, 0.0],
    specular=[0.0, 1.0, 0.0],
))
debug_plcs.append(plc.PointLightCube(
    pos=[-5.0, 5.0, move_whole_compass_z+5.0],
    ambient=[1.0, 1.0, 0.0],
    diffuse=[1.0, 1.0, 0.0],
    specular=[1.0, 1.0, 0.0],
))

seed = 1946
r.seed(seed)


# direction light pass to shader
glUniform3fv(glGetUniformLocation(shader, "dir_light.position"), 1, [0, 20, 10])
glUniform3fv(glGetUniformLocation(shader, "dir_light.diffuse"), 1, direction_diffuse)
glUniform3fv(glGetUniformLocation(shader, "dir_light.ambient"), 1, direction_ambient)
glUniform3fv(glGetUniformLocation(shader, "dir_light.specular"), 1, direction_specular)


textures = glGenTextures(25)
load_texture("Textures/photo_texture_atlas_diffuse_3x3.png", textures[0])
load_texture("Textures/photo_texture_atlas_specular_3x3.png", textures[1])
load_texture("Textures/photo_texture_atlas_emission.png", textures[2])
load_texture("Textures/debug_quad_red.png", textures[3])
load_texture("Textures/penguin_atlas_specular.png", textures[4])
load_texture("Textures/penguin_atlas_specular.png", textures[5])
load_texture("Textures/debug_texture_atlas.png", textures[6])
load_texture("Textures/blue_gold_alt_diffuse.png", textures[7])
load_texture("Textures/blue_gold_specular.png", textures[8])
load_texture("Textures/no_emission.png", textures[9])
load_texture("Textures/debug_diffuse_coordinates.png", textures[10])
load_texture("Textures/penguin_atlas_emission.png", textures[11])
load_texture("Textures/penguin_atlas_specular.png", textures[12])
load_texture("Textures/whoa_atlas_diffuse_3x3.png", textures[13])
load_texture("Textures/whoa_atlas_specular_3x3.png", textures[14])
load_texture("Textures/penguin_atlas_emission.png", textures[15])
load_texture("Fonts/my_font.png", textures[16])
load_texture("Textures/button_atlas_gradient.png", textures[17])
load_texture("Textures/dark_metal_diffuse.png", textures[18])
load_texture("Textures/dark_metal_specular.png", textures[19])
load_texture("Textures/dark_metal_emission.png", textures[20])
load_texture("Textures/SkyboxLearnOpenGL.png", textures[21])


texture_dictionary = {
    "pink": textures[3],
    "penguin_diffuse": textures[0],
    "penguin_specular": textures[1],
    "penguin_emission": textures[2],
    "blue_metal_diffuse": textures[7],
    "blue_metal_specular": textures[8],
    "blue_metal_emission": textures[9],
    "atlas_debug_diffuse": textures[10],
    "atlas_debug_emission": textures[11],
    "atlas_debug_specular": textures[12],
    "whoa_diffuse": textures[13],
    "whoa_specular": textures[14],
    "whoa_emission": textures[15],
    "font_atlas": textures[16],
    "button_atlas": textures[17],
    "ship_a_diffuse": textures[18],
    "ship_a_specular": textures[19],
    "ship_a_emission": textures[20],
    "skybox_nebula": textures[21],
}


debug_ship_orders = [
        ['stuck']
]

spaceship_parameters = {
    'number_of_sides': 8,
    'number_of_segments': 5,
    'transform_x': 1.0,
    'transform_z': 2.0,
    'scale': 3.3,
    'diffuse': texture_dictionary['penguin_diffuse'],
    'specular': texture_dictionary['penguin_specular'],
    'emission': texture_dictionary['penguin_emission'],
    'position': [0.0, -50.0, 0.0],
    'length_of_segment': 10,
}


def update_spaceship_parameters(key, value):
    spaceship_parameters[key] = value


def increase_light_brightness():
    ambient_current = my_plc.ambient[0]
    ambient_current = min(ambient_current + 0.1, 1.0)
    my_plc.ambient = [ambient_current] * 3

def decrease_light_brightness():
    ambient_current = my_plc.ambient[0]
    ambient_current = max(ambient_current - 0.1, 0.0)
    my_plc.ambient = [ambient_current] * 3

def increase_specular_brightness():
    specular_current = my_plc.specular[0]
    specular_current = min(specular_current + 0.1, 1.0)
    my_plc.specular = [specular_current] * 3

def decrease_specular_brightness():
    specular_current = my_plc.specular[0]
    specular_current = max(specular_current - 0.1, 0.0)
    my_plc.specular = [specular_current] * 3

def increase_diffuse_brightness():
    diffuse_current = my_plc.diffuse[0]
    diffuse_current = min(diffuse_current + 0.1, 1.0)
    my_plc.diffuse = [diffuse_current] * 3

def decrease_diffuse_brightness():
    diffuse_current = my_plc.diffuse[0]
    diffuse_current = max(diffuse_current - 0.1, 0.0)
    my_plc.diffuse = [diffuse_current] * 3

def increase_ship_sides(display):
    MAX_SIDES = 10
    sides = spaceship_parameters['number_of_sides']
    if sides == MAX_SIDES:
        return
    sides += 1
    update_spaceship_parameters(key='number_of_sides', value=sides)
    generate_new_ship()

    display.update_text(text=str(int(display.text_box.text) + 1))

def decrease_ship_sides(display):
    MIN_SIDES = 3
    sides = spaceship_parameters['number_of_sides']
    if sides == MIN_SIDES:
        return
    sides -= 1
    update_spaceship_parameters(key='number_of_sides', value=sides)
    generate_new_ship()
    display.update_text(text=str(int(display.text_box.text) - 1))


def increase_ship_segments(display=None):
    MAX_SEGMENTS = 20
    segments = spaceship_parameters['number_of_segments']
    if segments == MAX_SEGMENTS:
        return
    segments += 1
    update_spaceship_parameters(key='number_of_segments', value=segments)
    generate_new_ship()
    display.update_text(text=str(int(display.text_box.text) + 1))

def decrease_ship_segments(display=None):
    MIN_SEGMENTS = 1
    segments = spaceship_parameters['number_of_segments']
    if segments == MIN_SEGMENTS:
        return
    segments -= 1
    update_spaceship_parameters(key='number_of_segments', value=segments)
    generate_new_ship()
    display.update_text(text=str(int(display.text_box.text) - 1))

def increase_ship_segment_length(display=None):
    MAX_LENGTH = 60.0
    length = spaceship_parameters['length_of_segment']
    if length >= MAX_LENGTH:
        return
    delta= 0.5
    length += delta
    update_spaceship_parameters(key='length_of_segment', value=length)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) + delta, 2)))

def decrease_ship_segment_length(display=None):
    MIN_LENGTH = 4.5
    length = spaceship_parameters['length_of_segment']
    print('length=', length)
    if length <= MIN_LENGTH:
        return
    delta = 0.5
    length -= delta
    update_spaceship_parameters(key='length_of_segment', value=length)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) - delta, 2)))

def increase_ship_x_scaling(display=None):
    MAX_SCALE = 10.0
    x_scale = spaceship_parameters['transform_x']
    if x_scale >= MAX_SCALE:
        return
    delta = .1
    x_scale += delta
    update_spaceship_parameters(key='transform_x', value=x_scale)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) + delta, 2)))

def decrease_ship_x_scaling(display=None):
    MIN_SCALE = 0.1
    x_scale = spaceship_parameters['transform_x']
    if x_scale <= MIN_SCALE:
        return
    delta = .1
    x_scale -= delta
    update_spaceship_parameters(key='transform_x', value=x_scale)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) - delta, 2)))

def increase_ship_z_scaling(display=None):
    MAX_SCALE = 10.0
    z_scale = spaceship_parameters['transform_z']
    if z_scale >= MAX_SCALE:
        return
    delta = .1
    z_scale += delta
    update_spaceship_parameters(key='transform_z', value=z_scale)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) + delta, 2)))

def decrease_ship_z_scaling(display=None):
    MIN_SCALE = 0.1
    z_scale = spaceship_parameters['transform_z']
    if z_scale <= MIN_SCALE:
        return
    delta = .1
    z_scale -= delta
    update_spaceship_parameters(key='transform_z', value=z_scale)
    generate_new_ship()
    display.update_text(text=str(round(float(display.text_box.text) - delta, 2)))


ships = []
num_ships = 1
for ship in range(num_ships):
    spaceship = Spaceship3x3(
        shader=shader,
        diffuse=spaceship_parameters['diffuse'],
        specular=spaceship_parameters['specular'],
        emission=spaceship_parameters['emission'],
        dimensions=[5.0, 5.0],
        position=spaceship_parameters['position'],
        rotation_magnitude=[0.0, 0.0, -pi*0.5],
        number_of_sides=spaceship_parameters['number_of_sides'],
        number_of_segments=spaceship_parameters['number_of_segments'],
        transform_x=spaceship_parameters['transform_x'],
        transform_z=spaceship_parameters['transform_z'],
        length_of_segment=spaceship_parameters['length_of_segment'],
        radius=3.0,
        scale=spaceship_parameters['scale'],
        seed=seed
    )
    ships.append(spaceship)


my_fps = FPSCounter.FPSCounter(frame_interval=300.0, mute=True)


meshes = []
meshes += ships


def increase_seed():
    global seed
    seed += 1

def decrease_seed():
    global seed
    seed -= 1


def generate_next_ship(display=None):
    global seed
    increase_seed()
    generate_new_ship()
    if display:
        display.update_text(text='seed: ' + str(seed))


def generate_previous_ship(display=None):
    global seed
    decrease_seed()
    generate_new_ship()
    if display:
        display.update_text(text='seed: ' + str(seed))


def generate_new_ship():
    global seed
    global meshes
    spaceship_next = Spaceship3x3(
        shader=shader,
        diffuse=spaceship_parameters['diffuse'],
        specular=spaceship_parameters['specular'],
        emission=spaceship_parameters['emission'],
        dimensions=[5.0, 5.0],
        position=ships[0].position,
        rotation_magnitude=ships[0].rotation_magnitude,
        rotation_axis=glm.vec3((0.0, 0.0, 1.0)),
        number_of_sides=spaceship_parameters['number_of_sides'],
        number_of_segments=spaceship_parameters['number_of_segments'],
        transform_x=spaceship_parameters['transform_x'],
        transform_z=spaceship_parameters['transform_z'],
        length_of_segment=spaceship_parameters['length_of_segment'],
        radius=3.0,
        scale=spaceship_parameters['scale'],
        seed=seed
    )
    meshes[0].clean_up()
    del meshes[0]
    gc.collect()
    meshes.append(spaceship_next)


"""GUI CREATION"""
gui = GUI(screen_size=(WIDTH, HEIGHT))
gui.add_text_element(
    shader=None,
    position=(0.75, 0.73),
    scale=(0.20, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='button',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Texture',
)
gui.add_text_button(
    shader=None,
    position=(0.95, 0.73),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=cycle_ship_texture,
    context_id='button',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)
gui.add_text_button(
    shader=None,
    position=(0.5, 0.73),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=cycle_ship_texture,
    forward=False,
    context_id='button',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

gui.add_text_button(
    shader=None,
    position=(0.88, 0.95),
    scale=(0.12, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=gui.toggle_context_status,
    context_id='context_on',
    context_status=True,
    click_function_context_id='button',
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Options',
)

gui.add_text_element(
    shader=None,
    position=(-0.25, 0.83),
    scale=(0.08, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text=str(spaceship_parameters['number_of_sides']),
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(-0.38, 0.83),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_ship_sides,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)
gui.add_text_button(
    shader=None,
    position=(-0.48, 0.83),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_ship_sides,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)
gui.add_text_element(
    shader=None,
    # position=(-0.5, -0.0),
    position=(-0.78, 0.83), #-.28, +0.83
    scale=(0.25, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Sides',
)

gui.add_text_element(
    shader=None,
    position=(-0.25, 0.73),
    scale=(0.08, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text=str(spaceship_parameters['number_of_segments']),
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(-0.48, .73),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_ship_segments,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)
gui.add_text_button(
    shader=None,
    position=(-0.38, 0.73),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_ship_segments,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)
gui.add_text_element(
    shader=None,
    position=(-0.78, 0.73),
    scale=(0.25, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Segments',
)


gui.add_text_element(
    shader=None,
    position=(-0.25, 0.63),
    scale=(0.08, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='10.0',
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_element(
    shader=None,
    position=(-0.78,  0.63),
    scale=(0.25, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Length Scale',
)
gui.add_text_button(
    shader=None,
    position=(-0.38,  0.63),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_ship_segment_length,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)
gui.add_text_button(
    shader=None,
    position=(-0.48,  0.63),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_ship_segment_length,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

gui.add_text_element(
    shader=None,
    position=(-0.78, 0.53),
    scale=(0.25, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Height Scale',
)
gui.add_text_element(
    shader=None,
    position=(-0.25, 0.53),
    scale=(0.08, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='1.0',
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(-0.48, 0.53),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_ship_x_scaling,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

gui.add_text_button(
    shader=None,
    position=(-0.38, 0.53),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_ship_x_scaling,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)



gui.add_text_element(
    shader=None,
    position=(-0.78, 0.42999999999999994),
    scale=(0.25, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Width Scale',
)
gui.add_text_element(
    shader=None,
    position=(-0.25, 0.42999999999999994),
    scale=(0.08, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='ship_settings',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='2.0',
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(-0.48, 0.42999999999999994),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_ship_z_scaling,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

gui.add_text_button(
    shader=None,
    position=(-0.38, 0.42999999999999994),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_ship_z_scaling,
    display=displayer,
    context_id='ship_settings',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)

gui.add_text_element(
    shader=None,
    position=(-0.90, 0.95),
    scale=(0.15, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='fps',
    context_status=True,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='100',
)
#todo refactor this.  Need way to access text/element we want to update
display_fps = gui.elements[-1]


gui.add_text_button(
    shader=None,
    position=(-0.55, 0.95),
    scale=(0.22, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=gui.toggle_context_status,
    context_id='context_on',
    context_status=True,
    click_function_context_id='ship_settings',
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Ship Settings',
)

gui.add_text_element(
    shader=None,
    position=(0.75, 0.83),
    scale=(0.20, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='button',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Seed: ' + str(seed),
)
#todo refactor this.  Need way to access text/element we want to update
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(0.95, 0.83),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=generate_next_ship,
    display=displayer,
    context_id='button',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)
gui.add_text_button(
    shader=None,
    position=(0.50, 0.83),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=generate_previous_ship,
    display=displayer,
    context_id='button',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

gui.add_text_button(
    shader=None,
    position=(-0.185, 0.95),
    scale=(0.15, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=gui.toggle_context_status,
    context_id='context_on',
    context_status=True,
    click_function_context_id='light',
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Light',
)
gui.add_text_element(
    shader=None,
    position=(0.75, 0.52),
    scale=(0.20, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='light',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Ambient',
)
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(0.95, 0.52),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_light_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)

gui.add_text_button(
    shader=None,
    position=(0.50, 0.52),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_light_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

#Specular
gui.add_text_element(
    shader=None,
    position=(0.75, 0.42),
    scale=(0.20, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='light',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Specular',
)
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(0.95, 0.42),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_specular_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)

gui.add_text_button(
    shader=None,
    position=(0.50, 0.42),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_specular_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)

#Diffuse
gui.add_text_element(
    shader=None,
    position=(0.75, 0.32),
    scale=(0.20, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=2,
    context_id='light',
    context_status=False,
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Diffuse',
)
displayer = gui.elements[-1]
gui.add_text_button(
    shader=None,
    position=(0.95, 0.32),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=increase_diffuse_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='+',
)

gui.add_text_button(
    shader=None,
    position=(0.50, 0.32),
    scale=(0.05, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=decrease_diffuse_brightness,
    context_id='light',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='-',
)


def export_ship():
    """
    Open Window to export the ship model as a wavefront obj file with name of choice
    """
    filename = filedialog.asksaveasfilename()
    meshes[0].export_as_obj(filename=filename)


gui.add_text_button(
    shader=None,
    position=(0.11, 0.95),
    scale=(0.15, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=export_ship,
    context_id='context_on',
    context_status=True,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Export',
)


cubemaps = {
    2: [
        "Textures/Skyboxes/mountain_lake/right.jpg",
        "Textures/Skyboxes/mountain_lake/left.jpg",
        "Textures/Skyboxes/mountain_lake/top.jpg",
        "Textures/Skyboxes/mountain_lake/bottom.jpg",
        "Textures/Skyboxes/mountain_lake/front.jpg",
        "Textures/Skyboxes/mountain_lake/back.jpg",
    ],
    0: [
        "Textures/Skyboxes/galaxy/galaxy+X.png",
        "Textures/Skyboxes/galaxy/galaxy-X.png",
        "Textures/Skyboxes/galaxy/galaxy+Y.png",
        "Textures/Skyboxes/galaxy/galaxy-Y.png",
        "Textures/Skyboxes/galaxy/galaxy+Z.png",
        "Textures/Skyboxes/galaxy/galaxy-Z.png",
    ],
    1: [
        "Textures/Skyboxes/nebula/skybox_left.png",
        "Textures/Skyboxes/nebula/skybox_right.png",
        "Textures/Skyboxes/nebula/skybox_up.png",
        "Textures/Skyboxes/nebula/skybox_down.png",
        "Textures/Skyboxes/nebula/skybox_front.png",
        "Textures/Skyboxes/nebula/skybox_back.png",
    ]
}

skybox_cube_map = CubeMapSkybox.Skybox(
    texture_paths=cubemaps[0],
    scale=8000,
)
skybox_index = 0


def change_skybox():
    global skybox_cube_map
    global skybox_index
    MAX_SKYBOX_INDEX = 2
    skybox_index += 1
    if skybox_index > MAX_SKYBOX_INDEX:
        skybox_index = 0
    skybox_cube_map = CubeMapSkybox.Skybox(
        texture_paths=cubemaps[skybox_index],
        scale=8000,
    )


gui.add_text_button(
    shader=None,
    position=(0.75, 0.63),
    scale=(0.3, 0.05),
    texture=texture_dictionary['button_atlas'],
    atlas_size=2,
    atlas_coordinate=(2, 1),
    click_function=change_skybox,
    context_id='button',
    context_status=False,
    color=(1.0, 1.0, 1.0, 1.0),
    font_texture=texture_dictionary['font_atlas'],
    font_size=0.35,
    font_color=(1.0, 1.0, 1.0, 1.0),
    text='Skybox',
)


#must call as final setup of GUI
gui.build_elements_list()

#decouple fps from camera movement with time delta
time_start = 0
time_end = 1/60
time_delta = 1.0

projection = pyrr.matrix44.create_perspective_projection_matrix(45, WIDTH / HEIGHT, 0.1, 20050)
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection)

if not DEV_BUILD:
    display_fps.update_text(text='v1.0')


def write_fbo_to_gif():
    global data, image
    data = glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE)
    image = Image.frombytes("RGB", (WIDTH, HEIGHT), data)
    image = ImageOps.flip(image)
    images.append(image)


while not glfw.window_should_close(window):
    glfw.poll_events()
    time_start = glfw.get_time()
    do_movement(speed=100 * (time_delta))
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    fps = my_fps.update()
    # if DEV_BUILD:
    #     display_fps.update_text(text=str(round(my_fps.get_fps())))


    view = active_camera.get_view_matrix()
    glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)

    for mesh in meshes:
        mesh.draw(view=view)

    #GUI hover events
    #todo: mouse hover is maybe expensive! Consider spatial partition
    if use_follow_cam:
        gui.button_update(position_mouse=glfw.get_cursor_pos(window), left_click=False, right_click=False)

    skybox_cube_map.draw(view=view, projection=projection)
    glUseProgram(shader)

    # pass cam position for specular light
    glUniform3fv(view_pos_loc, 1, list(active_camera.camera_pos))
    point_light_position_loc = glGetUniformLocation(shader, "point_light.position")
    glUniform1f(glGetUniformLocation(shader, "material.shininess"), 4.0)
    glUniform3fv(glGetUniformLocation(shader, "point_lights[0].position"), 1, debug_plcs[0].get_pos())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[0].diffuse"), 1, debug_plcs[0].get_diffuse())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[0].ambient"), 1, debug_plcs[0].get_ambient())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[0].specular"), 1, debug_plcs[0].get_specular())
    glUniform1f(glGetUniformLocation(shader, "point_lights[0].constant"), debug_plcs[0].get_constant())
    glUniform1f(glGetUniformLocation(shader, "point_lights[0].linear"), debug_plcs[0].get_linear())
    glUniform1f(glGetUniformLocation(shader, "point_lights[0].quadratic"), debug_plcs[0].get_quadratic())

    #second light for mesh viewing
    glUniform3fv(glGetUniformLocation(shader, "point_lights[1].position"), 1, debug_plcs[1].get_pos())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[1].diffuse"), 1, debug_plcs[1].get_diffuse())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[1].ambient"), 1, debug_plcs[1].get_ambient())
    glUniform3fv(glGetUniformLocation(shader, "point_lights[1].specular"), 1, debug_plcs[1].get_specular())
    glUniform1f(glGetUniformLocation(shader, "point_lights[1].constant"), debug_plcs[1].get_constant())
    glUniform1f(glGetUniformLocation(shader, "point_lights[1].linear"), debug_plcs[1].get_linear())
    glUniform1f(glGetUniformLocation(shader, "point_lights[1].quadratic"), debug_plcs[1].get_quadratic())

    # spotlight
    glUniform3fv(glGetUniformLocation(shader, "spot_light.position"), 1, list(active_camera.camera_pos))
    glUniform3fv(glGetUniformLocation(shader, "spot_light.direction"), 1, list(active_camera.camera_front))
    glUniform3fv(glGetUniformLocation(shader, "spot_light.diffuse"), 1, [0.0]*3)
    glUniform3fv(glGetUniformLocation(shader, "spot_light.ambient"), 1, [0.0]*3)
    glUniform3fv(glGetUniformLocation(shader, "spot_light.specular"), 1, [0.0]*3)
    glUniform1f(glGetUniformLocation(shader, "spot_light.cut_off"), glm.cos(glm.radians(12.5)))
    glUniform1f(glGetUniformLocation(shader, "spot_light.outer_cut_off"), glm.cos(glm.radians(45.0)))
    glUniform1f(glGetUniformLocation(shader, "spot_light.constant"), 1.0)
    glUniform1f(glGetUniformLocation(shader, "spot_light.linear"), 0.00003)
    glUniform1f(glGetUniformLocation(shader, "spot_light.quadratic"), 0.00007)

    if use_follow_cam:
        gui.draw()
        glUseProgram(shader)

    if write_to_gif:
        write_fbo_to_gif()

    glfw.swap_buffers(window)
    time_end = glfw.get_time()
    time_delta = time_end - time_start

if wrote_to_gif:
    name = f'spaceship_generator.gif'
    print(f'saving gif as {name}')
    images[0].save(
            name,
            save_all=True,
            append_images=images[1:],
            optimize=False,
            duration=20,
            loop=0
        )
glfw.terminate()
