/* file command.c
 * Main routine and initialization for the application
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */



#include "command.h"
#include "common.h"
#include "cselect.h"
#include "cundo.h"
#include "draw.h"
#include "fileio.h"
#include "track.h"
#include "common-ui.h"
#include "menu.h"
#include "include/toolbar.h"

/*****************************************************************************
 *
 * COMMAND
 *
 */

#define COMMAND_MAX (250)

static struct {
	procCommand_t cmdProc;
	char * helpKey;
	wIndex_t buttInx;
	char * labelStr;
	wIcon_p icon;
	int reqLevel;
	wBool_t enabled;
	long options;
	long stickyMask;
	long acclKey;
	wMenuPush_p menu[NUM_CMDMENUS];
	void * context;
} commandList[COMMAND_MAX];


EXPORT int commandCnt = 0;

static wIndex_t curCommand = 0;

static int log_command;


EXPORT long preSelect = 0;	/**< default command 0 = Describe 1 = Select */
EXPORT long rightClickMode = 0;
EXPORT void * commandContext;
EXPORT coOrd cmdMenuPos;

/*--------------------------------------------------------------------*/
EXPORT const char* GetCurCommandName()
{
	return commandList[curCommand].helpKey;
}

/**
 * Decide whether command is available in the current application mode.
 * Basically track modifications are not available in Train Mode, file
 * operations are always available and train control ops are available in
 * train mode only.
 * The specific logic was developed with the help of Wolfram Alpha:
 * CNF | ((NOT m) OR o OR t) AND(m OR (NOT o) OR t)
 *
 * \param mode		application mode
 * \param options	availability options
 * \return			true for enabled, false if disabled
 */

EXPORT bool IsCommandEnabled(long mode, long options)
{
	/*
	       	if (((mode == MODE_DESIGN) || (options & IC_MODETRAIN_ONLY) ||
		     (options & IC_MODETRAIN_TOO)) &&
		    ((mode == MODE_TRAIN) || !(options & IC_MODETRAIN_ONLY)) ||
		    (options & IC_MODETRAIN_TOO)) {
	*/
	if (
	        ((mode == MODE_DESIGN) ||
	         (options & IC_MODETRAIN_ONLY) ||
	         (options & IC_MODETRAIN_TOO))
	        &&
	        ((mode == MODE_TRAIN) ||
	         !(options & IC_MODETRAIN_ONLY) ||
	         (options & IC_MODETRAIN_TOO))
	) {
		return true;
	}

	return false;
}

EXPORT void EnableCommands(void)
{
	int inx, minx;
	wBool_t enable;

	LOG(log_command, 5,
	    ( "COMMAND enable S%d M%d\n", selectedTrackCount, programMode ))
	for (inx = 0; inx < commandCnt; inx++) {
		if (commandList[inx].buttInx) {
			if ((commandList[inx].options & IC_SELECTED)
			    && selectedTrackCount <= 0) {
				enable = FALSE;
			} else if (IsCommandEnabled(programMode, commandList[inx].options )) {
				enable = TRUE;
			} else {
				enable = FALSE;
			}
			if (commandList[inx].enabled != enable) {
				if (commandList[inx].buttInx >= 0) {
					ToolbarButtonEnable(commandList[inx].buttInx, enable );
				}
				for (minx = 0; minx < NUM_CMDMENUS; minx++)
					if (commandList[inx].menu[minx]) {
						wMenuPushEnable(commandList[inx].menu[minx], enable);
					}
				commandList[inx].enabled = enable;
			}
		}
	}

	EnableMenus();

	ToolbarButtonEnableIfSelect(selectedTrackCount > 0);
}

EXPORT wIndex_t GetCurrentCommand()
{
	return curCommand;
}

