#include <glew.h>
#define GLFW_DLL
#include <glfw3.h>
#include <stdio.h>
#include "../glfw_utility.h"
#include "../vector_math.h"
#include "../matrix_math.h"
#define STB_IMAGE_IMPLEMENTATION
#include "../../stb_libs/stb_image.h"



// Game State Enum and Struct Definitions:
// ---------------------------------------

enum game_state_enum : int
{
    EnumGameStateHandleInput = 0,
    EnumGameStateMoveEntities,
    EnumGameStateNumGameStates,
};


struct game_state
{
    int   CurrentGameStateEnum;
    int   PlayerIndex;
    
    vec2  MoveDirection;
    float TotalMoveDistance;
};



// Player Input Struct Definition:
// -------------------------------

struct player_input
{
    int  WPressed;
    int  APressed;
    int  SPressed;
    int  DPressed;
    int  MinusPressed;
    int  EqualPressed;
    
    bool WRisingEdge;
    bool ARisingEdge;
    bool SRisingEdge;
    bool DRisingEdge;
    bool MinusRisingEdge;
    bool EqualRisingEdge;
    
    void ResetRisingEdgeFlags()
    {
        WRisingEdge     = FALSE;
        ARisingEdge     = FALSE;
        SRisingEdge     = FALSE;
        DRisingEdge     = FALSE;
        MinusRisingEdge = FALSE;
        EqualRisingEdge = FALSE;
    }
};



// Entity Enum and Struct Definitions:
// -----------------------------------

enum entity_type_enum : int
{
    EnumEntityTypePlayer = 0,
    EnumEntityTypeFloor,
    EnumEntityTypeWall,
    EnumEntityTypeBlock,

    EnumEntityTypeNumEntityTypes,
};


enum move_type_enum : int
{
    EnumMoveTypeStatic = 0,
    EnumMoveTypeDynamic,
    EnumMoveTypeNoMovement,  // "NoMovement" is a move type for entities that do not move, and do not prevent other entities from moving.
    EnumMoveTypeNumMoveTypes,
};


struct entity
{
    vec2 Position;
    vec2 NextPosition;
    
    int  EntityTypeEnum;
    int  MoveTypeEnum;
    
    bool IsMoving;
};




// GLFW Window:
// ------------

#define DEFAULT_WINDOW_HIGH         (  800 )
#define DEFAULT_WINDOW_WIDE         ( 1200 )
#define DEFAULT_WINDOW_ASPECT_RATIO ( ((float)(DEFAULT_WINDOW_WIDE)) / ((float)(DEFAULT_WINDOW_HIGH)) )

static GLFWwindow *Window;
static int         WindowHigh;
static int         WindowWide;
static float       WindowHighFloat;
static float       WindowWideFloat;
static float       WindowAspectRatio;
static int         FrameRate;
static float       TimePerFrame;


// Camera:
// -------

static vec3 CameraPosition;
static vec3 CameraTarget;
static vec3 CameraUp;
static mat4 LookAt;


// Orthographic Projection:
// ------------------------

#define DEFAULT_ORTHO_ASPECT_RATIO  ( DEFAULT_WINDOW_ASPECT_RATIO )
#define DEFAULT_ORTHO_HIGH          ( 10.f )
#define DEFAULT_ORTHO_WIDE          ( DEFAULT_ORTHO_HIGH * DEFAULT_ORTHO_ASPECT_RATIO )
#define DEFAULT_ORTHO_NEAR          (  0.f )
#define DEFAULT_ORTHO_FAR           ( 10.f )
#define ORTHO_MIN_LEVEL_BORDER      (  2.f * ENTITY_SCALE )

static float OrthoHigh;
static float OrthoWide;
static float OrthoNear;
static float OrthoFar;
static float OrthoAspectRatio;
static mat4  Orthographic;



// GLFW Utility:
// -------------

extern int               GlobalGLFWKeyInputCount;
extern glfw_key_input    GlobalGLFWKeyInputs[MAX_GLFW_KEY_INPUTS];
extern int               GlobalGLFWCharacterCount;
extern unsigned int      GlobalGLFWCharacters[MAX_GLFW_CHARACTERS];
extern int               GlobalGLFWMouseInputCount;
extern glfw_mouse_input  GlobalGLFWMouseInputs[MAX_GLFW_MOUSE_INPUTS];
extern double            GlobalMouseWheelY;


// Game State:
// -----------

static game_state GameState;
static vec2i      DesiredMoveDirection;


// Player Input:
// -------------

static player_input PlayerInput;


// Entities:
// ---------

#define MAX_ENTITIES             ( 1024 )
#define INVALID_ENTITY_INDEX     ( -1 )
#define ENTITY_SCALE             ( 1.f )
#define ENTITY_HALF_SCALE        ( 0.5f * ENTITY_SCALE )
#define ENTITY_MOVE_TIME         ( 0.2f ) // Time in seconds
#define ENTITY_MOVE_SPEED        ( ENTITY_SCALE / ENTITY_MOVE_TIME )

