/*  Included Header Files
    ************************************************************************ */

#include "untitled.h"



/*  Function Prototypes
    ************************************************************************ */

LRESULT CALLBACK MainWndProc ( HWND, UINT, WPARAM, LPARAM );



/*  ************************************************************************
    * processes messages sent to the window                                *
    * [param] hWnd    handle to the window                                 *
    * [param] uMsg    specifies the message                                *
    * [param] wParam  additional message information                       *
    * [param] lParam  additional message information                       *
    * [returns] result of message processing; depends on the message       *
    ************************************************************************ */

LRESULT CALLBACK MainWndProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    switch (uMsg)
    {
        /* user clicked "X" */
        case WM_CLOSE:
            /* send WM_DESTROY message */
            if (DestroyWindow(hWnd) == 0)
                win_error("Failed to destroy the window.", __FILE__, __LINE__);
            break;
        case WM_DESTROY:
            /* send WM_QUIT message */
            PostQuitMessage(0);
            break;
        default:
            /* default window procedure handles all other messages */
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
            break;
    };

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}



/*  ***********************************************************************
    * entry point for the application                                     *
    * [param] hInstance       handle to the program's current instance    *
    * [param] hPrevInstance   handle to the program's previous instance   *
    * [param] lpCmdLine       pointer to the command line string          *
    * [param] nCmdShow        specifies how the window is to be shown     *
    * [returns] exit value in the message's wParam parameter              *
    *           0 if terminated before entering the message loop          *
    ***********************************************************************  */

int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
    /*  Variables
        ************************************************************************ */

    /* Windows */
    MSG        message;
    HWND       window;
    WNDCLASSEX window_class;
    HWND       player_input, output_display;
    HFONT      font;

    /* core */
    BOOL          game_running;
#ifdef WIN32
    LARGE_INTEGER frequency, current_time, last_time;
#else
    timeval       current_time, last_time;
#endif
    double        elapsed_time;
    clock_t       current_time_lrt, last_time_lrt;

    /* testing (temporary) */
    char          user_input[USER_INPUT_MAX_LENGTH];
    Command_Type  command;
    Creature *    player = NULL;
    Item *        armor = NULL;
    Item *        helm = NULL;
    Item *        boots = NULL;
    Room *        world = NULL;
    Room *        room = NULL;
    Spawn_Group * room_spawn = NULL;


    /*  Initialization
        ************************************************************************ */

    lpCmdLine = lpCmdLine;
    game_running = TRUE;

    initialize();

	/* set the seed for randomness at the beginning of the program, and only once! */
	srand((unsigned int) time(NULL));

    /* register the window class */
    if (hPrevInstance == NULL)
    {
        window_class.cbSize        = sizeof(WNDCLASSEX);
        window_class.style         = 0;
        window_class.lpfnWndProc   = MainWndProc;
        window_class.lpszClassName = "Dungeons of Ulgonoth";
        window_class.cbClsExtra    = 0;
        window_class.cbWndExtra    = 0;
        window_class.hInstance     = hInstance;
        window_class.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        window_class.hCursor       = LoadCursor(NULL, IDC_ARROW);
        window_class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        window_class.lpszMenuName  = NULL;
        window_class.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

        if (RegisterClassEx(&window_class) == 0)
        {
            win_error("Failed to register the window class.", __FILE__, __LINE__);
            return 0;
        };
    };

    /* create the window */
    window = CreateWindowEx(WS_EX_CLIENTEDGE,               /* extended window style */
                            TEXT("Dungeons of Ulgonoth"),   /* class name */
                            TEXT("Dungeons of Ulgonoth"),   /* window name */
                            WS_OVERLAPPEDWINDOW,            /* window style */
                            0,                              /* x coordinate (0 = left) */
                            0,                              /* y coordinate (0 = top) */
                            800,                            /* width */
                            400,                            /* height */
                            NULL,                           /* parent window */
                            NULL,                           /* menu */
                            hInstance,                      /* instance */
                            NULL);                          /* for MDI window */
    if (window == NULL)
        win_error("Failed to create the window.", __FILE__, __LINE__);

    /* specify how to show the window */
    (void) ShowWindow(window, nCmdShow);

    /* redraw the window */
    if (UpdateWindow(window) == 0)
        win_error("Failed to update the window.", __FILE__, __LINE__);

    /* create the edit box for user input */
    player_input = CreateWindow(TEXT("EDIT"), "", WS_CHILD | WS_BORDER | WS_VISIBLE, 10, 330, 765, 18, window, NULL, hInstance, NULL);
    if (player_input == NULL)
        win_error("Failed to create the player input edit box.", __FILE__, __LINE__);

    /* create the display box for output */
    output_display = CreateWindow(TEXT("EDIT"), "", WS_CHILD | ES_READONLY | ES_MULTILINE | WS_VSCROLL | ES_AUTOVSCROLL | WS_VISIBLE, 10, 10, 770, 310, window, NULL, hInstance, NULL);
    if (output_display == NULL)
        win_error("Failed to create the output static box.", __FILE__, __LINE__);

    /* set up the display font */
    font = CreateFont(14, 7, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, TEXT("Courier New"));
    if (font == NULL)
        win_error("Failed to create the font.", __FILE__, __LINE__);
    (void) SendMessage(output_display, WM_SETFONT, (WPARAM) font, TRUE);

    /* initialize the timer(s) */
