/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

void Cmd_ForwardToServer (void);

typedef boost::unordered_map<std::string, std::string> alias_map;
static alias_map cmd_alias;
qboolean cmd_wait;

//=============================================================================

/*
============
Cmd_Wait_f

Causes execution of the remainder of the command buffer to be delayed until
next frame.  This allows commands like:
bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
============
*/
void Cmd_Wait_f (void)
{
    cmd_wait = true;
}

/*
=============================================================================

                        COMMAND BUFFER

=============================================================================
*/

sizebuf_t    cmd_text;

/*
============
Cbuf_Init
============
*/
void Cbuf_Init (void)
{
    SZ_Alloc (&cmd_text, 8192);        // space for commands and script files
}


/*
============
Cbuf_AddText

Adds command text at the end of the buffer
============
*/
void Cbuf_AddText (const std::string &text)
{
    size_t l = text.length();

    if (cmd_text.cursize + (int)l >= cmd_text.maxsize)
    {
        Con_Printf ("Cbuf_AddText: overflow\n");
        return;
    }

    SZ_Write (&cmd_text, text.c_str(), l);
}


/*
============
Cbuf_InsertText

Adds command text immediately after the current command
Adds a \n to the text
FIXME: actually change the command buffer to do less copying
============
*/
void Cbuf_InsertText (const std::string &text)
{
    std::vector<uint8_t> temp;

    // copy off any commands still remaining in the exec buffer
    if (cmd_text.cursize)
    {
        temp.resize(cmd_text.cursize);
        memcpy(&temp[0], cmd_text.data, cmd_text.cursize);
        SZ_Clear(&cmd_text);
    }

    // add the entire text of the file
    Cbuf_AddText (text);

    // add the copied off data
    if (temp.size())
        SZ_Write(&cmd_text, &temp[0], temp.size());
}

/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute (void)
{
    int        i;
    char    *text;
    char    line[1024];
    int        quotes;

    while (cmd_text.cursize)
    {
// find a \n or ; line break
        text = (char *)cmd_text.data;

        quotes = 0;
        for (i=0 ; i< cmd_text.cursize ; i++)
        {
            if (text[i] == '"')
                quotes++;
            if ( !(quotes&1) &&  text[i] == ';')
                break;    // don't break if inside a quoted string
            if (text[i] == '\n')
                break;
        }


        memcpy (line, text, i);
        line[i] = 0;

        // delete the text from the command buffer and move remaining commands down
        // this is necessary because commands (exec, alias) can insert data at the
        // beginning of the text buffer
        if (i == cmd_text.cursize)
            cmd_text.cursize = 0;
        else
        {
            i++;
            cmd_text.cursize -= i;
            memcpy (text, text+i, cmd_text.cursize);
        }

        // execute the command line
        Cmd_ExecuteString (line, src_command);

        if (cmd_wait)
        {    // skip out while text still remains in buffer, leaving it
            // for next frame
            cmd_wait = false;
            break;
        }
    }
}

/*
==============================================================================

                        SCRIPT COMMANDS

==============================================================================
*/

/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f (void)
{
    if (Cmd_Argc () != 2)
    {
        Con_Printf ("exec <filename> : execute a script file\n");
        return;
    }
    
    File fp;
    if (!fp.OpenRead(Cmd_Argv(1)))
    {
        Con_Printf("couldn't exec %s, cannot open file\n", Cmd_Argv(1).c_str());
        return;
    }

    char *f = (char *)fp.Read();
    if (!f)
    {
        Con_Printf ("couldn't exec %s, cannot read file\n", Cmd_Argv(1).c_str());
        return;
    }
    Con_Printf ("execing %s\n", Cmd_Argv(1).c_str());
    
    Cbuf_InsertText (f);
    
    delete[] f;
    fp.Close();
}


/*
===============
Cmd_Echo_f

Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f (void)
{
    for (size_t i = 1 ; i < Cmd_Argc() ; i++)
        Con_Printf("%s ", Cmd_Argv(i).c_str());
    Con_Printf("\n");
}

/*
===============
Cmd_Alias_f

Creates a new command that executes a command string (possibly ; seperated)
===============
*/
void Cmd_Alias_f (void)
{
    if (Cmd_Argc() == 1)
    {
        Con_Printf ("Current alias commands:\n");
        for (alias_map::iterator iter = cmd_alias.begin() ; 
             iter != cmd_alias.end() ; ++iter)
            Con_Printf ("%s : %s\n", (*iter).first.c_str(), (*iter).second.c_str());
        return;
    }

    std::string name(Cmd_Argv(1));
    boost::to_lower(name);

    // copy the rest of the command line
    int c = Cmd_Argc();
    std::string value;
    for (int i = 2 ; i < c ; i++)
    {
        value += Cmd_Argv(i);
        if (i != c)
            value += " ";
    }
    value += "\n";
    
    cmd_alias[name] = value;
}

