/*
===========================================================================
Copyright (C) 2008-2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

//
// DESCRIPTION:
// text commands typed in at the local console, or executed by a key binding

#include "cg_local.h"
#include "../fgame/bg_voteoptions.h"

void CG_TargetCommand_f(void);

/*
=================
CG_SizeUp_f

Keybinding command
=================
*/
static void CG_SizeUp_f(void)
{
    cgi.Cvar_Set("viewsize", va("%i", (int)(cg_viewsize->integer + 10)));
}

/*
=================
CG_SizeDown_f

Keybinding command
=================
*/
static void CG_SizeDown_f(void)
{
    cgi.Cvar_Set("viewsize", va("%i", (int)(cg_viewsize->integer - 10)));
}

/*
=============
CG_Viewpos_f

Debugging command to print the current position
=============
*/
static void CG_Viewpos_f(void)
{
    cgi.Printf(
        "(%i %i %i) : %i\n",
        (int)cg.refdef.vieworg[0],
        (int)cg.refdef.vieworg[1],
        (int)cg.refdef.vieworg[2],
        (int)cg.refdefViewAngles[YAW]
    );
}

void CG_SetDesiredObjectiveAlpha(float fAlpha)
{
    cg.ObjectivesDesiredAlpha = fAlpha;
    cg.ObjectivesAlphaTime    = (float)(cg.time + 250);
    cg.ObjectivesBaseAlpha    = cg.ObjectivesCurrentAlpha;
}

void CG_ScoresDown_f(void)
{
    if (cgs.gametype == GT_SINGLE_PLAYER) {
        if (!cg.scoresRequestTime) {
            cg.scoresRequestTime = cg.time;
            CG_SetDesiredObjectiveAlpha(1.0f);
        }

        return;
    }

    if (cg.scoresRequestTime + 2000 >= cg.time) {
        // send another request
        cg.showScores = qtrue;
        CG_PrepScoreBoardInfo();
        cgi.UI_ShowScoreBoard(cg.scoresMenuName);
        return;
    }

    cg.scoresRequestTime = cg.time;
    cgi.SendClientCommand("score");

    if (!cg.showScores) {
        // don't display anything until first score returns
        cg.showScores = qtrue;
        CG_PrepScoreBoardInfo();
        cgi.UI_ShowScoreBoard(cg.scoresMenuName);
    }
}

void CG_ScoresUp_f(void)
{
    if (cgs.gametype == GT_SINGLE_PLAYER) {
        if (cg.scoresRequestTime) {
            cg.scoresRequestTime = 0;
            CG_SetDesiredObjectiveAlpha(0.0f);
        }

        return;
    }

    if (!cg.showScores) {
        return;
    }

    cg.showScores = qfalse;
    cgi.UI_HideScoreBoard();
}

baseshader_t *CG_GetShaderUnderCrosshair(qboolean bVerbose, trace_t *pRetTrace)
{
    vec3_t        vPos, vEnd;
    vec3_t        axis[3];
    baseshader_t *pShader;
    trace_t       trace;

    AnglesToAxis(cg.refdefViewAngles, axis);
    vPos[0] = cg.refdef.vieworg[0];
    vPos[1] = cg.refdef.vieworg[1];
    vPos[2] = cg.refdef.vieworg[2];
    VectorMA(vPos, 4096.0, axis[0], vEnd);

    CG_Trace(&trace, vPos, vec3_origin, vec3_origin, vEnd, 0, MASK_SHOT, 0, 0, "CG_GetShaderUnderCrosshair");

    if (trace.startsolid || trace.fraction == 1.0) {
        return NULL;
    }

    if (bVerbose) {
        cgi.Printf("Surface hit at (%i %i %i)\n", (int)trace.endpos[0], (int)trace.endpos[1], (int)trace.endpos[2]);
    }

    pShader = cgi.GetShader(trace.shaderNum);
    if (pRetTrace) {
        *pRetTrace = trace;
    }

    return pShader;
}

