#include "duke3d.h"
#include "console.h"
#include "cvars.h"
#include "cvar_defs.h"
#include <stdarg.h>

// For autoexec.cfg
#include <stdio.h>

#define MAX_STRING_LENGTH 64
#define MAX_CONSOLE_STRING_LENGTH 70
#define MAX_CVAR_ARGS 10
#define MAX_CONSOLE_VIEWABLE 10

#define BUILD_NUMBER "Build 19"

typedef struct console_element
{
    char  text[MAX_CONSOLE_STRING_LENGTH];
    void* prev;
    void* next;
}CONSOLEELEMENT;

// Private member functions
void CONSOLE_InsertUsedCommand(const char * szUsedCommand);
void CONSOLE_ClearUsedCommandList(void);
void CONSOLE_RecalculateDirtyBuffer(void);

// console argument tracker
int argc;
char  argv[MAX_CVAR_ARGS][MAX_CONSOLE_STRING_LENGTH];
// Console entries, prepending linked list
CONSOLEELEMENT *console_buffer = NULL;
// Current viewed setion of the console
CONSOLEELEMENT *p_console_current_view = NULL;
// Previously entered commands:
CONSOLEELEMENT *console_used_command_list = NULL;
// Previously entered commands:
CONSOLEELEMENT *console_used_command_list_current = NULL;

// dirty buffer
char  dirty_buffer[MAX_CONSOLE_STRING_LENGTH];

// dirty buffer control vars
int console_cursor_pos = 0; //without spaces
int console_num_spaces = 0; //number of spaces

// Toggle for the console state
int nConsole_Active = 0;

void CVAR_RegisterDefaultCvarBindings(void);
// Initialize the console
void CONSOLE_Init(void)
{
    CONSOLE_Reset();

    // Console Variable init
    CVAR_RegisterDefaultCvarBindings();

    // read in our startup.txt
    CONSOLE_ParseStartupScript();

    CONSOLE_Printf("Type \'HELP\' for help with console Commands.");
}

void CONSOLE_Reset(void)
{
    CONSOLEELEMENT *pElement;
    CONSOLEELEMENT *pDelElement;

    // Free the list if it exists
    pElement = console_buffer;
    while(pElement)
    {
        pDelElement = pElement;
        pElement = (CONSOLEELEMENT*)pElement->next;

        free(pDelElement);
    }

    console_buffer = NULL;
    p_console_current_view = NULL;
}

void CONSOLE_Term()
{
    // Do any clean up releasing to the console
    CONSOLE_Reset();
	CONSOLE_ClearUsedCommandList();
}

void CONSOLE_ParseStartupScript()
{
	// FIX_00017: heavy autoexec.cfg not needed anymore.
    char  *sStartupScript = ENGINE_DIR "startup.cfg";

    FILE* fp = fopen(sStartupScript, "r");

    // If the file exists
    if(NULL != fp)
    {
        char  line[128];
        memset(line, 0, 128);

        while(fgets(line ,128-1, fp) != NULL)
        {
            CONSOLE_ParseCommand(line);

            memset(line, 0, 128);
        }
        fclose(fp);
    }
}