static int    EntityCount;
static entity Entities[MAX_ENTITIES];


// Level Strings:
// --------------

#define MAX_LEVELS      ( 128 )
#define PLAYER_CHAR     ( 'P' )
#define FLOOR_CHAR      ( '.' )
#define WALL_CHAR       ( 'X' )
#define BLOCK_CHAR      ( 'B' )
#define SPACE_CHAR      ( ' ' )
#define NEXT_ROW_CHAR   ( '\n' )

static int   CurrentLevel;
static int   LevelCount;
static char *LevelStrings[MAX_LEVELS];

static float LevelCenterX;
static float LevelCenterY;
static float LevelWide;
static float LevelHigh;


// Rendering:
// ----------

#define TEXTURE_LOAD_DESIRED_NUMBER_OF_CHANNELS_UNSPECIFIED ( 0 )
#define ENTITY_TYPE_RENDER_DATA_NUM_COLOR_CHANNELS          ( 4 )
#define TEXTURE_COLOR_INDEX_RED                             ( 0 )
#define TEXTURE_COLOR_INDEX_GREEN                           ( 1 )
#define TEXTURE_COLOR_INDEX_BLUE                            ( 2 )
#define TEXTURE_COLOR_INDEX_ALPHA                           ( 3 )

#define STATIC_ENTITY_Z                                     ( 1.f )
#define DYNAMIC_ENTITY_Z                                    ( 2.f )
#define NO_MOVEMENT_ENTITY_Z                                ( 0.f )

struct entity_type_textures
{
    GLuint        Texture;
    unsigned char Color[ENTITY_TYPE_RENDER_DATA_NUM_COLOR_CHANNELS]; // "Color" is a single texel that serves as a backup texture in case the desired entity texture cannot be loaded from file
    char*         TextureFilename;
};


static entity_type_textures EntityTypeTextures[EnumEntityTypeNumEntityTypes];

static float MoveTypeRenderingDepths[EnumMoveTypeNumMoveTypes] =
{
    STATIC_ENTITY_Z,
    DYNAMIC_ENTITY_Z,
    NO_MOVEMENT_ENTITY_Z,
};



// Function Declarations:
// ----------------------
// Note: definitions for the following functions are included after the "main" function, below.

static void InitEntities(entity *Entities, int *EntityCountPointer, char *LevelString);
static void UpdateEntities(entity *Entities, int EntityCount, game_state *GameStatePointer, int HorizontalInputDirection, int VerticalInputDirection, float DeltaTime);
static void DrawEntities(entity *Entities, int EntityCount);

static int  FindStaticOrDynamicEntityAtPosition(entity *Entities, int EntityCount, vec2 Position);
static int  FindFloorEntityAtPosition(entity *Entities, int EntityCount, vec2 Position);

static int  GetStringLength(char *String);
static void InitLevelStrings();
static void GetLevelDimensions(entity *Entities, int EntityCount, float *LevelCenterX, float *LevelCenterY, float *LevelWide, float *LevelHigh);
static void UpdateOrthographicProjectionForLevel(float LevelWide, float LevelHigh, float *OrthoWide, float *OrthoHigh);
static void LoadEntityGlTextures();





