// <ZZ> Functions to handle mouse/keyboard/dll input...

#include "SDL_syswm.h"

#define MAX_MOUSE_BUTTON 4
#define MAX_CAMERA_Y (49152-3000)
#define MIN_CAMERA_Y (16384+3000)
#define MAX_KEY SDLK_LAST
float mouse_x = 200.0f;                         // Left/Right cursor position, 0-399.99
float mouse_y = 150.0f;                         // Up/Down cursor position, 0-299.99
float mouse_last_x = 200.0f;                    // Last Left/Right cursor position, 0-399.99
float mouse_last_y = 1500.0f;                   // Last Up/Down cursor position, 0-299.99
unsigned char mouse_down[MAX_MOUSE_BUTTON];     // Current button states for the mouse
unsigned char mouse_pressed[MAX_MOUSE_BUTTON];
unsigned char key_pressed[MAX_KEY];
unsigned char* key_down = NULL;
int last_key = 0;



//------------------------------------------------------------------------------------
void input_rotate_z_cheat(float angle)
{
    unsigned short object, i;
    float sine, cosine;

    angle = angle*2*PI/360.0f;
    sine = (float) sin(angle);
    cosine = (float) cos(angle);
    repeat(object, num_object)
    {
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][X] = object_vertex_data[object][i][OX] * cosine + object_vertex_data[object][i][OY] * sine;
            object_vertex_data[object][i][Y] = object_vertex_data[object][i][OY] * cosine - object_vertex_data[object][i][OX] * sine;
            object_vertex_data[object][i][OX] = object_vertex_data[object][i][X];
            object_vertex_data[object][i][OY] = object_vertex_data[object][i][Y];
        }
        object_determine_bounds(object);
    }
}

//------------------------------------------------------------------------------------
void input_rotate_xy_cheat(float angle)
{
    unsigned short object, i;
    float sine, cosine;
    float ox, oy, oz;
    float x, y, z;
    float ny, nz;

    angle = angle*2*PI/360.0f;
    sine = (float) sin(angle);
    cosine = (float) cos(angle);
    repeat(object, num_object)
    {
        repeat(i, object_num_vertex[object])
        {
            ox = object_vertex_data[object][i][OX];
            oy = object_vertex_data[object][i][OY];
            oz = object_vertex_data[object][i][OZ];

            x = ox*camera_side_xyz[X] + oy*camera_side_xyz[Y] + oz*camera_side_xyz[Z];
            y = ox*camera_fore_xyz[X] + oy*camera_fore_xyz[Y] + oz*camera_fore_xyz[Z];
            z = ox*camera_up_xyz[X] + oy*camera_up_xyz[Y] + oz*camera_up_xyz[Z];

            ny = y*cosine + z*sine;
            nz = z*cosine - y*sine;

            ny = ny-y;
            nz = nz-z;

            object_vertex_data[object][i][OX] += ny*camera_fore_xyz[X] + nz*camera_up_xyz[X];
            object_vertex_data[object][i][OY] += ny*camera_fore_xyz[Y] + nz*camera_up_xyz[Y];
            object_vertex_data[object][i][OZ] += ny*camera_fore_xyz[Z] + nz*camera_up_xyz[Z];
            object_vertex_data[object][i][X] = object_vertex_data[object][i][OX];
            object_vertex_data[object][i][Y] = object_vertex_data[object][i][OY];
            object_vertex_data[object][i][Z] = object_vertex_data[object][i][OZ];
        }
        object_determine_bounds(object);
    }
}

//------------------------------------------------------------------------------------
void input_move_cheat(float x, float y, float z)
{
    unsigned short object, i;
    repeat(object, num_object)
    {
        repeat(i, object_num_vertex[object])
        {
            object_vertex_data[object][i][X] += x;
            object_vertex_data[object][i][OX] += x;
            object_vertex_data[object][i][Y] += y;
            object_vertex_data[object][i][OY] += y;
            object_vertex_data[object][i][Z] += z;
            object_vertex_data[object][i][OZ] += z;
        }
        object_determine_bounds(object);
    }
}