static void CG_PrintContentTypes(int iContentFlags)
{
    if (iContentFlags & CONTENTS_SOLID) {
        cgi.Printf(" solid");
    }
    if (iContentFlags & CONTENTS_LAVA) {
        cgi.Printf(" lava");
    }
    if (iContentFlags & CONTENTS_SLIME) {
        cgi.Printf(" slime");
    }
    if (iContentFlags & CONTENTS_WATER) {
        cgi.Printf(" water");
    }
    if (iContentFlags & CONTENTS_FOG) {
        cgi.Printf(" fog");
    }
    if (iContentFlags & CONTENTS_FENCE) {
        cgi.Printf(" fence");
    }
    if (iContentFlags & CONTENTS_AREAPORTAL) {
        cgi.Printf(" areaportal");
    }
    if (iContentFlags & CONTENTS_PLAYERCLIP) {
        cgi.Printf(" playerclip");
    }
    if (iContentFlags & CONTENTS_VEHICLECLIP) {
        cgi.Printf(" vehicleclip");
    }
    if (iContentFlags & CONTENTS_MONSTERCLIP) {
        cgi.Printf(" monsterclip");
    }
    if (iContentFlags & CONTENTS_WEAPONCLIP) {
        cgi.Printf(" weaponclip");
    }
    if (iContentFlags & CONTENTS_SHOOTONLY) {
        cgi.Printf(" shootableonly");
    }
    if (iContentFlags & CONTENTS_ORIGIN) {
        cgi.Printf(" origin");
    }
    if (iContentFlags & CONTENTS_TRANSLUCENT) {
        cgi.Printf(" trans");
    }
}

static void CG_PrintSurfaceProperties(int iSurfaceFlags)
{
    if (iSurfaceFlags & SURF_NODAMAGE) {
        cgi.Printf(" nodamage");
    }
    if (iSurfaceFlags & SURF_SLICK) {
        cgi.Printf(" slick");
    }
    if (iSurfaceFlags & SURF_SKY) {
        cgi.Printf(" sky");
    }
    if (iSurfaceFlags & SURF_LADDER) {
        cgi.Printf(" ladder");
    }
    if (iSurfaceFlags & SURF_NOIMPACT) {
        cgi.Printf(" noimpact");
    }
    if (iSurfaceFlags & SURF_NOMARKS) {
        cgi.Printf(" nomarks");
    }
    if (iSurfaceFlags & SURF_CASTSHADOW) {
        cgi.Printf(" castshadow");
    }
    if (iSurfaceFlags & SURF_NODRAW) {
        cgi.Printf(" nodraw");
    }
    if (iSurfaceFlags & SURF_NOLIGHTMAP) {
        cgi.Printf(" nolightmap");
    }
    if (iSurfaceFlags & SURF_ALPHASHADOW) {
        cgi.Printf(" alphashadow");
    }
    if (iSurfaceFlags & SURF_NOSTEPS) {
        cgi.Printf(" nofootsteps");
    }
    if (iSurfaceFlags & SURF_NONSOLID) {
        cgi.Printf(" nonsolid");
    }
    if (iSurfaceFlags & SURF_OVERBRIGHT) {
        cgi.Printf(" overbright");
    }
    if (iSurfaceFlags & SURF_BACKSIDE) {
        cgi.Printf(" backside");
    }
    if (iSurfaceFlags & SURF_NODLIGHT) {
        cgi.Printf(" nodlight");
    }
    if (iSurfaceFlags & SURF_HINT) {
        cgi.Printf(" hint");
    }
    if (iSurfaceFlags & SURF_PATCH) {
        cgi.Printf(" patch");
    }
}

static void CG_PrintSurfaceType(int iSurfType)
{
    switch (iSurfType & MASK_SURF_TYPE) {
    case SURF_FOLIAGE:
        cgi.Printf("foliage");
        break;
    case SURF_SNOW:
        cgi.Printf("snow");
        break;
    case SURF_CARPET:
        cgi.Printf("carpet");
        break;
    case SURF_SAND:
        cgi.Printf("sand");
        break;
    case SURF_PUDDLE:
        cgi.Printf("puddle");
        break;
    case SURF_GLASS:
        cgi.Printf("glass");
        break;
    case SURF_GRAVEL:
        cgi.Printf("gravel");
        break;
    case SURF_MUD:
        cgi.Printf("mud");
        break;
    case SURF_DIRT:
        cgi.Printf("dirt");
        break;
    case SURF_GRILL:
        cgi.Printf("metal grill");
        break;
    case SURF_GRASS:
        cgi.Printf("grass");
        break;
    case SURF_ROCK:
        cgi.Printf("rock");
        break;
    case SURF_PAPER:
        cgi.Printf("paper");
        break;
    case SURF_WOOD:
        cgi.Printf("wood");
        break;
    case SURF_METAL:
        cgi.Printf("metal");
        break;
    default:
        cgi.Printf("!!*none specified*!!");
        break;
    }
}