void CONSOLE_HandleInput()
{
    char * lastKey;
    int tmp;

    if(g_CV_classic)
    {
        nConsole_Active = 0;
        return;
    }

    // See if we should toggle the console
    if(ACTION(gamefunc_Console))
    {
        nConsole_Active = !nConsole_Active;

		CONTROL_ClearAction(gamefunc_Console);
        //KB_ClearKeyDown(sc_Tilde);


		if(nConsole_Active)
		{
			// If this is a singleplayer game, let's pause the game when the console is active
			if (numplayers == 1)
			{
				if (!ud.recstat) //check if we are not playing a Demo and console is active
				{
					ud.pause_on = 1;
				}else
				{
					ud.pause_on = 0;
				}
			}
		}
		else
        // Bug fix: make sure the frag bar displays after console
        // is hidden
        //if(!nConsole_Active)
        {
            if ( ud.multimode > 1 && ud.coop != 1 )
            {
                displayfragbar();
            }
			if(numplayers<2)
				ud.pause_on = 0;
			// FIX_00078: Out Of Synch error (multiplayer) when using console in pause mode
        }

        return;
    }

    // If the console is not active, there's no need to process input
    if(!nConsole_Active)
    {
        return;
    }

    switch(KB_GetLastScanCode())
    {
    case sc_Space:
        {
            if(strlen(dirty_buffer) < MAX_CONSOLE_STRING_LENGTH-2)
            {
                strncat(dirty_buffer, " ", 1);
                console_num_spaces++;
            }
        }
        break;
    case sc_Delete:
    case sc_BackSpace:
        {
            tmp = strlen(dirty_buffer);
            if(tmp > 0)
            {
                if( dirty_buffer[tmp - 1] == ' ' )
                {
                    dirty_buffer[tmp - 1] = '\0';
                    console_num_spaces--;
                }
                else
                {
                    dirty_buffer[tmp - 1] = '\0';
                    console_cursor_pos--;
                }
            }
        }
        break;
        //Ignore list
    case sc_LeftShift:
    case sc_RightShift:
    case sc_PrintScreen:
    case sc_Tab:
    case sc_NumLock:
    case sc_LeftAlt:
    case sc_LeftControl:
    case sc_CapsLock:
    case sc_Bad:
    case sc_LeftArrow:
    case sc_RightArrow:
    case sc_Insert:
    case sc_Home: //this should take us to the top of the list
    case sc_RightAlt:
    case sc_RightControl:
    case sc_Tilde: //ignore
        {
        }break;
    case sc_kpad_Enter:
    case sc_Enter:
        {
            //If console_buffer[0] strlen() != 0
            //1. Push the dirty_buffer unto the console_buffer
            //2. parse the text
            rb->kbd_input(dirty_buffer, sizeof(dirty_buffer), NULL);

            CONSOLE_Printf("%s", dirty_buffer);
            console_cursor_pos = 0;
            console_num_spaces = 0;

			CONSOLE_InsertUsedCommand(dirty_buffer);
            CONSOLE_ParseCommand(dirty_buffer);

            memset(dirty_buffer, 0, MAX_CONSOLE_STRING_LENGTH);

        }break;
    case sc_UpArrow:
        {
			/*
            if(p_console_current_view->next != NULL)
            {
                p_console_current_view = p_console_current_view->next;
            }
			*/
			if(console_used_command_list_current == NULL)
			{
				if(NULL == console_used_command_list)
				{
					break;
				}
				console_used_command_list_current = console_used_command_list;
				sprintf(dirty_buffer, "%s", console_used_command_list_current->text);
				CONSOLE_RecalculateDirtyBuffer();
				break;
			}

            if(console_used_command_list_current->next != NULL)
            {
                console_used_command_list_current = console_used_command_list_current->next;
				sprintf(dirty_buffer, "%s", console_used_command_list_current->text);
				CONSOLE_RecalculateDirtyBuffer();
            }else
            if(console_used_command_list_current != NULL)
			{
				sprintf(dirty_buffer, "%s", console_used_command_list_current->text);
				CONSOLE_RecalculateDirtyBuffer();
			}

        }break;
    case sc_PgUp:
    case sc_A:
        {
            int i;
            for(i = 0; i < MAX_CONSOLE_VIEWABLE; i++)
            {
                if(p_console_current_view->next != NULL)
                {
                    p_console_current_view = p_console_current_view->next;
                }
                else
                {
                    break;
                }
            }
        }break;
    case sc_DownArrow:
        {
			/*
            if(p_console_current_view->prev != NULL)
            {
                p_console_current_view = p_console_current_view->prev;
            }
			*/
            if(console_used_command_list_current != NULL)
            {
				if(console_used_command_list_current->prev != NULL)
				{
					console_used_command_list_current = console_used_command_list_current->prev;
					sprintf(dirty_buffer, "%s", console_used_command_list_current->text);
					CONSOLE_RecalculateDirtyBuffer();
				}
			}
        }break;
    case sc_PgDn:
    case sc_D:
        {
            int i;
            for(i = 0; i < MAX_CONSOLE_VIEWABLE; i++)
            {
                if(p_console_current_view->prev != NULL)
                {
                    p_console_current_view = p_console_current_view->prev;
                }
                else
                {
                    break;
                }
            }
        }break;
    case sc_End:
        {
            p_console_current_view = console_buffer;
        }break;
    case sc_Escape:
        {
            nConsole_Active = 0;
            KB_ClearKeyDown(sc_Escape);
			// FIX_00057: Using ESC to get out of the console mode wouldn't take pause mode off
			if(numplayers<2)
				ud.pause_on = 0;
			// FIX_00078: Out Of Synch error (multiplayer) when using console in pause mode
        }break;
    default:
        {
            if(strlen(dirty_buffer) < MAX_CONSOLE_STRING_LENGTH-2)
            {
                lastKey = KB_ScanCodeToString(KB_GetLastScanCode());

                if(lastKey)
                {
                    strncat(dirty_buffer, lastKey, 1);
                    console_cursor_pos++;
                    //printf("Key %s : %s\n", lastKey, console_buffer[0]);
                }
            }

        }
        break;
    }


    KB_ClearLastScanCode();
}