#ifdef WIN32
    if (QueryPerformanceFrequency(&frequency) == 0)
        error("Failed to retrieve the frequency from the high resolution timer.  It may not be supported.", __FILE__, __LINE__);
    if (QueryPerformanceCounter(&current_time) == 0)
        error("Failed to retrieve a value from the high resolution timer.  It may not be supported.", __FILE__, __LINE__);
    last_time = current_time;
#else
    if (gettimeofday(&current_time, NULL) != 0)
        error("Failed to retrieve a value from the high resolution timer.  It may not be supported.", __FILE__, __LINE__);
    last_time = current_time;
#endif
    current_time_lrt = clock();
    last_time_lrt = current_time_lrt;

    /* make sure focus is on input box */
    if (SetFocus(player_input) == NULL)
        win_error("Failed to set focus to the edit box.", __FILE__, __LINE__);


    /*  Test
        ************************************************************************ */

    player = create_creature("Devin", 0);
    player->type = PLAYER;
    player->strength = 8;
    player->dexterity = 8;
    player->constitution = 8;
    player->max_endurance = (float) player->constitution * 10;
    player->endurance = player->max_endurance;
    player->scalp = 100.0;
    player->forehead = 80.0;
    player->left_ear = 100.0;
    player->right_ear = 100.0;
    player->left_eye = 100.0;
    player->right_eye = 100.0;
    player->nose = 100.0;
    player->lips = 75.0;
    player->left_cheek = 100.0;
    player->right_cheek = 100.0;
    player->chin = 100.0;
    player->jaw = 100.0;
    player->neck = 100.0;
    player->left_shoulder = 100.0;
    player->right_shoulder = 100.0;
    player->chest = 30.0;
    player->back = 100.0;
    player->butt = 100.0;
    player->abdomen = 100.0;
    player->groin = 100.0;
    player->left_bicep = 100.0;
    player->right_bicep = 100.0;
    player->left_forearm = 100.0;
    player->right_forearm = 100.0;
    player->left_wrist = 100.0;
    player->right_wrist = 100.0;
    player->left_elbow = 100.0;
    player->right_elbow = 100.0;
    player->left_dorsal = 100.0;
    player->right_dorsal = 85.0;
    player->left_palm = 100.0;
    player->right_palm = 100.0;
    player->left_fingers = 100.0;
    player->right_fingers = 95.0;
    player->left_thumb = 100.0;
    player->right_thumb = 100.0;
    player->left_thigh = 100.0;
    player->right_thigh = 100.0;
    player->left_calf = 100.0;
    player->right_calf = 100.0;
    player->left_knee = 100.0;
    player->right_knee = 100.0;
    player->left_shin = 100.0;
    player->right_shin = 100.0;
    player->left_ankle = 100.0;
    player->right_ankle = 100.0;
    player->left_heel = 100.0;
    player->right_heel = 100.0;
    player->left_midfoot = 70.0;
    player->right_midfoot = 100.0;
    player->left_toes = 100.0;
    player->right_toes = 100.0;
    player->vitality = calculate_vitality(player);

    player->unarmed = 100.0f;
    player->one_handed_slash = 25.6f;
    player->one_handed_blunt = 10.8f;
    player->one_handed_piercing = 14.2f;
    player->two_handed_slash = 15.0f;
    player->two_handed_blunt = 45.33f;
    player->two_handed_piercing = 11.0f;
    player->polearms = 23.1f;
    player->staves = 33.75f;
    player->throwing = 22.5f;
    player->bows = 10.5f;
    player->crossbows = 20.0f;
    player->bash = 0.0f;
    player->backstab = 0.0f;
    player->dual_wielding = 45.0f;
    player->double_attack = 50.0f;
    player->disarm = 75.0f;

    armor = create_armor("mail", "Dark-forged Plate Mail");  /* handle NULL return */
    armor->next = player->equipment;      /* equip it */
    player->equipment = armor;

    helm = create_armor("helmet", "really long-winded description of a helmet");  /* handle NULL return */
    helm->next = player->equipment;
    player->equipment = helm;

    boots = create_armor("boots", "a pair of moccassin boots");  /* handle NULL return */
    player->right_held = boots;

    /* handle NULL return */
    room = create_room("Whistling Wind Tavern, Alcove", "A large mahogany table sits in the center of the room.  Two dwarves are arguing about metals in the corner.  You notice a person shrouded in dark cloth, clearly not wanting to be seen.");
    room->players = player;
    room->next = world;
    world = room;
    player->location = room;

    /* TODO: change so create_room takes in the exits too */
    output(output_display, "[%s]\r\n", room->name);
    output(output_display, "%s", room->description);
    output(output_display, "\r\n", room->description);
    output(output_display, "Obvious exits: none\r\n\r\n");

    room_spawn = create_spawn_group("goblin", 3, 5.0, 60.0, 25.0);  /* name, max_spawns, time to spawn, time to live, spawn probability */
    room_spawn->next = NULL;
    room->spawns = room_spawn;


    /*  Main Loop
        ************************************************************************ */

    while (game_running == TRUE)
    {
        /* dispatch incoming sent messages, check the thread message queue, retrieve message */
        while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE) == TRUE)
        {
            switch (message.message)
            {
                case WM_QUIT:
                    game_running = FALSE;
                    break;
                case WM_KEYDOWN:
                    switch (message.wParam)
                    {
                        case VK_ESCAPE:
                            game_running = FALSE;
                            break;
                        case VK_RETURN:
                            /* user is trying to input something */
                            /* TODO: handle input that's too long */
                            if (GetWindowText(player_input, user_input, USER_INPUT_MAX_LENGTH) != 0)
                            {
                                output(output_display, ">");
                                output(output_display, user_input);
                                output(output_display, "\r\n");
                                command = process_input(user_input);
                                
                                switch (command)
                                {
                                    case COMMAND_QUIT:
                                        game_running = FALSE;
                                        break;
                                    case COMMAND_INVENTORY:
                                        command_inventory(output_display, player);
                                        break;
                                    case COMMAND_EQUIPMENT:
                                        command_equipment(output_display, player);
                                        break;
                                    case COMMAND_WEAR:
                                        command_wear(output_display, player, user_input);
                                        break;
                                    case COMMAND_REMOVE:
                                        command_remove(output_display, player, user_input);
                                        break;
                                    case COMMAND_ATTACK:
                                        command_attack(output_display, player, user_input);
                                        break;
                                    case COMMAND_SWAP:
                                        command_swap(output_display, player);
                                        break;
                                    case COMMAND_LOOK:
                                        command_look(output_display, player->location, player, user_input);
                                        break;
                                    case COMMAND_HEALTH:
                                        command_health(output_display, player);
                                        break;
                                    default:
                                        output(output_display, "What?");
                                        break;
                                };

                                output(output_display, "\r\n\r\n");

                                /* clear the input field */
                                (void) SetWindowText(player_input, "");
                            };
                            break;
                        default:
                            /* make sure focus is on input box */
                            if (GetFocus() != player_input)
                                if (SetFocus(player_input) == NULL)
                                    win_error("Failed to set focus to the edit box.", __FILE__, __LINE__);
                            break;
                    };
                    break;
                default:
                    break;
            };

            TranslateMessage(&message);
            DispatchMessage(&message);
        };

        /* get the elapsed time from the last game cycle */
        if (LOW_RESOLUTION_TIMER == FALSE)
        {
#ifdef WIN32
            if (QueryPerformanceCounter(&current_time) == 0)
                error("Failed to retrieve a value from the high resolution timer.  It may not be supported.", __FILE__, __LINE__);
            elapsed_time = (current_time.QuadPart - last_time.QuadPart) * 1000.0 / frequency.QuadPart;
            last_time = current_time;
#else
            if (gettimeofday(&current_time)) != 0)
                error("Failed to retrieve a value from the high resolution timer.  It may not be supported.", __FILE__, __LINE__);
            elapsed_time = ((current_time.tv_sec - last_time.tv_sec) * 1000.0)
                         + ((current_time.tv_usec - last_time.tv_usec) / 1000.0);
            last_time = current_time;