int main()
{
    glfwInit();

    const GLFWvidmode *VideoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
    
    WindowHigh        = DEFAULT_WINDOW_HIGH;
    WindowWide        = DEFAULT_WINDOW_WIDE;
    WindowAspectRatio = DEFAULT_WINDOW_ASPECT_RATIO;
    FrameRate         = VideoMode->refreshRate;
    TimePerFrame      = 1.f / ((float)(FrameRate));
    
    
    if ( WindowWide > VideoMode->width )
    {
        WindowWide = VideoMode->width;
        WindowHigh = WindowWide / WindowAspectRatio;
    }
    else if ( WindowHigh > VideoMode->height )
    {
        WindowHigh = VideoMode->height;
        WindowWide = WindowHigh * WindowAspectRatio;
    }
    
    
    Window = glfwCreateWindow(WindowWide, WindowHigh, "Grid-Based Movement Systems Part 1: Basic Movement", NULL, NULL);
    
    if ( !Window )
    {
        printf("ERROR: Failed to create GLFW window; exiting application...\n");
        return 0;
    }
    
    
    glfwSetKeyCallback(Window, KeyCallback);


    WindowWideFloat = (float)WindowWide;
    WindowHighFloat = (float)WindowHigh;
    
    glfwSetWindowPos(Window, (VideoMode->width - WindowWide) / 2 , (VideoMode->height - WindowHigh) / 2);
    
    glfwSwapInterval(1);
    
    glfwMakeContextCurrent(Window);
    glewInit();
    
    
    
    // Load/initialize entity textures, level strings, and entities:
    
    LoadEntityGlTextures();
    InitLevelStrings();
    
    CurrentLevel = 0;
    
    InitEntities(Entities, &EntityCount, LevelStrings[CurrentLevel]);
    
    
    
    // Initialize the game state and obtain the Player entity index for the first level:
    
    GameState.CurrentGameStateEnum = EnumGameStateHandleInput;
    GameState.PlayerIndex          = INVALID_ENTITY_INDEX;
    
    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        if ( Entities[EntityIndex].EntityTypeEnum == EnumEntityTypePlayer )
        {
            GameState.PlayerIndex = EntityIndex;
            break;
        }
    }
    
    if ( GameState.PlayerIndex == INVALID_ENTITY_INDEX )
    {
        printf("WARNING: No Player entity found in level file\n");
    }
    
    
    GetLevelDimensions(Entities, EntityCount, &LevelCenterX, &LevelCenterY, &LevelWide, &LevelHigh);
    
    
    CameraPosition.x = LevelCenterX;
    CameraPosition.y = LevelCenterY;
    CameraPosition.z = 0.5f * ( DEFAULT_ORTHO_FAR - DEFAULT_ORTHO_NEAR ) + DEFAULT_ORTHO_NEAR;
    CameraTarget.x   = CameraPosition.x;
    CameraTarget.y   = CameraPosition.y;
    CameraTarget.z   = 0.f;
    CameraUp         = { 0.f, 1.f, 0.f };
        
    LookAt.CreateLookAt(CameraPosition, CameraTarget, CameraUp);
    
    UpdateOrthographicProjectionForLevel(LevelWide, LevelHigh, &OrthoWide, &OrthoHigh);
    
    OrthoNear = DEFAULT_ORTHO_NEAR;
    OrthoFar  = DEFAULT_ORTHO_FAR;
    
    Orthographic.CreateOrthographic(OrthoWide, OrthoHigh, OrthoNear, OrthoFar);
    
    
    
    while (!glfwWindowShouldClose(Window))
    {
        PlayerInput.ResetRisingEdgeFlags();
        
        float DeltaTime = TimePerFrame;


        //
        // Handle input:
        //
        
        for ( int KeyInputIndex = 0; KeyInputIndex < GlobalGLFWKeyInputCount; KeyInputIndex++ )
        {
            int Key    = GlobalGLFWKeyInputs[KeyInputIndex].Key;
            int Action = GlobalGLFWKeyInputs[KeyInputIndex].Action;
            
            if ( Action == GLFW_PRESS )
            {
                if      ( Key == GLFW_KEY_W      ) { PlayerInput.WPressed     = 1; PlayerInput.WRisingEdge     = TRUE; }
                else if ( Key == GLFW_KEY_A      ) { PlayerInput.APressed     = 1; PlayerInput.ARisingEdge     = TRUE; }
                else if ( Key == GLFW_KEY_S      ) { PlayerInput.SPressed     = 1; PlayerInput.SRisingEdge     = TRUE; }
                else if ( Key == GLFW_KEY_D      ) { PlayerInput.DPressed     = 1; PlayerInput.DRisingEdge     = TRUE; }
                else if ( Key == GLFW_KEY_MINUS  ) { PlayerInput.MinusPressed = 1; PlayerInput.MinusRisingEdge = TRUE; }
                else if ( Key == GLFW_KEY_EQUAL  ) { PlayerInput.EqualPressed = 1; PlayerInput.EqualRisingEdge = TRUE; }
                else if ( Key == GLFW_KEY_ESCAPE ) glfwSetWindowShouldClose(Window, GLFW_TRUE);
            }
            else if ( Action == GLFW_REPEAT )
            {
            }
            else if ( Action == GLFW_RELEASE )
            {
                if      ( Key == GLFW_KEY_W     ) { PlayerInput.WPressed     = 0; }
                else if ( Key == GLFW_KEY_A     ) { PlayerInput.APressed     = 0; }
                else if ( Key == GLFW_KEY_S     ) { PlayerInput.SPressed     = 0; }
                else if ( Key == GLFW_KEY_D     ) { PlayerInput.DPressed     = 0; }
                else if ( Key == GLFW_KEY_MINUS ) { PlayerInput.MinusPressed = 0; }
                else if ( Key == GLFW_KEY_EQUAL ) { PlayerInput.EqualPressed = 0; }
            }
        }
        
        GlobalGLFWKeyInputCount = 0;



        // Change levels if the "minus" or "equal" (i.e. "plus") keys were pressed:
        
        if ( PlayerInput.MinusRisingEdge || PlayerInput.EqualRisingEdge )
        {
            bool CurrentLevelIndexWasChanged = FALSE;
            
            if ( PlayerInput.MinusRisingEdge && ( CurrentLevel > 0 ) )
            {
                CurrentLevel--;
                CurrentLevelIndexWasChanged = TRUE;
            }
            else if ( PlayerInput.EqualRisingEdge && ( CurrentLevel < ( LevelCount - 1 ) ) )
            {
                CurrentLevel++;
                CurrentLevelIndexWasChanged = TRUE;
            }
            
            
            
            // If the current level was changed, initialize entities for the new level:
            
            if ( CurrentLevelIndexWasChanged )
            {
                InitEntities(Entities, &EntityCount, LevelStrings[CurrentLevel]);
                
                GameState.CurrentGameStateEnum = EnumGameStateHandleInput;
                GameState.PlayerIndex          = INVALID_ENTITY_INDEX;
                
                for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
                {
                    if ( Entities[EntityIndex].EntityTypeEnum == EnumEntityTypePlayer )
                    {
                        GameState.PlayerIndex = EntityIndex;
                        break;
                    }
                }
                
                if ( GameState.PlayerIndex == INVALID_ENTITY_INDEX )
                {
                    printf("WARNING: No Player entity found in level file\n");
                }
                
                
                GetLevelDimensions(Entities, EntityCount, &LevelCenterX, &LevelCenterY, &LevelWide, &LevelHigh);
                
                
                // Update camera for the new level:
                CameraPosition.x = LevelCenterX;
                CameraPosition.y = LevelCenterY;
                CameraPosition.z = 0.5f * ( DEFAULT_ORTHO_FAR - DEFAULT_ORTHO_NEAR ) + DEFAULT_ORTHO_NEAR;
                CameraTarget.x   = CameraPosition.x;
                CameraTarget.y   = CameraPosition.y;
                CameraTarget.z   = 0.f;
                CameraUp         = { 0.f, 1.f, 0.f };
                
                LookAt.CreateLookAt(CameraPosition, CameraTarget, CameraUp);
                
                
                // Update the orthographic projection for the new level:
                UpdateOrthographicProjectionForLevel(LevelWide, LevelHigh, &OrthoWide, &OrthoHigh);

                Orthographic.CreateOrthographic(OrthoWide, OrthoHigh, OrthoNear, OrthoFar);
            }
        }



        // Determine the desired move direction based on player input:
        
        DesiredMoveDirection   = {};
        DesiredMoveDirection.x = PlayerInput.DRisingEdge - PlayerInput.ARisingEdge;
        DesiredMoveDirection.y = PlayerInput.WRisingEdge - PlayerInput.SRisingEdge;


        //
        // Update Entities:
        //

        UpdateEntities(Entities, EntityCount, &GameState, DesiredMoveDirection.x, DesiredMoveDirection.y, DeltaTime);



        //
        // Render:
        //

        glEnable(GL_DEPTH_TEST);
        glClearColor(0.4f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        DrawEntities(Entities, EntityCount);

        glfwSwapBuffers(Window);

        glfwPollEvents();
    }
    
    
    return 0;
}