EXPORT void Reset(void)
{
	if (recordF) {
		fprintf(recordF, "RESET\n");
		fflush(recordF);
	}
	LOG(log_command, 2,
	    ( "COMMAND CANCEL %s\n", commandList[curCommand].helpKey ))
	commandList[curCommand].cmdProc( C_CANCEL, zero);
	if (commandList[curCommand].buttInx >= 0) {
		ToolbarButtonBusy(commandList[curCommand].buttInx, FALSE);
	}
	curCommand = (preSelect ? selectCmdInx : describeCmdInx);
	wSetCursor(mainD.d, preSelect ? defaultCursor : wCursorQuestion);
	commandContext = commandList[curCommand].context;
	if (commandList[curCommand].buttInx >= 0) {
		ToolbarButtonBusy(commandList[curCommand].buttInx, TRUE);
	}

	DYNARR_RESET( trkSeg_t, tempSegs_da );

	TryCheckPoint();

	ClrAllTrkBits( TB_UNDRAWN );
	DoRedraw(); // Reset
	EnableCommands();
	ResetMouseState();
	LOG(log_command, 1,
	    ( "COMMAND RESET %s\n", commandList[curCommand].helpKey ))
	(void) commandList[curCommand].cmdProc( C_START, zero);
}

static BOOL_T CheckClick(wAction_t *action, coOrd *pos, BOOL_T checkLeft,
                         BOOL_T checkRight)
{
	static long time0;
	static coOrd pos0;
	long time1;
	long timeDelta;
	DIST_T distDelta;

	switch (*action) {
	case C_LDOUBLE:
		if (!checkLeft) {
			return TRUE;
		}
		time0 = 0;
		break;
	case C_DOWN:
		if (!checkLeft) {
			return TRUE;
		}
		time0 = wGetTimer() - adjTimer;
		pos0 = *pos;
		return FALSE;
	case C_MOVE:
		if (!checkLeft) {
			return TRUE;
		}
		if (time0 != 0) {
			time1 = wGetTimer() - adjTimer;
			timeDelta = time1 - time0;
			distDelta = FindDistance(*pos, pos0);
			if (timeDelta > dragTimeout || distDelta > dragDistance) {
				time0 = 0;
				*pos = pos0;
				*action = C_DOWN;
			} else {
				return FALSE;
			}
		}
		break;
	case C_UP:
		if (!checkLeft) {
			return TRUE;
		}
		if (time0 != 0) {
			time1 = wGetTimer() - adjTimer;
			timeDelta = time1 - time0;
			distDelta = FindDistance(*pos, pos0);
			time0 = 0;
			*action = C_LCLICK;
		}
		break;
	case C_RDOWN:
		if (!checkRight) {
			return TRUE;
		}
		time0 = wGetTimer() - adjTimer;
		pos0 = *pos;
		return FALSE;
	case C_RMOVE:
		if (!checkRight) {
			return TRUE;
		}
		if (time0 != 0) {
			time1 = wGetTimer() - adjTimer;
			timeDelta = time1 - time0;
			distDelta = FindDistance(*pos, pos0);
			if (timeDelta > dragTimeout || distDelta > dragDistance) {
				time0 = 0;
				*pos = pos0;
				*action = C_RDOWN;
			} else {
				return FALSE;
			}
		}
		break;
	case C_RUP:
		if (!checkRight) {
			return TRUE;
		}
		if (time0 != 0) {
			time0 = 0;
			*action = C_RCLICK;
		}
		break;
	}
	return TRUE;
}