/*
=============================================================================

                    COMMAND EXECUTION

=============================================================================
*/

typedef boost::unordered_map<std::string, xcommand_t> function_map;
static function_map cmd_functions;

static std::vector<std::string> cmd_argv;
static std::string cmd_args;

cmd_source_t    cmd_source;

/*
============
Cmd_Init
============
*/
void Cmd_Init (void)
{
//
// register our commands
//
    Cmd_AddCommand("exec", Cmd_Exec_f);
    Cmd_AddCommand("echo", Cmd_Echo_f);
    Cmd_AddCommand("alias", Cmd_Alias_f);
    Cmd_AddCommand("cmd", Cmd_ForwardToServer);
    Cmd_AddCommand("wait", Cmd_Wait_f);
}

/*
============
Cmd_Argc
============
*/
size_t Cmd_Argc()
{
    return cmd_argv.size();
}

/*
============
Cmd_Argv
============
*/
std::string Cmd_Argv(size_t arg)
{
    if (arg < 0 || arg >= cmd_argv.size())
        return std::string();
    return cmd_argv[arg];
}

/*
============
Cmd_Args
============
*/
std::string Cmd_Args()
{
    return cmd_args;
}


/*
============
Cmd_TokenizeString

Parses the given string into command line tokens.
============
*/
void Cmd_TokenizeString (char *text)
{
    // clear the args from the last string
    cmd_argv.clear();
    cmd_args.erase();

    while (1)
    {
        // skip whitespace up to a /n
        while (*text && *text <= ' ' && *text != '\n')
            text++;

        if (*text == '\n')
        {
            // a newline seperates commands in the buffer
            text++;
            break;
        }

        if (!*text)
            return;

        if (cmd_argv.size() == 1)
            cmd_args = text;

        text = COM_Parse (text);
        if (!text)
            return;

        std::string str(com_token);
        cmd_argv.push_back(str);
    }
}


/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand(const std::string &cmd_name, xcommand_t function)
{
    cmd_functions[cmd_name] = function;
}



/*
============
Cmd_CompleteCommand
============
*/
std::string Cmd_CompleteCommand(const std::string &partial)
{
    if (!partial.length())
        return std::string();

    // check functions
    for (function_map::iterator iter = cmd_functions.begin() ;
         iter != cmd_functions.end() ; ++iter)
    {
        if (!(*iter).first.compare(0, partial.length(), partial))
            return (*iter).first;
    }

    return std::string();
}

/*
============
Cmd_ExecuteString

A complete command line has been parsed, so try to execute it
FIXME: lookupnoadd the token to speed search?
============
*/
void Cmd_ExecuteString (char *text, cmd_source_t src)
{
    cmd_source = src;
    Cmd_TokenizeString (text);

// execute the command line
    if (!Cmd_Argc())
        return;        // no tokens
    
    std::string lower = cmd_argv[0];
    boost::to_lower(lower);
    
    // Try to call the command signal, see if it works
    if (service::ConsoleCommand(cmd_argv[0]))
        return;

    // check functions
    for (function_map::iterator iter = cmd_functions.begin() ; 
         iter != cmd_functions.end() ; ++iter)
    {
        if (boost::iequals((*iter).first, cmd_argv[0]))
        {
            (*iter).second();
            return;
        }
    }

    // check aliases
    for (alias_map::iterator iter = cmd_alias.begin() ; 
         iter != cmd_alias.end() ; ++iter)
    {
        if ((*iter).first == lower)
        {
            Cbuf_InsertText((*iter).second);
            return;
        }
    }

    // check cvars
    if (Cvar_Command())
        return;

    Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0).c_str());
}


/*
===================
Cmd_ForwardToServer

Sends the entire command line over to the server
===================
*/
void Cmd_ForwardToServer (void)
{
    if (cls.state != ca_connected)
    {
        Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0).c_str());
        return;
    }

    if (cls.demoplayback)
        return;        // not really connected

    MSG_WriteByte (&cls.message, clc_stringcmd);
    if (!boost::iequals(Cmd_Argv(0), "cmd"))
    {
        SZ_Print (&cls.message, Cmd_Argv(0));
        SZ_Print (&cls.message, " ");
    }
    if (Cmd_Argc() > 1)
        SZ_Print (&cls.message, Cmd_Args());
    else
        SZ_Print (&cls.message, "\n");
}