// Function Definitions:
// ---------------------

static void InitEntities(entity *Entities, int *EntityCountPointer, char* LevelString)
{
    float X = 0.f;
    float Y = 0.f;
    
    entity* EntityPointer = Entities;
    int     EntityCount   = 0;
    
    int LevelStringLength = GetStringLength(LevelString);
    
    
    for ( int StringIndex = 0; StringIndex < LevelStringLength; StringIndex++ )
    {
        char C = LevelString[StringIndex];
        
        if ( C == NEXT_ROW_CHAR )
        {
            X  = 0.f;
            Y -= ENTITY_SCALE;
            continue;
        }
        else if ( C == SPACE_CHAR )
        {
            X += ENTITY_SCALE;
            continue;
        }
        else
        {
            *EntityPointer = {};
            
            bool AddFloorEntityUnderneathCurrentEntity = TRUE;
            
            if ( C == PLAYER_CHAR )
            {
                EntityPointer->EntityTypeEnum = EnumEntityTypePlayer;
                EntityPointer->MoveTypeEnum   = EnumMoveTypeDynamic;
            }
            else if ( C == FLOOR_CHAR )
            {
                EntityPointer->EntityTypeEnum = EnumEntityTypeFloor;
                EntityPointer->MoveTypeEnum   = EnumMoveTypeNoMovement;
                
                AddFloorEntityUnderneathCurrentEntity = FALSE;
            }
            else if ( C == WALL_CHAR )
            {
                EntityPointer->EntityTypeEnum = EnumEntityTypeWall;
                EntityPointer->MoveTypeEnum   = EnumMoveTypeStatic;
            }
            else if ( C == BLOCK_CHAR )
            {
                EntityPointer->EntityTypeEnum = EnumEntityTypeBlock;
                EntityPointer->MoveTypeEnum   = EnumMoveTypeDynamic;
            }
            else
            {
                printf("WARNING: No entity character defined for character '%c' (%d)\n", C, (int)C);
                continue;
            }
            
            
            EntityPointer->Position = { X, Y };
            
            
            if ( AddFloorEntityUnderneathCurrentEntity )
            {
                EntityPointer++;
                EntityCount++;
                
                EntityPointer->EntityTypeEnum = EnumEntityTypeFloor;
                EntityPointer->MoveTypeEnum   = EnumMoveTypeNoMovement;
                EntityPointer->Position       = { X, Y };
            }
            
            
            X += ENTITY_SCALE;
            EntityPointer++;
            EntityCount++;
        }
    }
    
    *EntityCountPointer = EntityCount;
}