EXPORT wBool_t DoCurCommand(wAction_t action, coOrd pos)
{
	wAction_t rc;
	int mode;
	wBool_t bExit = FALSE;

	if (action == wActionMove) {
		if ((commandList[curCommand].options & IC_WANT_MOVE) == 0) {
			bExit = TRUE;
		}
	} else if ((action&0xFF) == wActionModKey) {
		if ((commandList[curCommand].options & IC_WANT_MODKEYS) == 0) {
			bExit = TRUE;
		}
	} else if (!CheckClick(&action, &pos,
	                       (int) (commandList[curCommand].options & IC_LCLICK), TRUE)) {
		bExit = TRUE;
	} else if (action == C_RCLICK
	           && (commandList[curCommand].options & IC_RCLICK) == 0) {
		if (!inPlayback) {
			mode = MyGetKeyState();
			if ((mode & (~WKEY_SHIFT)) != 0) {
				wBeep();
				bExit = TRUE;
			} else if (((mode & WKEY_SHIFT) == 0) == (rightClickMode == 0)) {
				if (selectedTrackCount > 0) {
					if (commandList[curCommand].options & IC_CMDMENU) {
					}
					wMenuPopupShow(popup2M);
				} else {
					wMenuPopupShow(popup1M);
				}
				bExit = TRUE;
			} else if ((commandList[curCommand].options & IC_CMDMENU)) {
				cmdMenuPos = pos;
				action = C_CMDMENU;
			} else {
				wBeep();
				bExit = TRUE;
			}
		} else {
			bExit = TRUE;
		}
	}
	if ( bExit ) {
		TempRedraw(); // DoCurCommand: precommand
		return C_CONTINUE;
	}

	LOG(log_command, 2,
	    ( "COMMAND MOUSE %s %d @ %0.3f %0.3f\n", commandList[curCommand].helpKey,
	      (int)action, pos.x, pos.y ))
	rc = commandList[curCommand].cmdProc(action, pos);
	LOG(log_command, 4, ( "    COMMAND returns %d\n", rc ))
	switch ( action & 0xFF ) {
	case wActionMove:
	case wActionModKey:
	case C_DOWN:
	case C_MOVE:
	case C_UP:
	case C_RDOWN:
	case C_RMOVE:
	case C_RUP:
	case C_LCLICK:
	case C_RCLICK:
	case C_TEXT:
	case C_OK:
		if (rc== C_TERMINATE) { MainRedraw(); }
		else { TempRedraw(); } // DoCurCommand: postcommand
		break;
	default:
		break;
	}
	if ((rc == C_TERMINATE )
	    && (commandList[curCommand].options & IC_STICKY)
	    && (commandList[curCommand].stickyMask & stickySet)) {
		DYNARR_RESET( trkSeg_t, tempSegs_da );
		UpdateAllElevations();
		if (commandList[curCommand].options & IC_NORESTART) {
			return C_CONTINUE;
		}
		//Make sure we checkpoint even sticky commands
		TryCheckPoint();
		LOG(log_command, 1,
		    ( "COMMAND START %s\n", commandList[curCommand].helpKey ))
		wSetCursor(mainD.d,defaultCursor);
		rc = commandList[curCommand].cmdProc( C_START, pos);
		LOG(log_command, 4, ( "    COMMAND returns %d\n", rc ))
		switch (rc) {
		case C_CONTINUE:
			break;
		case C_ERROR:
			Reset();
#ifdef VERBOSE
			lprintf( "Start returns Error");
#endif
			break;
		case C_TERMINATE:
			InfoMessage("");
			Reset();
			break;
		}
	}
	return rc;
}

/*
 * \parm reset says if the user used Esc rather than undo/redo
 */
EXPORT int ConfirmReset(BOOL_T retry)
{
	wAction_t rc;
	if (curCommand != describeCmdInx) {
		LOG(log_command, 3,
		    ( "COMMAND CONFIRM %s\n", commandList[curCommand].helpKey ))
		rc = commandList[curCommand].cmdProc( C_CONFIRM, zero);
		LOG(log_command, 4, ( "    COMMAND returns %d\n", rc ))
		if (rc == C_ERROR) {
			if (retry)
				rc =
				        wNotice3(
				                _(
				                        "Cancelling the current command will undo the changes\n"
				                        "you are currently making. Do you want to do the update instead?"),
				                _("Yes"), _("No"), _("Cancel"));
			else
				rc =
				        wNoticeEx( NT_WARNING,
				                   _(
				                           "Cancelling the current command will undo the changes\n"
				                           "you are currently making. Do you want to do the update instead?"),
				                   _("Yes"), _("No"));
			if (rc == 1) {
				LOG(log_command, 3,
				    ( "COMMAND OK %s\n", commandList[curCommand].helpKey ))
				commandList[curCommand].cmdProc( C_OK, zero);
				return C_OK;
			} else if (rc == -1) {
				return C_ERROR;
			}
		} else if (rc == C_TERMINATE) {
			return C_TERMINATE;
		}
	}
	if (retry) {
		/* because user pressed esc */
		SetAllTrackSelect( FALSE);
	}
	Reset();
	LOG(log_command, 1,
	    ( "COMMAND RESET %s\n", commandList[curCommand].helpKey ))
	commandList[curCommand].cmdProc( C_START, zero);
	return C_CONTINUE;
}