#endif
        }
        else
        {
            current_time_lrt = clock();
            if (current_time_lrt == -1)
                error("Failed to retrieve a value from the low resolution timer.", __FILE__, __LINE__);
            elapsed_time = ((double) current_time_lrt - last_time_lrt) / CLOCKS_PER_SEC * 1000;
            last_time_lrt = current_time_lrt;
        };

        /* perform A.I. logic */

        room = world;
        while (room != NULL)
        {
            Spawn_Group * spawn = room->spawns;
            Creature * head_of_list = room->creatures;

            while (spawn != NULL)
            {
                /* check to see if a creature should spawn */
                spawn->spawn_timer += elapsed_time / 1000;
                if (spawn->spawn_timer >= spawn->time_to_spawn)
                {
                    /* spawn probability */
                    if (spawn->spawn_probability > (rand() % 100) + 1)
                    {
                        /* make sure we're no going past the spawngroup limit or room limit */
                        if (spawn->current_num_spawns < spawn->max_spawns && room->number_of_spawns < room->spawn_limit)
                        {
                            Creature * a_creature;

                            /* Create the creature */
                            /* TODO: Implement a real method that loads from a database of the room spawns */
                            a_creature = create_creature("goblin", spawn->time_to_live);  
							(void) strcpy(a_creature->description, "an ugly goblin");
                            a_creature->location = room;

                            /* add creature to the end of the list */
                            if (room->creatures == NULL)
                            {
                                room->creatures = a_creature;
                                room->creatures->next = NULL;
                                head_of_list = room->creatures;
                            }
                            else
                            {
                                Creature * temp = room->creatures;
                                while (temp->next != NULL)
                                    temp = temp->next;
                                a_creature->next = NULL;
                                temp->next = a_creature;
                            };

                            room->number_of_spawns++;
                            spawn->current_num_spawns++;
                            spawn->spawn_timer = 0.0;

                            /* TODO: we need to look-up the creature by matching the name
                                     given from the spawn table (room->spawns->creature_name)
                                     to something in a database */

                            output(output_display, "%s wanders into the room.\r\n\r\n", a_creature->description);
                        };
                    }
                    else
                    {
                        spawn->spawn_timer *= 0.9;
                    }
                };

                spawn = spawn->next;
            };

            while (room->creatures != NULL)
            {
                room->creatures->time_alive += elapsed_time / 1000;

                /* check if the creature should despawn */
                if (room->creatures->time_alive >= room->creatures->time_to_live && room->creatures->time_to_live != 0)
                {
                    if (85 > (rand() % 100) + 1)
                    {
                        Creature * temp = room->creatures->next;

                        output(output_display, "%s lumbers away.\r\n\r\n", room->creatures->description);

                        if (room->creatures == head_of_list)
                        {
                            free(room->creatures);
                            head_of_list = temp;
                            room->creatures = temp;
                        }
                        else
                        {
                            free(room->creatures);
                            room->creatures = temp;
                        }

                    }
                    else
                    {
                        room->creatures->time_to_live *= 1.05;
                        room->creatures = room->creatures->next;
                    };
                }

                /* check if the creature should move */
                /* TODO */

                else
                {
                    room->creatures = room->creatures->next;
                };
            };

            room->creatures = head_of_list;
            room = room->next;
        };
    };

    /*  Free Memory
        ************************************************************************ */

    free(room_spawn);
    shut_down(player);

    while (world->next != NULL) /* TODO: update this with all the new shit we need to free */
    {
        Creature * temp = room->creatures;

        while (temp != NULL)
        {
            temp = room->creatures->next;
            free(room->creatures);
            room->creatures = temp;
        };

        room = world->next;
        free(world);
        world = room;
    };

    return message.wParam;  /* UINT_PTR */
}