static void UpdateEntities(entity *Entities, int EntityCount, game_state *GameStatePointer, int HorizontalInputDirection, int VerticalInputDirection, float DeltaTime)
{
    switch ( GameStatePointer->CurrentGameStateEnum )
    {
        case EnumGameStateHandleInput:
        {
            // Check if any input direction was pressed:
            if ( !HorizontalInputDirection && !VerticalInputDirection )
            {
                // Exit the input handler if no input was pressed:
                break;
            }
            
            
            // Set the game state's move direction based on the input direction(s):
            GameStatePointer->MoveDirection = { (float)HorizontalInputDirection, 
                                                (float)VerticalInputDirection    };
            
            
            // Prevent diagonal movement if two orthogonal input directions were pressed:
            if ( HorizontalInputDirection && VerticalInputDirection ) 
            {
                GameStatePointer->MoveDirection.y = 0.f;
            }
            

            // Check for a Floor entity at the Player's next movement position:
            vec2 NextPlayerPosition = Entities[GameStatePointer->PlayerIndex].Position
                                    + ENTITY_SCALE * GameStatePointer->MoveDirection;
            int  FloorEntityIndex   = FindFloorEntityAtPosition(Entities, EntityCount, NextPlayerPosition);
            
            if ( FloorEntityIndex == INVALID_ENTITY_INDEX )
            {
                // No Floor entity was found for the Player entity to move to;
                // no entities will move, so exit the Handle Input game state handler:
                break;
            }
            
            
            // Reset all entity flags that are movement-related before determining if entities can move:
            for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
            {
                Entities[EntityIndex].IsMoving = FALSE;
            }
            
            
            // Find all entities in the desired move direction that the player will push:
            // - For all Dynamic entities found, set their "IsMoving" flags and determine their next positions
            // - If an entity with Static move type is found, stop searching for entities as no entities will be moving
            
            int  CurrentEntityIndex = GameStatePointer->PlayerIndex;
            vec2 CurrentPosition    = Entities[CurrentEntityIndex].Position;
            
            while ( CurrentEntityIndex != INVALID_ENTITY_INDEX )
            {
                if ( Entities[CurrentEntityIndex].MoveTypeEnum == EnumMoveTypeDynamic )
                {
                    Entities[CurrentEntityIndex].IsMoving     = TRUE;
                    Entities[CurrentEntityIndex].NextPosition = Entities[CurrentEntityIndex].Position
                                                              + ENTITY_SCALE * GameStatePointer->MoveDirection;
                }
                else if ( Entities[CurrentEntityIndex].MoveTypeEnum == EnumMoveTypeStatic )
                {
                    break;
                }
                
                
                // Search for the next Static or Dynamic entity in the desired movement direction:
                CurrentPosition   += ENTITY_SCALE * GameStatePointer->MoveDirection;
                CurrentEntityIndex = FindStaticOrDynamicEntityAtPosition(Entities, EntityCount, CurrentPosition);
            }
            
            
            // Check if the last entity found in the movement direction was static:
            // - If the last entity found has Static move type, then entities will not move in the desired movement direction this turn.
            
            if ( ( CurrentEntityIndex != INVALID_ENTITY_INDEX )
              && ( Entities[CurrentEntityIndex].MoveTypeEnum == EnumMoveTypeStatic ) )
            {
                // No entities will move, so break from the "Handle Input" game state handler:
                break;
            }

            
            // At least one dynamic entity is moving; go to the Move Entities game state:
            GameStatePointer->CurrentGameStateEnum = EnumGameStateMoveEntities;
            GameStatePointer->TotalMoveDistance    = 0.f;
        }
        break;
        case EnumGameStateMoveEntities:
        {
            // Move Entities game state:
            // Move entities continuously along the move direction until they have moved one full grid space. 
            
            
            // Determine the maximum distance that entities will move along the move direction this frame:
            
            float Displacement = ENTITY_MOVE_SPEED * DeltaTime;
            
            GameStatePointer->TotalMoveDistance += Displacement;
            
            
            if ( GameStatePointer->TotalMoveDistance < ENTITY_SCALE )
            {
                // Entities will not have moved one full entity unit distance by the end of 
                // this frame, so just move entities along the move direction by the displacement
                // calculated above:

                for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
                {
                    if ( !Entities[EntityIndex].IsMoving ) continue; // Skip entities that are not moving
                    
                    Entities[EntityIndex].Position += Displacement * GameStatePointer->MoveDirection;
                }
            }
            else
            {
                // Entities will have moved one full entity unit distance (or more) by the end 
                // of this frame, so we can now set each moving entity to be at its "Next Position,"
                // and reset their "IsMoving" flags:
                
                for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
                {
                    if ( !Entities[EntityIndex].IsMoving ) continue;

                    Entities[EntityIndex].Position = Entities[EntityIndex].NextPosition;
                    Entities[EntityIndex].IsMoving = FALSE;
                }
                
                
                // Now that entities have finished moving, we can go back to the Handle Input game state:
                
                GameStatePointer->CurrentGameStateEnum = EnumGameStateHandleInput;
            }
        }
        break;
        default:
        break;
    }
}