EXPORT void DoCommandB(void * data)
{
	wIndex_t inx = (wIndex_t)VP2L(data);
	STATUS_T rc;
	static coOrd pos = { 0, 0 };
	static int inDoCommandB = FALSE;

	if (inDoCommandB) {
		return;
	}
	inDoCommandB = TRUE;

	if (inx < 0 || inx >= commandCnt) {
		CHECK(FALSE);
		inDoCommandB = FALSE;
		return;
	}

	if ((!inPlayback) && (!commandList[inx].enabled)) {
		ErrorMessage(MSG_COMMAND_DISABLED);
		inx = describeCmdInx;
	}

	InfoMessage("");
	if (curCommand != selectCmdInx) {
		LOG(log_command, 3,
		    ( "COMMAND FINISH %s\n", commandList[curCommand].helpKey ))
		rc = commandList[curCommand].cmdProc( C_FINISH, zero);
		LOG(log_command, 3,
		    ( "COMMAND CONFIRM %s\n", commandList[curCommand].helpKey ))
		rc = commandList[curCommand].cmdProc( C_CONFIRM, zero);
		LOG(log_command, 4, ( "    COMMAND returns %d\n", rc ))
		if (rc == C_ERROR) {
			rc = wNotice3(
			             _("Cancelling the current command will undo the changes\n"
			               "you are currently making. Do you want to update?"),
			             _("Yes"), _("No"), _("Cancel"));
			if (rc == 1) {
				commandList[curCommand].cmdProc( C_OK, zero);
			} else if (rc == -1) {
				inDoCommandB = FALSE;
				return;
			}
		}
		LOG(log_command, 3,
		    ( "COMMAND CANCEL %s\n", commandList[curCommand].helpKey ))
		commandList[curCommand].cmdProc( C_CANCEL, pos);
		DYNARR_RESET( trkSeg_t, tempSegs_da );
	} else {
		LOG(log_command, 3,
		    ( "COMMAND FINISH %s\n", commandList[curCommand].helpKey ))
		rc = commandList[curCommand].cmdProc( C_FINISH, zero);
	}
	if (commandList[curCommand].buttInx >= 0) {
		ToolbarButtonBusy(commandList[curCommand].buttInx, FALSE);
	}

	if (recordF) {
		fprintf(recordF, "COMMAND %s\n", commandList[inx].helpKey + 3);
		fflush(recordF);
	}

	curCommand = inx;
	commandContext = commandList[curCommand].context;
	// update the toolbar icon when a sub-command is selected (eg. circle
	// vs. filled circle)

	if (commandList[curCommand].buttInx >= 0) {
		ToolbarUpdateButton(commandList[curCommand].buttInx,
		                    curCommand, (char *)commandList[curCommand].icon,
		                    commandList[curCommand].helpKey, I2VP(curCommand));
		ToolbarButtonBusy(commandList[curCommand].buttInx, TRUE);
	}

	LOG(log_command, 1,
	    ("COMMAND START %s\n", commandList[curCommand].helpKey));
	wSetCursor(mainD.d,defaultCursor);
	rc = commandList[curCommand].cmdProc( C_START, pos);
	LOG(log_command, 4, ( "    COMMAND returns %d\n", rc ))
	TempRedraw(); // DoCommandB
	switch (rc) {
	case C_CONTINUE:
		break;
	case C_ERROR:
		Reset();
#ifdef VERBOSE
		lprintf( "Start returns Error");
#endif
		break;
	case C_TERMINATE:
		if (rc == C_TERMINATE) {
			InfoMessage("");
		}
		Reset();
		break;
	}
	inDoCommandB = FALSE;
}


/***************************************************************************
 *
 *
 *
 */