void CG_GetCHShader(void)
{
    trace_t       trace;
    baseshader_t *pShader;

    pShader = CG_GetShaderUnderCrosshair(qtrue, &trace);
    cgi.Printf("\n");
    if (pShader) {
        if (pShader->surfaceFlags & SURF_SKY) {
            cgi.Printf("Hit the sky\n");
        } else {
            cgi.Printf("Shader: %s\n", pShader->shader);
            cgi.Printf("Shader Contents:");
            CG_PrintContentTypes(pShader->contentFlags);
            cgi.Printf("\n");
            cgi.Printf("Shader Surface Properties:");
            CG_PrintSurfaceProperties(pShader->surfaceFlags);
            cgi.Printf("\n");
            cgi.Printf("Shader Surfacetype: ");
            CG_PrintSurfaceType(pShader->surfaceFlags);
            cgi.Printf("\n");
            cgi.Printf("Trace Contents:");
            CG_PrintContentTypes(trace.contents);
            cgi.Printf("\n");
            cgi.Printf("Trace Surface Properties:");
            CG_PrintSurfaceProperties(trace.surfaceFlags);
            cgi.Printf("\n");
            cgi.Printf("Trace Surfacetype: ");
            CG_PrintSurfaceType(trace.surfaceFlags);
            cgi.Printf("\n\n");
        }
    } else {
        cgi.Printf("No surface selected\n");
    }
}

void CG_EditCHShader(void)
{
    char          name[1024];
    baseshader_t *pShader;

    pShader = CG_GetShaderUnderCrosshair(qfalse, NULL);
    if (pShader) {
        Q_strncpyz(name, "editspecificshader ", sizeof(name));
        Q_strcat(name, sizeof(name), pShader->shader);
        Q_strcat(name, sizeof(name), "\n");
        cgi.AddCommand(name);
    } else {
        cgi.Printf("No surface selected\n");
    }
}

#if 0


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

  MODEL TESTING

The viewthing and gun positioning tools from Q2 have been integrated and
enhanced into a single model testing facility.

Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>".

The names must be the full pathname after the basedir, like 
"models/weapons/v_launch/tris.md3" or "players/male/tris.md3"

Testmodel will create a fake entity 100 units in front of the current view
position, directly facing the viewer.  It will remain immobile, so you can
move around it to view it from different angles.

Testgun will cause the model to follow the player around and supress the real
view weapon model.  The default frame 0 of most guns is completely off screen,
so you will probably have to cycle a couple frames to see it.

"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
frame or skin of the testmodel.  These are bound to F5, F6, F7, and F8 in
q3default.cfg.

Note that none of the model testing features update while the game is paused, so
it may be convenient to test with deathmatch set to 1 so that bringing down the
console doesn't pause the game.

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

/*
=================
CG_TestModel_f

Creates an entity in front of the current position, which
can then be moved around
=================
*/
void CG_TestModel_f (void) {
	vec3_t		angles;

	memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) );
	if ( cgi.Argc() < 2 ) {
		return;
	}

	Q_strncpyz (cg.testModelName, cgi.Argv( 1 ), MAX_QPATH );
	cg.testModelEntity.hModel = cgi.R_RegisterModel( cg.testModelName );

	if ( cgi.Argc() == 3 ) {
		cg.testModelEntity.backlerp = atof( cgi.Argv( 2 ) );
		cg.testModelEntity.frame = 1;
		cg.testModelEntity.oldframe = 0;
	}
	if (! cg.testModelEntity.hModel ) {
		cgi.Printf( "Can't register model\n" );
		return;
	}

	VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin );

	angles[PITCH] = 0;
	angles[YAW] = 180 + cg.refdefViewAngles[1];
	angles[ROLL] = 0;

	AnglesToAxis( angles, cg.testModelEntity.axis );
	cg.testGun = qfalse;
}

void CG_TestModelNextFrame_f (void) {
	cg.testModelEntity.frame++;
	cgi.Printf( "frame %i\n", cg.testModelEntity.frame );
}

void CG_TestModelPrevFrame_f (void) {
	cg.testModelEntity.frame--;
	cgi.Printf( "frame %i\n", cg.testModelEntity.frame );
}

void CG_TestModelNextSkin_f (void) {
	cg.testModelEntity.skinNum++;
	cgi.Printf( "skin %i\n", cg.testModelEntity.skinNum );
}

void CG_TestModelPrevSkin_f (void) {
	cg.testModelEntity.skinNum--;
	if ( cg.testModelEntity.skinNum < 0 ) {
		cg.testModelEntity.skinNum = 0;
	}
	cgi.Printf( "skin %i\n", cg.testModelEntity.skinNum );
}