//------------------------------------------------------------------------------------
void input_show_help(void)
{
    // <ZZ> This function pops up the help html file...
    SDL_SysWMinfo info;
    SDL_VERSION(&info.version);
    if(SDL_GetWMInfo(&info))
    {
        ShellExecute(NULL, TEXT("open") , TEXT("FILES\\HELP.HTML"), NULL, NULL, SW_SHOWNORMAL);
        ShowWindow(info.window,SW_MINIMIZE);
    }
}

//-----------------------------------------------------------------------------------------------
void input_read(void)
{
    // <ZZ> This function figgers out where the mouse is...
    SDL_Event event;
    unsigned char button_state, button_down;
    int x, y;
    unsigned short i, key;
    float speed;
    float fx, fy;
    int object;
    int animation;


    // Ask SDL how the mouse is doing...
    button_state = SDL_GetMouseState(&x, &y);
    mouse_last_x = mouse_x;
    mouse_last_y = mouse_y;
    mouse_x = x * 400.0f / screen_x;
    mouse_y = y * 300.0f / screen_y;
    repeat(i, MAX_MOUSE_BUTTON)
    {
        button_down = (button_state >> i) & 1;
        mouse_pressed[i] = mouse_down[i];
        mouse_down[i] = button_down;
        if(mouse_pressed[i] == FALSE && mouse_down[i])
        {
            mouse_pressed[i] = TRUE;
        }
        else
        {
            mouse_pressed[i] = FALSE;
        }
    }



    // Ask SDL whether each key is up or down...
    last_key = 0;
    repeat(i, MAX_KEY)
    {
        key_pressed[i] = FALSE;
    }
    key_down = SDL_GetKeyState(NULL);



    // Check for any quit events...
    while(SDL_PollEvent(&event))
    {
        if(event.type == SDL_KEYDOWN)
        {
            key = (unsigned short) event.key.keysym.sym;
            if(key < MAX_KEY)
            {
                key_pressed[key] = TRUE;
                last_key = key;
            }
        }
        if(event.type == SDL_QUIT)
        {
            main_loop_active = FALSE;
        }
    }



    // Check to see if any of our special keys were pressed...
    if(!active_menu_textbox)
    {
        // Flag setting keys...
        if(key_pressed[SDLK_h])
        {
            object_draw_hit = !object_draw_hit;
        }
        if(key_pressed[SDLK_n])
        {
            object_draw_normal = !object_draw_normal;
        }
        if(key_pressed[SDLK_s])
        {
            object_draw_spring = !object_draw_spring;
        }
        if(key_pressed[SDLK_d])
        {
            global_dll_active = !global_dll_active;
        }
        if(key_pressed[SDLK_c])
        {
            global_scope_cam = !global_scope_cam;
        }
        if(key_pressed[SDLK_w])
        {
            object_draw_wireframe = !object_draw_wireframe;
        }
        if(key_pressed[SDLK_t])
        {
            global_draw_dll_text = !global_draw_dll_text;
        }
        if(key_pressed[SDLK_m])
        {
            menu_open = !menu_open;
        }




        if(!global_scope_cam)
        {
            if(key_down[SDLK_LCTRL] || key_down[SDLK_RCTRL])
            {
                fx = mouse_x - mouse_last_x;
                fy = -(mouse_y - mouse_last_y);
                if(key_down[SDLK_LSHIFT] || key_down[SDLK_RSHIFT])
                {
                    // Cheat object rotation keys
                    input_rotate_z_cheat(fx);
                    input_rotate_xy_cheat(fy);
                }
                else
                {
                    // Cheat object movement keys
                    input_move_cheat(fx*camera_side_xyz[X] + fy*camera_up_xyz[X], fx*camera_side_xyz[Y] + fy*camera_up_xyz[Y], fx*camera_side_xyz[Z] + fy*camera_up_xyz[Z]);
                }
            }
        }


        if(key_down[SDLK_LSHIFT] || key_down[SDLK_RSHIFT])
        {
            // Cheat movement keys
            if(key_down[SDLK_UP] || key_down[SDLK_DOWN])
            {
                speed = 1.0f;
                if(key_down[SDLK_DOWN]) { speed = -speed;  }
                i = scope_control_list[0];
                scope_point_data[i][VX]+=camera_up_xyz[X]*speed;
                scope_point_data[i][VY]+=camera_up_xyz[Y]*speed;
                scope_point_data[i][VZ]+=camera_up_xyz[Z]*speed;
            }
            if(key_down[SDLK_LEFT] || key_down[SDLK_RIGHT])
            {
                speed = 1.0f;
                if(key_down[SDLK_LEFT]) { speed = -speed;  }
                i = scope_control_list[0];
                scope_point_data[i][VX]+=camera_side_xyz[X]*speed;
                scope_point_data[i][VY]+=camera_side_xyz[Y]*speed;
                scope_point_data[i][VZ]+=camera_side_xyz[Z]*speed;
            }
        }
        else if(!global_scope_cam)
        {
            // Camera control keys...
            if(key_down[SDLK_UP] || key_down[SDLK_DOWN])
            {
                speed = CAMERA_WALK_RATE;
                if(key_down[SDLK_UP]) { speed = -speed;  }
                target_xyz[VZ]+=speed;
            }
            if(key_down[SDLK_LEFT] || key_down[SDLK_RIGHT])
            {
                speed = CAMERA_WALK_RATE;
                if(key_down[SDLK_LEFT]) { speed = -speed;  }
                target_xyz[VX]+=speed;
            }
        }




        // Scope control keys...
        if(key_down[SDLK_i] || key_down[SDLK_o])
        {
            if(num_scope_control > 1)
            {
                speed = 3.5f;
                if(key_down[SDLK_o]) { speed = -speed;  }
                global_dll_zin += speed;
            }
        }
        if(key_down[SDLK_j] || key_down[SDLK_l])
        {
            speed = 2.00f;
            if(key_down[SDLK_j]) { speed = -speed;  }
            global_dll_headangle+=speed;
        }
        if(key_pressed[SDLK_k])
        {
            // Zero the head angle...
            global_dll_headangle = 0.0f;
        }
        if(key_down[SDLK_COMMA])
        {
            global_dll_zangle+=5.0f;
        }
        if(key_down[SDLK_PERIOD])
        {
            global_dll_zangle-=5.0f;
        }
        if(key_pressed[SDLK_r])
        {
            // Reset the endoscope...
            scope_initialize(global_scope_control_points, global_scope_length, global_scope_radius, 0.0f, 0.0f, global_scope_length/(global_scope_control_points+1));
        }
    }
    if(key_down[SDLK_ESCAPE])
    {
        main_loop_active = FALSE;
    }
    if(key_pressed[SDLK_F1])
    {
        input_show_help();
    }


    // Read our DLL input...
    if(global_dll_active)
    {
        global_dll_zin = dll_get_zin();
        global_dll_zangle = dll_get_zangle();
        global_dll_headangle = dll_get_headangle();
        global_dll_degrees = dll_get_degrees();
        global_dll_topdown = dll_get_topdown();
        if(global_dll_degrees < 0.1f)
        {
            // Radian mode -- convert to degrees to use internally....
            global_dll_zangle = global_dll_zangle * 180.0f / PI;
            global_dll_headangle = global_dll_headangle * 180.0f / PI;
        }

        // Get whiteout values...
        global_whiteout_min_seconds = dll_get_whiteout_min_seconds();
        global_whiteout_max_seconds = dll_get_whiteout_max_seconds();
        global_percent_chance_of_permanent_whiteout = dll_get_whiteout_percent_chance();

        // Get whether or not to draw tally...
        global_draw_tally_text = (unsigned char) dll_get_tally_shown();

        // Get whether or not to draw tally...
        animation = (int) dll_get_animation_to_start();
        if(animation)
        {
            // DLL is requesting that we begin a new animation sequence...
            global_animation = animation;
            global_animation_timer = 0.0f;
        }

        // Get mesh settings for each object...
        repeat(object, num_object)
        {
            object_dampen[object] = dll_get_object_dampen(object);
            object_toothpick_force[object] = dll_get_object_toothpick(object);
            object_spring_force[object] = dll_get_object_spring(object);
            object_return_force[object] = dll_get_object_return(object);
            object_weight[object] = dll_get_object_weight(object);
        }
    }
}