EXPORT BOOL_T CommandEnabled(wIndex_t cmdInx)
{
	return commandList[cmdInx].enabled;
}


EXPORT wIndex_t AddCommand(procCommand_t cmdProc, const char * helpKey,
                           const char * nameStr, wIcon_p icon, int reqLevel, long options, long acclKey,
                           wIndex_t buttInx, long stickyMask, wMenuPush_p cmdMenus[NUM_CMDMENUS],
                           void * context)
{
	CHECK( commandCnt < COMMAND_MAX - 1 );
	commandList[commandCnt].labelStr = MyStrdup(nameStr);
	commandList[commandCnt].helpKey = MyStrdup(helpKey);
	commandList[commandCnt].cmdProc = cmdProc;
	commandList[commandCnt].icon = icon;
	commandList[commandCnt].reqLevel = reqLevel;
	commandList[commandCnt].enabled = TRUE;
	commandList[commandCnt].options = options;
	commandList[commandCnt].acclKey = acclKey;
	commandList[commandCnt].context = context;
	commandList[commandCnt].buttInx = buttInx;
	commandList[commandCnt].stickyMask = stickyMask;
	commandList[commandCnt].menu[0] = cmdMenus[0];
	commandList[commandCnt].menu[1] = cmdMenus[1];
	commandList[commandCnt].menu[2] = cmdMenus[2];
	commandList[commandCnt].menu[3] = cmdMenus[3];

	ToolbarButtonCommandLink(buttInx, commandCnt);

	commandCnt++;
	return commandCnt - 1;
}




EXPORT void PlaybackCommand(const char * line, wIndex_t lineNum)
{
	size_t inx;
	wIndex_t buttInx;
	size_t len1, len2;
	len1 = strlen(line + 8);
	for (inx = 0; inx < commandCnt; inx++) {
		len2 = strlen(commandList[inx].helpKey + 3);
		if (len1 == len2
		    && strncmp(line + 8, commandList[inx].helpKey + 3, len2) == 0) {
			break;
		}
	}
	if (inx >= commandCnt) {
		fprintf(stderr, "Unknown playback COMMAND command %d : %s\n", lineNum,
		        line);
	} else {
		buttInx = commandList[inx].buttInx;
		if ((commandList[inx].buttInx) >= 0) {
			ToolbarButtonPlayback(commandList[inx].buttInx);
		}
		if (strcmp(line + 8, "Undo") == 0) {
			if (buttInx > 0 && playbackTimer == 0) {
				ToolbarButtonBusy(buttInx, TRUE);
				wFlush();
				wPause(500);
				ToolbarButtonBusy(buttInx, FALSE);
				wFlush();
			}
			UndoUndo(NULL);
		} else if (strcmp(line + 8, "Redo") == 0) {
			if (buttInx >= 0 && playbackTimer == 0) {
				ToolbarButtonBusy(buttInx, TRUE);
				wFlush();
				wPause(500);
				ToolbarButtonBusy(buttInx, FALSE);
				wFlush();
			}
			UndoRedo(NULL);
		} else {
			if (buttInx >= 0 && playbackTimer == 0) {
				ToolbarButtonBusy(buttInx, TRUE);
				wFlush();
				wPause(500);
				ToolbarButtonBusy(buttInx, FALSE);
				wFlush();
			}
			DoCommandB(I2VP(inx));
		}
	}
}

/*--------------------------------------------------------------------*/


EXPORT BOOL_T IsCurCommandSticky(void)
{
	if ((commandList[curCommand].options & IC_STICKY) != 0
	    && (commandList[curCommand].stickyMask & stickySet) != 0) {
		return TRUE;
	}
	return FALSE;
}

EXPORT void ResetIfNotSticky(void)
{
	if ((commandList[curCommand].options & IC_STICKY) == 0
	    || (commandList[curCommand].stickyMask & stickySet) == 0) {
		Reset();
	}
}


/*--------------------------------------------------------------------*/
EXPORT void CommandInit( void )
{
	curCommand = describeCmdInx;
	commandContext = commandList[curCommand].context;
	log_command = LogFindIndex( "command" );
}