void CG_AddTestModel (void) {
	// re-register the model, because the level may have changed
	cg.testModelEntity.hModel = cgi.R_RegisterModel( cg.testModelName );
	if (! cg.testModelEntity.hModel ) {
		cgi.Printf ("Can't register model\n");
		return;
	}
	cgi.R_AddRefEntityToScene( &cg.testModelEntity );
}

#endif

typedef struct {
    char *cmd;
    void (*function)(void);
} consoleCommand_t;

static consoleCommand_t commands[] = {
    {"useweaponclass",         &CG_UseWeaponClass_f        },
    {"weapnext",               &CG_NextWeapon_f            },
    {"weapprev",               &CG_PrevWeapon_f            },
    {"uselast",                &CG_UseLastWeapon_f         },
    {"holster",                &CG_HolsterWeapon_f         },
    {"weapdrop",               &CG_DropWeapon_f            },
    {"toggleitem",             &CG_ToggleItem_f            },
    {"+scores",                &CG_ScoresDown_f            },
    {"-scores",                &CG_ScoresUp_f              },
    {"viewpos",                &CG_Viewpos_f               },
    {"sizeup",                 &CG_SizeUp_f                },
    {"sizedown",               &CG_SizeDown_f              },
    {"cg_eventlist",           &CG_EventList_f             },
    {"cg_eventhelp",           &CG_EventHelp_f             },
    {"cg_dumpevents",          &CG_DumpEventHelp_f         },
    {"cg_pendingevents",       &CG_PendingEvents_f         },
    {"cg_classlist",           &CG_ClassList_f             },
    {"cg_classtree",           &CG_ClassTree_f             },
    {"cg_classevents",         &CG_ClassEvents_f           },
    {"cg_dumpclassevents",     &CG_DumpClassEvents_f       },
    {"cg_dumpallclasses",      &CG_DumpAllClasses_f        },
    {"testemitter",            &CG_TestEmitter_f           },
    {"triggertestemitter",     &CG_TriggerTestEmitter_f    },
    {"prevemittercommand",     &CG_PrevEmitterCommand_f    },
    {"nextemittercommand",     &CG_NextEmitterCommand_f    },
    {"newemittercommand",      &CG_NewEmitterCommand_f     },
    {"deleteemittercommand",   &CG_DeleteEmitterCommand_f  },
    {"dumpemitter",            &CG_DumpEmitter_f           },
    {"loademitter",            &CG_LoadEmitter_f           },
    {"resetvss",               &CG_ResetVSSSources         },
    {"getchshader",            &CG_GetCHShader             },
    {"editchshader",           &CG_EditCHShader            },
    {"messagemode",            &CG_MessageMode_f           },
    {"messagemode_all",        &CG_MessageMode_All_f       },
    {"messagemode_team",       &CG_MessageMode_Team_f      },
    {"messagemode_private",    &CG_MessageMode_Private_f   },
    {"say",                    &CG_MessageSingleAll_f      },
    {"sayteam",                &CG_MessageSingleTeam_f     },
    {"teamsay",                &CG_MessageSingleTeam_f     },
    {"sayprivate",             &CG_MessageSingleClient_f   },
    {"sayone",                 &CG_MessageSingleClient_f   },
    {"wisper",                 &CG_MessageSingleClient_f   },
    {"instamsg_main",          &CG_InstaMessageMain_f      },
    {"instamsg_group_a",       &CG_InstaMessageGroupA_f    },
    {"instamsg_group_b",       &CG_InstaMessageGroupB_f    },
    {"instamsg_group_c",       &CG_InstaMessageGroupC_f    },
    {"instamsg_group_d",       &CG_InstaMessageGroupD_f    },
    {"instamsg_group_e",       &CG_InstaMessageGroupE_f    },
    {"pushmenu_teamselect",    &CG_PushMenuTeamSelect_f    },
    {"pushmenu_weaponselect",  &CG_PushMenuWeaponSelect_f  },
    // Added in 2.0
    {"pushcallvote",           &CG_PushCallVote_f          },
    {"pushcallvotesublist",    &CG_PushCallVoteSubList_f   },
    {"pushcallvotesubtext",    &CG_PushCallVoteSubText_f   },
    {"pushcallvotesubinteger", &CG_PushCallVoteSubInteger_f},
    {"pushcallvotesubfloat",   &CG_PushCallVoteSubFloat_f  },
    {"pushcallvotesubclient",  &CG_PushCallVoteSubClient_f },
    {"pushvote",               &CG_PushVote_f              },
    {"callentryvote",          &CG_CallEntryVote_f         },
};