//-------------------------------------------------------------------------------------------
void input_camera_controls(void)
{
    // <ZZ> This function lets the user move the camera around...
    signed short off_x, off_y;

    if(SDL_GetAppState() & SDL_APPACTIVE)
    {
        if(menu_active==FALSE && (mouse_down[0] || mouse_down[1] || mouse_down[2]))
        {
            // Motion...
            off_x = (signed short) (mouse_x - mouse_last_x);
            off_y = (signed short) (mouse_y - mouse_last_y);


            // Keep the mouse in one spot...
            SDL_WarpMouse((Uint16) (mouse_last_x * screen_x / 400.0f), (Uint16) (mouse_last_y * screen_y / 300.0f));
            mouse_x = mouse_last_x;
            mouse_y = mouse_last_y;


            if(global_scope_cam)
            {
                global_dll_headangle -= off_y*SCOPE_CAMERA_ROTATION_RATE;
                global_dll_zangle -= off_x*SCOPE_CAMERA_ROTATION_RATE;
            }
            else
            {
                if(mouse_down[0])
                {
                    // Rotational control...
                    camera_rotation_xy[X] -= (signed int) (off_x * CAMERA_ROTATION_RATE);
                    camera_rotation_xy[Y] -= (signed int) (off_y * CAMERA_ROTATION_RATE);
                    if(camera_rotation_xy[Y] < MIN_CAMERA_Y)
                    {
                        camera_rotation_xy[Y] = MIN_CAMERA_Y;
                    }
                    if(camera_rotation_xy[Y] > MAX_CAMERA_Y)
                    {
                        camera_rotation_xy[Y] = MAX_CAMERA_Y;
                    }
                }
                else
                {
                    // Side/Forward control...
                    camera_distance += off_y*CAMERA_ZOOM_RATE;


//                    target_xyz[VX]+=camera_side_xyz[X]*off_x*CAMERA_WALK_RATE;
//                    target_xyz[VY]+=camera_side_xyz[Y]*off_x*CAMERA_WALK_RATE;
//                    target_xyz[VZ]+=camera_side_xyz[Z]*off_x*CAMERA_WALK_RATE;

//                    target_xyz[VX]-=camera_fore_xyz[X]*off_y*CAMERA_WALK_RATE;
//                    target_xyz[VY]-=camera_fore_xyz[Y]*off_y*CAMERA_WALK_RATE;
//                    target_xyz[VZ]-=camera_fore_xyz[Z]*off_y*CAMERA_WALK_RATE;
                }
            }
        }
    }
}