void initialize ( void )
{
    /* TODO: open save data file, check for player, if not there, then proceed
             to new_game() */
}

void shut_down ( Creature * creature )
{
    Item * item = NULL;
    Item * contents = NULL;

    /* player data */
    if (creature->right_held != NULL)
    {
        if (creature->right_held->next != NULL)
            error("The player's right hand is holding a list of items.  This shouldn't be possible.", __FILE__, __LINE__);
        while (creature->right_held->contents != NULL)
        {
            contents = creature->right_held->contents->next;
            free(creature->right_held->contents);
            creature->right_held->contents = contents;
        };
    };
    if (creature->left_held != NULL)
    {
        if (creature->left_held->next != NULL)
            error("The player's left hand is holding a list of items.  This shouldn't be possible.", __FILE__, __LINE__);
        while (creature->left_held->contents != NULL)
        {
            contents = creature->left_held->contents->next;
            free(creature->left_held->contents);
            creature->left_held->contents = contents;
        };
    };
    while (creature->inventory != NULL)
    {
        while (creature->inventory->contents != NULL)
        {
            contents = creature->inventory->contents->next;
            free(creature->inventory->contents);
            creature->inventory->contents = contents;
        };
        item = creature->inventory->next;
        free(creature->inventory);
        creature->inventory = item;
    };
    while (creature->equipment != NULL)
    {
        while (creature->equipment->contents != NULL)
        {
            contents = creature->equipment->contents->next;
            free(creature->equipment->contents);
            creature->equipment->contents = contents;
        };
        item = creature->equipment->next;
        free(creature->equipment);
        creature->equipment = item;
    };
    if (creature != NULL)
        free(creature);

    fprintf(stdout, "\nPress Enter to quit...");
    fgetc(stdin);
}