/*
=================
CG_ConsoleCommand

The string has been tokenized and can be retrieved with
Cmd_Argc() / Cmd_Argv()
=================
*/
qboolean CG_ConsoleCommand(void)
{
    const char *cmd;
    int         i;

    cmd = cgi.Argv(0);

    for (i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
        if (!Q_stricmp(cmd, commands[i].cmd)) {
            commands[i].function();
            return qtrue;
        }
    }

    return qfalse;
}

/*
=================
CG_InitConsoleCommands

Let the client system know about all of our commands
so it can perform tab completion
=================
*/
void CG_InitConsoleCommands(void)
{
    int i;

    for (i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
        cgi.AddCommand(commands[i].cmd);
    }

    cgi.AddCommand("callvote");
    cgi.AddCommand("vote");
}

void CG_Mapinfo_f(void)
{
    cgi.Printf("---------------------\n");
    cgi.R_PrintBSPFileSizes();
    cgi.CM_PrintBSPFileSizes();
    cgi.Printf("---------------------\n");
}

void CG_PushMenuTeamSelect_f(void)
{
    if (cgs.gametype == GT_SINGLE_PLAYER) {
        return;
    }

    cgi.Cmd_Execute(EXEC_NOW, "ui_getplayermodel\n");
    switch (cgs.gametype) {
    case GT_FFA:
        cgi.Cmd_Execute(EXEC_NOW, "pushmenu SelectFFAModel\n");
        break;
    case GT_OBJECTIVE:
        cgi.Cmd_Execute(EXEC_NOW, "pushmenu ObjSelectTeam\n");
        break;
    default:
        cgi.Cmd_Execute(EXEC_NOW, "pushmenu SelectTeam\n");
        break;
    }
}

void CG_PushMenuWeaponSelect_f(void)
{
    if (cgs.gametype == GT_SINGLE_PLAYER) {
        return;
    }

    cgi.Cmd_Execute(EXEC_NOW, "pushmenu SelectPrimaryWeapon\n");
}

void CG_UseWeaponClass_f(void)
{
    const char *cmd;

    cmd = cgi.Argv(1);

    if (!Q_stricmp(cmd, "pistol")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_PISTOL;
    } else if (!Q_stricmp(cmd, "rifle")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_RIFLE;
    } else if (!Q_stricmp(cmd, "smg")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_SMG;
    } else if (!Q_stricmp(cmd, "mg")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_MG;
    } else if (!Q_stricmp(cmd, "grenade")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_GRENADE;
    } else if (!Q_stricmp(cmd, "heavy")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_HEAVY;
    } else if (!Q_stricmp(cmd, "item1") || !Q_stricmp(cmd, "item")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_ITEM1;
    } else if (!Q_stricmp(cmd, "item2")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_ITEM2;
    } else if (!Q_stricmp(cmd, "item3")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_ITEM3;
    } else if (!Q_stricmp(cmd, "item4")) {
        cg.iWeaponCommand = WEAPON_COMMAND_USE_ITEM4;
    }

    cg.iWeaponCommandSend = 0;
}

void CG_NextWeapon_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_USE_NEXT_WEAPON;
    cg.iWeaponCommandSend = 0;
}

void CG_PrevWeapon_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_USE_PREV_WEAPON;
    cg.iWeaponCommandSend = 0;
}

void CG_UseLastWeapon_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_USE_LAST_WEAPON;
    cg.iWeaponCommandSend = 0;
}

void CG_HolsterWeapon_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_HOLSTER;
    cg.iWeaponCommandSend = 0;
}

void CG_DropWeapon_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_DROP;
    cg.iWeaponCommandSend = 0;
}

void CG_ToggleItem_f(void)
{
    cg.iWeaponCommand     = WEAPON_COMMAND_USE_ITEM1;
    cg.iWeaponCommandSend = 0;
}

int CG_WeaponCommandButtonBits(void)
{
    int iShiftedWeaponCommand;

    if (!cg.iWeaponCommand) {
        return 0;
    }

    iShiftedWeaponCommand = cg.iWeaponCommand << 7;

    cg.iWeaponCommandSend++;
    if (cg.iWeaponCommandSend > 2) {
        cg.iWeaponCommand = 0;
    }

    return iShiftedWeaponCommand & GetWeaponCommandMask(cg_protocol >= PROTOCOL_MOHTA_MIN ? WEAPON_COMMAND_MAX_VER17 : WEAPON_COMMAND_MAX_VER6);
}