void CONSOLE_Render(void)
{
    if(g_CV_classic)
    {
        return;
    }

    // Let the Cvars draw something if they need to.
    CVAR_Render();

    if(nConsole_Active)
    {
        int x,y, y1, y2;
        int i, iCurHeight = 0, iCurWidth = 0;
        int iYOffset = 3; //offset for the console text
        CONSOLEELEMENT *pElement;

        y1 = 0;
        y2 = (ydim / 2) - ((ydim / 2)/12);

        // Draw console background
        for(y=y1;y<y2;y+=128)
        {
            for(x=0;x<xdim;x+=128)
            {
                rotatesprite(x<<16,y<<16,65536L,0,BIGHOLE,8,0,(g_CV_TransConsole ? 1:0)+8+16+64+128,0,y1,xdim-1,y2-1);
            }
        }

        // Draw bottom egde of console
        rotatesprite(78<<16,94<<16,65536L,512,WINDOWBORDER1,24,0,2+8,0,0,xdim-1,ydim-1);
        rotatesprite(238<<16,94<<16,65536L,512,WINDOWBORDER1,24,0,2+8,0,0,xdim-1,ydim-1);

        // Draw the contents of the console buffer
        pElement = p_console_current_view;//console_buffer;
        for(i = 0; i < MAX_CONSOLE_VIEWABLE; i++)
        {
            if(!pElement)
            {
                break;
            }

            minitext(5,(8*(9-i)) + iYOffset,
                     pElement->text,
                     g_CV_console_text_color,
                     10+16);

             pElement = (CONSOLEELEMENT*)pElement->next;

        }

        // Set the height of the new line
        iCurHeight = (8*10) + iYOffset;

        // Draw dirty buffer
        minitext(5, iCurHeight, dirty_buffer, 0,10+16);

        // Calculate the location of the cursor
        iCurWidth =  (console_cursor_pos*4) + (console_num_spaces*5) +5;

        //Draw the version number
        minitext(283, iCurHeight, BUILD_NUMBER, 17,10+16);

        // Draw the cursor //Change the color every second
        minitext(iCurWidth, iCurHeight,"_",(time(NULL)%2)+1,10+16);
    }
    else
    if(g_CV_num_console_lines > 0)
    {
        int i, iYOffset = 3; //offset for the console text
        CONSOLEELEMENT *pElement;
        // Draw the contents of the console buffer
        pElement = console_buffer;
        for(i = 0; i < g_CV_num_console_lines; i++)
        {
            if(!pElement)
            {
                break;
            }

            minitext(5,(8*((g_CV_num_console_lines-1)-i)) + iYOffset,
                     pElement->text,
                     g_CV_console_text_color,
                     10+16);

             pElement = (CONSOLEELEMENT*)pElement->next;
        }
    }
}

void CONSOLE_ParseCommand(char * command)
{
    char  *cvar;
    char  *token;
    int i, numCvars;
    argc = 0;

    if( strlen(command) < 1)
    {
        return;
    }

    // Split the command into cvar and args
    cvar = strtok(command, " \r\n");

    for(i = 0; i < MAX_CVAR_ARGS; i++)
    {
        token = strtok(NULL, " ");

        if(!token)
        {
            break;
        }

        sprintf(argv[i], "%s", token);
        argc++;
    }

    // Cycle through our cvar list and look for this keyword
    numCvars = CVAR_GetNumCvarBindings();
    for(i = 0; i < numCvars; i++)
    {
        cvar_binding* binding = CVAR_GetCvarBinding(i);

        // Did we find it?
        if ( strcmpi(cvar, binding->name) == 0 )
        {
            binding->function(binding);
            break;
        }
    }

}