static void DrawEntities(entity *Entities, int EntityCount)
{
    glUseProgram(0);
    
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf((float*)&Orthographic);
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf((float*)&LookAt);


    glEnable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE0);


    // Draw bottom-most floor entities first to get proper back-to-front alpha blending:
    
    glBindTexture(GL_TEXTURE_2D, EntityTypeTextures[EnumEntityTypeFloor].Texture);
    
    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        if ( Entities[EntityIndex].EntityTypeEnum != EnumEntityTypeFloor ) continue;
        
        vec2 P = Entities[EntityIndex].Position;
        
        glBegin(GL_QUADS);
        glColor3f(1.f, 1.f, 1.f);
        glTexCoord2f(0.f, 1.f); glVertex3f(P.x - ENTITY_HALF_SCALE, P.y + ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(0.f, 0.f); glVertex3f(P.x - ENTITY_HALF_SCALE, P.y - ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(1.f, 0.f); glVertex3f(P.x + ENTITY_HALF_SCALE, P.y - ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(1.f, 1.f); glVertex3f(P.x + ENTITY_HALF_SCALE, P.y + ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glEnd();
    }
    

    // Draw all other non-Floor entities with blending enabled:
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        if ( Entities[EntityIndex].EntityTypeEnum == EnumEntityTypeFloor ) continue;

        glBindTexture(GL_TEXTURE_2D, EntityTypeTextures[Entities[EntityIndex].EntityTypeEnum].Texture);        

        vec2 P = Entities[EntityIndex].Position;
        
        glBegin(GL_QUADS);
        glColor3f(1.f, 1.f, 1.f);
        glTexCoord2f(0.f, 1.f); glVertex3f(P.x - ENTITY_HALF_SCALE, P.y + ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(0.f, 0.f); glVertex3f(P.x - ENTITY_HALF_SCALE, P.y - ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(1.f, 0.f); glVertex3f(P.x + ENTITY_HALF_SCALE, P.y - ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glTexCoord2f(1.f, 1.f); glVertex3f(P.x + ENTITY_HALF_SCALE, P.y + ENTITY_HALF_SCALE, MoveTypeRenderingDepths[Entities[EntityIndex].MoveTypeEnum]);
        glEnd();
    }
    
    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
}


static int FindStaticOrDynamicEntityAtPosition(entity *Entities, int EntityCount, vec2 Position)
{
    // Assemble an AABB around "Position"
    float AabbMinX = Position.x - ENTITY_HALF_SCALE;
    float AabbMaxX = Position.x + ENTITY_HALF_SCALE;
    float AabbMinY = Position.y - ENTITY_HALF_SCALE;
    float AabbMaxY = Position.y + ENTITY_HALF_SCALE;


    int FoundEntityIndex = INVALID_ENTITY_INDEX;
    
    
    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        // Skip entities that do not have Static or Dynamic move type:
        if ( ( Entities[EntityIndex].MoveTypeEnum != EnumMoveTypeStatic  )
          && ( Entities[EntityIndex].MoveTypeEnum != EnumMoveTypeDynamic ) ) continue;
        
        
        // Perform 2D AABB collision test with the current entity's position:
        if ( Entities[EntityIndex].Position.x < AabbMinX ) continue;
        if ( Entities[EntityIndex].Position.x > AabbMaxX ) continue;
        if ( Entities[EntityIndex].Position.y < AabbMinY ) continue;
        if ( Entities[EntityIndex].Position.y > AabbMaxY ) continue;
        
        
        // Stop searching for entities once an entity is found, as only
        // one Static or Dynamic entity can be located at a grid position:
        FoundEntityIndex = EntityIndex;
        break;
    }
    
    
    return FoundEntityIndex;
}


static int FindFloorEntityAtPosition(entity *Entities, int EntityCount, vec2 Position)
{
    // Assemble an AABB around "Position"
    float AabbMinX = Position.x - ENTITY_HALF_SCALE;
    float AabbMaxX = Position.x + ENTITY_HALF_SCALE;
    float AabbMinY = Position.y - ENTITY_HALF_SCALE;
    float AabbMaxY = Position.y + ENTITY_HALF_SCALE;


    int FoundEntityIndex = INVALID_ENTITY_INDEX;
    
    
    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        // Skip entities that are not Floor entities:
        if ( Entities[EntityIndex].EntityTypeEnum != EnumEntityTypeFloor ) continue;
        
        
        // Perform 2D AABB collision test with the current entity's position:
        if ( Entities[EntityIndex].Position.x < AabbMinX ) continue;
        if ( Entities[EntityIndex].Position.x > AabbMaxX ) continue;
        if ( Entities[EntityIndex].Position.y < AabbMinY ) continue;
        if ( Entities[EntityIndex].Position.y > AabbMaxY ) continue;
        
        
        // Stop searching for entities once an entity is found, as only
        // one Static or Dynamic entity can be located at a grid position:
        FoundEntityIndex = EntityIndex;
        break;
    }
    
    
    return FoundEntityIndex;
}


static int GetStringLength(char *String)
{
    int StringLength = 0;
    
    while ( *String++ )
    {
        StringLength++;
    }
    
    return StringLength;
}


static void InitLevelStrings()
{

    LevelCount = 0;

LevelStrings[LevelCount++] = "\
XXXXXXXX\n\
X......X\n\
X.P....X\n\
X......X\n\
X..B...X\n\
X......X\n\
X......X\n\
XXXXXXXX\n\
";

LevelStrings[LevelCount++] = "\
XXXXXXXXXXXX\n\
X......  ..X\n\
X......  ..X\n\
X....P.  ..X\n\
X..........X\n\
X.....B....X\n\
X..........X\n\
X..........X\n\
X..........X\n\
X..........X\n\
X..........X\n\
XXXXXXXXXXXX\n\
";

LevelStrings[LevelCount++] = "\
XXXXXXXXXXXXXXXXXX\n\
X................X\n\
X...........P....X\n\
X................X\n\
X............B...X\n\
X.......B........X\n\
X................X\n\
XXXXXXXXXXXXXXXXXX\n\
";

LevelStrings[LevelCount++] = "\
XXXXXX\n\
X....X\n\
X.P..X\n\
X....X\n\
X..B.X\n\
X....X\n\
X....X\n\
X....X\n\
X....X\n\
X....X\n\
X....X\n\
X....X\n\
X....X\n\
XXXXXX\n\
";

LevelStrings[LevelCount++] = "\
X........X\n\
X.P......X\n\
X........X\n\
";

LevelStrings[LevelCount++] = "\
X..... ..X\n\
X.P... ..X\n\
X..... ..X\n\
";

LevelStrings[LevelCount++] = "\
X........X\n\
X.P.B....X\n\
X........X\n\
";

LevelStrings[LevelCount++] = "\
X..... ..X\n\
X.P.B. ..X\n\
X..... ..X\n\
";

LevelStrings[LevelCount++] = "\
X........X\n\
X.P.B.B..X\n\
X........X\n\
";

LevelStrings[LevelCount++] = "\
X..... ..X\n\
X.P.BB ..X\n\
X..... ..X\n\
";

LevelStrings[LevelCount++] = "\
X........X\n\
X.P...X..X\n\
X........X\n\
";

LevelStrings[LevelCount++] = "\
X........X\n\
X.P.B.X..X\n\
X........X\n\
";

}


static void GetLevelDimensions(entity *Entities, int EntityCount, float *LevelCenterX, float *LevelCenterY, float *LevelWide, float *LevelHigh)
{
    float MinX =  1000000.f;
    float MinY =  1000000.f;
    float MaxX = -1000000.f;
    float MaxY = -1000000.f;
    
    for ( int EntityIndex = 0; EntityIndex < EntityCount; EntityIndex++ )
    {
        if      ( Entities[EntityIndex].Position.x < MinX ) MinX = Entities[EntityIndex].Position.x;
        else if ( Entities[EntityIndex].Position.x > MaxX ) MaxX = Entities[EntityIndex].Position.x;
        
        if      ( Entities[EntityIndex].Position.y < MinY ) MinY = Entities[EntityIndex].Position.y;
        else if ( Entities[EntityIndex].Position.y > MaxY ) MaxY = Entities[EntityIndex].Position.y;
    }


    MinX -= ENTITY_HALF_SCALE;
    MaxX += ENTITY_HALF_SCALE;

    MinY -= ENTITY_HALF_SCALE;
    MaxY += ENTITY_HALF_SCALE;

    *LevelCenterX = 0.5f * ( MinX + MaxX );
    *LevelCenterY = 0.5f * ( MinY + MaxY );
    *LevelWide    = ( MaxX - MinX );
    *LevelHigh    = ( MaxY - MinY );
}


static void UpdateOrthographicProjectionForLevel(float LevelWide, float LevelHigh, float *OrthoWide, float *OrthoHigh)
{
    float LevelAspectRatio = LevelWide / LevelHigh;

    if ( LevelAspectRatio > DEFAULT_ORTHO_ASPECT_RATIO )
    {
        // The level's aspect ratio is greater than the default orthographic aspect ratio, so use the level's width to determine
        // the appropriate width and height of the orthographic projection:
        
        float LevelWidePlusOrthoBorder = LevelWide + ORTHO_MIN_LEVEL_BORDER;
        
        if ( LevelWidePlusOrthoBorder > DEFAULT_ORTHO_WIDE )
        {
            *OrthoWide =  LevelWide + ORTHO_MIN_LEVEL_BORDER;
            *OrthoHigh = *OrthoWide / DEFAULT_ORTHO_ASPECT_RATIO;
        }
        else
        {
            *OrthoWide = DEFAULT_ORTHO_WIDE;
            *OrthoHigh = DEFAULT_ORTHO_HIGH;
        }
    }
    else
    {
        // The level's aspect ratio is less than or equal to the default orthographic aspect ratio, so use the level's height to determine
        // the appropriate width and height of the orthographic projection:
        
        float LevelHighPlusOrthoBorder = LevelHigh + ORTHO_MIN_LEVEL_BORDER;
        
        if ( LevelHighPlusOrthoBorder > DEFAULT_ORTHO_HIGH )
        {
            *OrthoHigh =  LevelHigh + ORTHO_MIN_LEVEL_BORDER;
            *OrthoWide = *OrthoHigh * DEFAULT_ORTHO_ASPECT_RATIO;
        }
        else
        {
            *OrthoWide = DEFAULT_ORTHO_WIDE;
            *OrthoHigh = DEFAULT_ORTHO_HIGH;
        }
    }
}


static void LoadEntityGlTextures()
{
    EntityTypeTextures[EnumEntityTypePlayer].TextureFilename = "textures/player.png";
    EntityTypeTextures[EnumEntityTypeBlock ].TextureFilename = "textures/block.png";
    EntityTypeTextures[EnumEntityTypeFloor ].TextureFilename = "textures/floor.png";
    EntityTypeTextures[EnumEntityTypeWall  ].TextureFilename = "textures/wall.png";
    
    EntityTypeTextures[EnumEntityTypePlayer].Color[TEXTURE_COLOR_INDEX_RED  ] = 255;
    EntityTypeTextures[EnumEntityTypePlayer].Color[TEXTURE_COLOR_INDEX_GREEN] = 128;
    EntityTypeTextures[EnumEntityTypePlayer].Color[TEXTURE_COLOR_INDEX_BLUE ] =   0;
    EntityTypeTextures[EnumEntityTypePlayer].Color[TEXTURE_COLOR_INDEX_ALPHA] = 255;
    
    EntityTypeTextures[EnumEntityTypeBlock ].Color[TEXTURE_COLOR_INDEX_RED  ] = 255;
    EntityTypeTextures[EnumEntityTypeBlock ].Color[TEXTURE_COLOR_INDEX_GREEN] =   0;
    EntityTypeTextures[EnumEntityTypeBlock ].Color[TEXTURE_COLOR_INDEX_BLUE ] =   0;
    EntityTypeTextures[EnumEntityTypeBlock ].Color[TEXTURE_COLOR_INDEX_ALPHA] = 255;
    
    EntityTypeTextures[EnumEntityTypeFloor ].Color[TEXTURE_COLOR_INDEX_RED  ] = 204;
    EntityTypeTextures[EnumEntityTypeFloor ].Color[TEXTURE_COLOR_INDEX_GREEN] = 204;
    EntityTypeTextures[EnumEntityTypeFloor ].Color[TEXTURE_COLOR_INDEX_BLUE ] = 204;
    EntityTypeTextures[EnumEntityTypeFloor ].Color[TEXTURE_COLOR_INDEX_ALPHA] = 255;
    
    EntityTypeTextures[EnumEntityTypeWall  ].Color[TEXTURE_COLOR_INDEX_RED  ] =  78;
    EntityTypeTextures[EnumEntityTypeWall  ].Color[TEXTURE_COLOR_INDEX_GREEN] =  78;
    EntityTypeTextures[EnumEntityTypeWall  ].Color[TEXTURE_COLOR_INDEX_BLUE ] =  78;
    EntityTypeTextures[EnumEntityTypeWall  ].Color[TEXTURE_COLOR_INDEX_ALPHA] = 255;
    
    
    stbi_set_flip_vertically_on_load(TRUE);
    
    
    // Load textures for each entity type:
    for ( int EntityTypeIndex = 0; EntityTypeIndex < EnumEntityTypeNumEntityTypes; EntityTypeIndex++ )
    {
        // Create OpenGL texture object for current entity type:
        glGenTextures(1, &EntityTypeTextures[EntityTypeIndex].Texture);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, EntityTypeTextures[EntityTypeIndex].Texture);
        
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        
        
        // Load texture from file:
        int TextureWide        = 0;
        int TextureHigh        = 0;
        int TextureNumChannels = 0;
        
        unsigned char* TextureData = NULL;
        
        TextureData = stbi_load(EntityTypeTextures[EntityTypeIndex].TextureFilename, 
                                &TextureWide, &TextureHigh, &TextureNumChannels, 
                                TEXTURE_LOAD_DESIRED_NUMBER_OF_CHANNELS_UNSPECIFIED);


        if ( TextureData != NULL )
        {
            // Successfully loaded texture from file; use the loaded texture data to create the texture:
            
            GLenum TextureFormat         = GL_RGBA;
            GLint  TextureInternalFormat = GL_RGBA;
            
            if ( TextureNumChannels == 3 )
            {
                TextureFormat         = GL_RGB;
                TextureInternalFormat = GL_RGB;
            }
            
            glTexImage2D(GL_TEXTURE_2D, 0, TextureInternalFormat, TextureWide, TextureHigh, 0, TextureFormat, GL_UNSIGNED_BYTE, TextureData);
            
            stbi_image_free(TextureData);
        }
        else
        {
            // Failed to load texture from file; use the entity type texture 'Color' as the texture data:
            
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &EntityTypeTextures[EntityTypeIndex].Color);
        }
        
        glGenerateMipmap(GL_TEXTURE_2D);
        
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}