//------------------------------------------------------------------------------------
void input_dll_cleanup(void)
{
    // <ZZ> Free up our library when it's no longer needed...
    log_message("Closing our DLL");
    FreeLibrary(dll_handle);
}

//------------------------------------------------------------------------------------
unsigned char input_dll_setup()
{
    // <ZZ> This function sets up the DLL that's used as a middle man between the
    //      main control input application and the simulation program
    log_message("Setting up the DLL to retrieve control inputs");

    dll_handle = LoadLibrary("EndoDLL.dll");
    if(dll_handle == NULL)
    {
        log_message("ERROR:  Couldn't load EndoDLL.dll");
        return FALSE;
    }
    atexit(input_dll_cleanup);


    // Find our first function...
    dll_setup_memory_mapped_file = (dll_setup_ptr) GetProcAddress(dll_handle, "setup_memory_mapped_file");
    if(dll_setup_memory_mapped_file == NULL)
    {
        log_message("ERROR:  Couldn't find setup_memory_mapped_file() function");
        return FALSE;
    }


    // Find our second function...
    dll_get_zin = (dll_func_ptr) GetProcAddress(dll_handle, "get_zin");
    if(dll_get_zin == NULL)
    {
        log_message("ERROR:  Couldn't find get_zin() function");
        return FALSE;
    }
    // Next...
    dll_get_zangle = (dll_func_ptr) GetProcAddress(dll_handle, "get_zangle");
    if(dll_get_zangle == NULL)
    {
        log_message("ERROR:  Couldn't find get_zangle() function");
        return FALSE;
    }
    // Next...
    dll_get_headangle = (dll_func_ptr) GetProcAddress(dll_handle, "get_headangle");
    if(dll_get_headangle == NULL)
    {
        log_message("ERROR:  Couldn't find get_headangle() function");
        return FALSE;
    }
    // Next...
    dll_get_degrees = (dll_func_ptr) GetProcAddress(dll_handle, "get_degrees");
    if(dll_get_degrees == NULL)
    {
        log_message("ERROR:  Couldn't find get_degrees() function");
        return FALSE;
    }
    // Next...
    dll_get_topdown = (dll_func_ptr) GetProcAddress(dll_handle, "get_topdown");
    if(dll_get_topdown == NULL)
    {
        log_message("ERROR:  Couldn't find get_topdown() function");
        return FALSE;
    }



    // Whiteout related functions...
    dll_get_whiteout_min_seconds = (dll_func_ptr) GetProcAddress(dll_handle, "get_whiteout_min_seconds");
    if(dll_get_whiteout_min_seconds == NULL)
    {
        log_message("ERROR:  Couldn't find get_whiteout_min_seconds() function");
        return FALSE;
    }

    dll_get_whiteout_max_seconds = (dll_func_ptr) GetProcAddress(dll_handle, "get_whiteout_max_seconds");
    if(dll_get_whiteout_max_seconds == NULL)
    {
        log_message("ERROR:  Couldn't find get_whiteout_max_seconds() function");
        return FALSE;
    }

    dll_get_whiteout_percent_chance = (dll_func_ptr) GetProcAddress(dll_handle, "get_whiteout_percent_chance");
    if(dll_get_whiteout_percent_chance == NULL)
    {
        log_message("ERROR:  Couldn't find get_whiteout_percent_chance() function");
        return FALSE;
    }


    // Tally visibility...
    dll_get_tally_shown = (dll_func_ptr) GetProcAddress(dll_handle, "get_tally_shown");
    if(dll_get_tally_shown == NULL)
    {
        log_message("ERROR:  Couldn't find get_tally_shown() function");
        return FALSE;
    }

    // Animation...
    dll_get_animation_to_start = (dll_func_ptr) GetProcAddress(dll_handle, "get_animation_to_start");
    if(dll_get_animation_to_start == NULL)
    {
        log_message("ERROR:  Couldn't find get_animation_to_start() function");
        return FALSE;
    }





    // Object mesh related functions...
    dll_get_object_dampen = (dll_object_ptr) GetProcAddress(dll_handle, "get_object_dampen");
    if(dll_get_object_dampen == NULL)
    {
        log_message("ERROR:  Couldn't find get_object_dampen() function");
        return FALSE;
    }

    dll_get_object_return = (dll_object_ptr) GetProcAddress(dll_handle, "get_object_return");
    if(dll_get_object_return == NULL)
    {
        log_message("ERROR:  Couldn't find get_object_return() function");
        return FALSE;
    }

    dll_get_object_spring = (dll_object_ptr) GetProcAddress(dll_handle, "get_object_spring");
    if(dll_get_object_spring == NULL)
    {
        log_message("ERROR:  Couldn't find get_object_spring() function");
        return FALSE;
    }

    dll_get_object_toothpick = (dll_object_ptr) GetProcAddress(dll_handle, "get_object_toothpick");
    if(dll_get_object_toothpick == NULL)
    {
        log_message("ERROR:  Couldn't find get_object_toothpick() function");
        return FALSE;
    }

    dll_get_object_weight = (dll_object_ptr) GetProcAddress(dll_handle, "get_object_weight");
    if(dll_get_object_weight == NULL)
    {
        log_message("ERROR:  Couldn't find get_object_weight() function");
        return FALSE;
    }
 


    log_message("Trying to call setup_memory_mapped_file()");
    dll_setup_memory_mapped_file();
    log_message("Finished with call to DLL");

    return TRUE;
}

//------------------------------------------------------------------------------------
void input_limit(void)
{
    // <ZZ> This function limits our dll inputs so things don't get wacky...


    // Limit maximum insertion...
    if(global_dll_zin > (-global_z_initial-global_scope_segment_length-1.0f))
    {
        global_dll_zin = -global_z_initial-global_scope_segment_length-1.0f;
    }


    // Limit maximum head angle...
    if(global_dll_headangle > global_scope_max_tip_angle)
    {
        global_dll_headangle = global_scope_max_tip_angle;
    }
    if(global_dll_headangle < -global_scope_max_tip_angle)
    {
        global_dll_headangle = -global_scope_max_tip_angle;
    }
}

//-----------------------------------------------------------------------------------------------