void CONSOLE_InsertUsedCommand(const char * szUsedCommand)
{
    //create a new element in the list, and add it to the front
    CONSOLEELEMENT *pElement = (CONSOLEELEMENT*)malloc(sizeof(CONSOLEELEMENT));
    if(pElement)
    {
        //Store our newly created member as the prev address
        if(NULL != console_used_command_list)
        {
            console_used_command_list->prev = pElement;
        }
        // Set the next pointer to the front of the list
        pElement->next = console_used_command_list;

        console_used_command_list_current = NULL;//pElement;

        // Prepend the entry. This entry is now the head of the list.
        console_used_command_list = pElement;

        // Make sure we NULL out the prev for our top level element
        pElement->prev = NULL;

        //sprintf(console_buffer->text, "%s", msg);
        memset(console_used_command_list->text, 0, MAX_CONSOLE_STRING_LENGTH);
        strncpy(console_used_command_list->text, szUsedCommand, MAX_CONSOLE_STRING_LENGTH-2);
    }
}

void CONSOLE_ClearUsedCommandList(void)
{
    CONSOLEELEMENT *pElement;
    CONSOLEELEMENT *pDelElement;

    // Free the list if it exists
    pElement = console_used_command_list;
    while(pElement)
    {
        pDelElement = pElement;
        pElement = (CONSOLEELEMENT*)pElement->next;

        free(pDelElement);
    }

    console_used_command_list = NULL;
    console_used_command_list_current = NULL;
}


void CONSOLE_RecalculateDirtyBuffer(void)
{
	int len;
	int l;

	console_cursor_pos = 0; //without spaces
	console_num_spaces = 0; //number of spac

	len = strlen(dirty_buffer);

	for(l = 0; l < len; ++l)
	{
		if(dirty_buffer[l] == ' ')
		{
			++console_num_spaces;
		}
		else
		{
			++console_cursor_pos;
		}
	}
}


void CONSOLE_Printf(const char  *newmsg, ...)
{
    CONSOLEELEMENT *pElement;
    va_list		argptr;
    char 		msg[512];//[MAX_CONSOLE_STRING_LENGTH];
    va_start (argptr,newmsg);
    vsprintf (msg, newmsg, argptr);
    va_end (argptr);

    //create a new element in the list, and add it to the front
    pElement = (CONSOLEELEMENT*)malloc(sizeof(CONSOLEELEMENT));
    if(pElement)
    {
        //Store our newly created member as the prev address
        if(NULL != console_buffer)
        {
            console_buffer->prev = pElement;
        }
        // Set the next pointer to the front of the list
        pElement->next = console_buffer;

        // Set our view, if they are at the bottom of the list.
        // Otherwise, if we set it everytime, Folks might lose
        // what they were looking for in the output, if they
        // were using pgup and pgdn to scroll through the entries.
        if(p_console_current_view == console_buffer)
        {
            p_console_current_view = pElement;
        }

        // Prepend the entry. This entry is now the head of the list.
        console_buffer = pElement;

        // Make sure we NULL out the prev for our top level element
        pElement->prev = NULL;

        //sprintf(console_buffer->text, "%s", msg);
        memset(console_buffer->text, 0, MAX_CONSOLE_STRING_LENGTH);
        strncpy(console_buffer->text, msg, MAX_CONSOLE_STRING_LENGTH-2);
    }

}

// Get the current number of args for this keyword
int CONSOLE_GetArgc(void)
{
    return argc;
}

// Get the current list of args for this keyword
char * CONSOLE_GetArgv(unsigned int var)
{
    return argv[var];
}

// Is our console showing?
int CONSOLE_IsActive(void)
{

    return nConsole_Active;
}

// Set our consople active or not.
void CONSOLE_SetActive(int i)
{
	nConsole_Active = (i == 0) ? 0 : 1;
}
