/*
 *                          E X P L O R E . C
 *
 *  Do the explore command:
 *  - initialize & display popup
 *  - send values
 *  - interpret result
 *
 *  Version      : $Revision: 1.11 $
 *
 *  Created      : Mon Jun  6 02:22:28 1994
 *  Author       : Ulrich Drepper <drepper@mydec>
 *
 *  Last modified: Mon Jul 18 14:16:43 1994
 *  Author       : Ulrich Drepper <drepper@compare.ira.uka.de>
 *
 *  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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#if !defined(lint)
static const char *vcid = "$Id: explore.c,v 1.11 1994/07/15 10:23:36 drepper Exp $";
#endif /* lint */

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Toggle.h>

#include "empire.h"

/* we use gcc and therefor can use the built-in alloca */
#if !defined(alloca)
# define alloca(size) __builtin_alloca(size)
#endif

/*
 * local data types
 */
struct _ExploreData {
    int xStart;
    int yStart;
    int x;
    int y;
    int number;
    char civOrMil;
    int lines;
    char *cp;
    char path[0];
};
typedef struct _ExploreData ExploreData;

/* 
 * local variables
 */
static Widget explorePopup;
static Widget currentEditWidget;
static Widget fromText;
static Widget toLabel;
static Widget toText;
static Widget numberText;
static Widget civilians;
static Widget pathLabel;
static Widget pathText;
static Widget bestPath;
static Widget givenPath;
static Widget okButton;
static Widget form;
static void (*oldEditAdd)(Bool, XawTextBlock *, Bool);

/*
 * prototypes for local functions
 */
static void commandInitExplore(void);
static void exploreEditAdd(Bool active, XawTextBlock *textBlock, Bool async);
static void callbackExplore(Widget widget, XtPointer closure,
			    XtPointer callData);
static void prepareExploreEdit(void);
static void toggleGivenPath(Widget widget, XtPointer closure,
			    XtPointer callData);
static void prevEdit(Widget w, XEvent *event, String *params,
		     Cardinal *numParams);
static void nextEdit(Widget w, XEvent *event, String *params,
		     Cardinal *numParams);
static void buttonOnText(Widget widget, XtPointer closure, XEvent *event,
			 Boolean *cont);
static Bool processExplore(Bool first, Bool last, char *str, void *data);
static void newKnownType(int mapX, int mapY, char typeChar);

/*
 * exported functions
 */
void
queryCommandExplore(void)
{
    static Bool firstCall = True;
    Position fx, fy;
    Position x, y;
    Boolean isGivenPath;

    if (firstCall) {
	commandInitExplore();
	firstCall = False;
    }
    
    XtVaGetValues(
	commandForm,
	XtNx, &fx,
	XtNy, &fy,
	NULL);

    XtVaGetValues(
	horizPane,
	XtNx, &x,
	XtNy, &y,
	NULL);

    XtTranslateCoords(
	topLevel,
	x+fx, y+fy,
	&x, &y);

    XtVaSetValues(
	explorePopup,
	XtNx, x,
	XtNy, y,
	NULL);

    /* prepare edit widget communication */
    oldEditAdd = currentEditAdd;
    oldEditAdd(False, NULL, False);

    currentEditAdd = exploreEditAdd;
    currentEditWidget = fromText;
    
    prepareExploreEdit();
    
    XtVaGetValues(
	givenPath,
	XtNstate, &isGivenPath,
	NULL);

    XtVaSetValues(
	pathLabel,
	XtNsensitive, isGivenPath,
	NULL);
    XtVaSetValues(
	pathText,
	XtNsensitive, isGivenPath,
	NULL);

    XtPopup(explorePopup, XtGrabNonexclusive);
    XtAddGrab(showViewport, False, False);
}

Bool
exploreCommand(char civOrMil, int number, int xStart, int yStart, char *path)
{
    ExploreData *exploreData;
    char *dest;
    char *cmdDest;
    char *cp = path;
    Bool appendH = True;

    assert(civOrMil=='c' || civOrMil=='m');

    exploreData = (ExploreData*)malloc(sizeof(ExploreData)+strlen(path)+2);
    
    dest = cmdDest = (char*)alloca((strlen(path)+1)*2+1);
    
    if (!exploreData || !cmdDest) {
	message(WARN, "out of memory");
	return False;
    }

    exploreData->lines = 0;
    
    do {
	switch (*cp) {
	case 'g':
	case 'y':
	case 'u':
	case 'j':
	case 'n':
	case 'b':
	    *dest++ = *cp++;
	    break;
	case 'h':
	    appendH = False;
	    *dest++ = 'h';
	    *dest   = '\0';
	    cp = dest;          /* means break loop */
	    continue;
	    break;
	case '-':
	case '0'...'9':
	    {
		int x, y;
		char *first = cp;
		if (!strToCoord(&cp, &x, &y) || (x==xStart && y==yStart)) {
		    message(WARN, "explore: invalid path");
		    return False;
		}
		dest = cmdDest;
		memcpy(dest, first, cp-first);
		dest += cp-first;
	    }
	    break;
	default:
	    message(WARN, "illegal character in path");
	    return False;
	}
	*dest++ = '\n';
	exploreData->lines++;
    } while (*cp);

    if (appendH) {
	*dest++ = 'h';
	*dest   = '\0';
    }
    exploreData->lines++;

    exploreData->xStart   = xStart;
    exploreData->yStart   = yStart;
    exploreData->x        = xStart;
    exploreData->y        = yStart;
    exploreData->civOrMil = civOrMil;
    exploreData->number   = number;
    strcpy(exploreData->path, path);
    if (appendH) {
	strcat(exploreData->path, "h");
    }
    exploreData->cp     = exploreData->path;
    
    return sendCmdStr(processExplore, exploreData, NULL,
		      "explore %c %d,%d %d %s\n", civOrMil,
		      xStart, yStart, number, cmdDest);
}

/*
 * local functions
 */
static void
commandInitExplore(void)
{
    static String textTranslations = 
	"#override\n"
	"Shift<Key>Tab: prevExploreEdit(False)\n"
	"<Key>Tab: nextExploreEdit(False)\n"
	"<Key>Return: nextExploreEdit(True)\n"
	"<Key>Linefeed: nextExploreEdit(True)\n"
	"Ctrl<Key>M: nextExploreEdit(True)\n"
	"Ctrl<Key>J: nextExploreEdit(True)\n";
    static XtActionsRec cursorActions[2] = {
	{ "nextExploreEdit", nextEdit },
	{ "prevExploreEdit", prevEdit },
    };
    static String toggleTranslations =
	"<EnterWindow>: highlight(Always)\n"
	"<LeaveWindow>: unhighlight()\n"
	"<Btn1Down>,<Btn1Up>: set() notify()\n";
    static Bool first = True;
    static XtTranslations textTrans;
    static XtTranslations toggleTrans;
    Widget tmp;

    if (first) {
	XtAppAddActions(appContext, cursorActions, 2);
	textTrans = XtParseTranslationTable(textTranslations);
	toggleTrans = XtParseTranslationTable(toggleTranslations);
	first = False;
    }
    
    /* explore command */
    explorePopup = XtVaCreatePopupShell(
	"explorepopupshell",
	topLevelShellWidgetClass,
	topLevel,
	NULL);
    form = XtVaCreateManagedWidget(
	"form",
	formWidgetClass,
	explorePopup,
	NULL);
    tmp = XtVaCreateManagedWidget(
	"explore",
	labelWidgetClass,
	form,
	NULL);
    tmp = XtVaCreateManagedWidget(
	"fromlabel",
	labelWidgetClass,
	form,
	NULL);
    fromText = XtVaCreateManagedWidget(
	"fromtext",
	asciiTextWidgetClass,
	form,
	XtNeditType, XawtextEdit,
	XtNtranslations, textTrans,
	NULL);
    XtAddEventHandler(
	fromText,
	ButtonPressMask,
	False,
	buttonOnText,
	NULL);
    civilians = XtVaCreateManagedWidget(
	"civilians",
	toggleWidgetClass,
	form,
	XtNradioData, (int)'c',
	XtNtranslations, toggleTrans,
	XtNstate, True,
	NULL);
    tmp = XtVaCreateManagedWidget(
	"military",
	toggleWidgetClass,
	form,
	XtNradioGroup, civilians,
	XtNradioData, (int)'m',
	XtNtranslations, toggleTrans,
	NULL);
    tmp = XtVaCreateManagedWidget(
	"numberlabel",
	labelWidgetClass,
	form,
	NULL);
    numberText = XtVaCreateManagedWidget(
	"numbertext",
	asciiTextWidgetClass,
	form,
	XtNeditType, XawtextEdit,
	XtNtranslations, textTrans,
	NULL);
    XtAddEventHandler(
	numberText,
	ButtonPressMask,
	False,
	buttonOnText,
	NULL);
    bestPath = XtVaCreateManagedWidget(
	"bestpath",
	toggleWidgetClass,
	form,
	XtNradioData, 1,
	XtNtranslations, toggleTrans,
	NULL);
    XtAddCallback(
	bestPath,
	XtNcallback, toggleGivenPath,
	NULL);
    givenPath = XtVaCreateManagedWidget(
	"givenpath",
	toggleWidgetClass,
	form,
	XtNradioGroup, bestPath,
	XtNradioData, 2,
	XtNtranslations, toggleTrans,
	XtNstate, True,
	NULL);
    XtAddCallback(
	givenPath,
	XtNcallback, toggleGivenPath,
	NULL);
    toLabel = XtVaCreateManagedWidget(
	"tolabel",
	labelWidgetClass,
	form,
	XtNsensitive, False,            /* bestPath != True */
	NULL);
    toText = XtVaCreateManagedWidget(
	"totext",
	asciiTextWidgetClass,
	form,
	XtNeditType, XawtextEdit,
	XtNtranslations, textTrans,
	XtNsensitive, False,            /* bestPath != True */
	NULL);
    XtAddEventHandler(
	toText,
	ButtonPressMask,
	False,
	buttonOnText,
	NULL);
    pathLabel = XtVaCreateManagedWidget(
	"pathlabel",
	labelWidgetClass,
	form,
	NULL);
    pathText = XtVaCreateManagedWidget(
	"pathtext",
	asciiTextWidgetClass,
	form,
	XtNeditType, XawtextEdit,
	XtNtranslations, textTrans,
	NULL);
    XtAddEventHandler(
	pathText,
	ButtonPressMask,
	False,
	buttonOnText,
	NULL);
    okButton = XtVaCreateManagedWidget(
	"OK",
	commandWidgetClass,
	form,
	NULL);
    XtAddCallback(
	okButton,
	XtNcallback, callbackExplore,
	(XtPointer)True);
    tmp = XtVaCreateManagedWidget(
	"Cancel",
	commandWidgetClass,
	form,
	NULL);
    XtAddCallback(
	tmp,
	XtNcallback, callbackExplore,
	(XtPointer)False);

    XtRealizeWidget(explorePopup);
}

/* ARGSUSED */
static void
exploreEditAdd(Bool active, XawTextBlock *textBlock, Bool async)
{
    assert(async==False);
    if (textBlock) {
	XtVaSetValues(
	    currentEditWidget,
	    XtNstring, textBlock->ptr,
	    NULL);
	XawTextSetInsertionPoint(currentEditWidget, strlen(textBlock->ptr));
    } else {
	XawTextDisplayCaret(currentEditWidget, active);
	if (active) {
	    XtSetKeyboardFocus(form, currentEditWidget);
	    XtSetKeyboardFocus(vertPane, currentEditWidget);
	}
    }
}

static void
callbackExplore(Widget widget, XtPointer closure, XtPointer callData)
{
    if ((Bool)closure) {
	char civOrMil;
	char *fromSect;
	char *toSect;
	char *number;
	char *path;
	int x, y;

	XtVaGetValues(
	    fromText,
	    XtNstring, &fromSect,
	    NULL);

	XtVaGetValues(
	    toText,
	    XtNstring, &toSect,
	    NULL);

	XtVaGetValues(
	    numberText,
	    XtNstring, &number,
	    NULL);

	XtVaGetValues(
	    pathText,
	    XtNstring, &path,
	    NULL);
	
	civOrMil = (char)(int)XawToggleGetCurrent(civilians);

	if (strToCoord(&fromSect, &x, &y)) {
	    exploreCommand(civOrMil, atoi(number), x, y, path);

	    /* prepare next input */
	    prepareExploreEdit();
	}
    } else {
	XtPopdown(explorePopup);

	currentEditAdd(False, NULL, False);
	currentEditAdd = oldEditAdd;
	oldEditAdd(True, NULL, False);
    }
}

/* ARGSUSED */
static void
toggleGivenPath(Widget widget, XtPointer closure, XtPointer callData)
{
    Boolean thisState;

    XtVaGetValues(
	widget,
	XtNstate, &thisState,
	NULL);
    
    if (widget == givenPath) {
	XtVaSetValues(
	    pathLabel,
	    XtNsensitive, thisState,
	    NULL);

	XtVaSetValues(
	    pathText,
	    XtNsensitive, thisState,
	    NULL);

	if (!thisState && currentEditWidget == pathText) {
	    XawTextDisplayCaret(fromText, True);
	    XawTextDisplayCaret(pathText, False);
	    currentEditWidget = fromText;
	    XtSetKeyboardFocus(form, fromText);
	    XtSetKeyboardFocus(vertPane, fromText);
	}
    } else {
	XtVaSetValues(
	    toLabel,
	    XtNsensitive, thisState,
	    NULL);

	XtVaSetValues(
	    toText,
	    XtNsensitive, thisState,
	    NULL);

	if (!thisState && currentEditWidget == toText) {
	    XawTextDisplayCaret(fromText, True);
	    XawTextDisplayCaret(toText, False);
	    currentEditWidget = fromText;
	    XtSetKeyboardFocus(form, fromText);
	    XtSetKeyboardFocus(vertPane, fromText);
	}
    }
}

static void
prepareExploreEdit(void)
{
    /* prepare next input */
    XtVaSetValues(
	fromText,
        XtNstring, "",
        NULL);
    XtVaSetValues(
	pathText,
        XtNstring, "",
        NULL);

    currentEditAdd(False, NULL, False);
    currentEditWidget = fromText;
    currentEditAdd(True, NULL, False);
    
    XawTextSetInsertionPoint(fromText, 0);
    XawTextDisplayCaret(toText, False);
    XawTextSetInsertionPoint(toText, 0);
    XawTextDisplayCaret(numberText, False);
    XawTextSetInsertionPoint(numberText, 0);
    XawTextDisplayCaret(pathText, False);
    XawTextSetInsertionPoint(pathText, 0);
}

/* ARGSUSED */
static void
prevEdit(Widget w, XEvent *event, String *params, Cardinal *numParams)
{
    XawTextDisplayCaret(currentEditWidget, False);

    if (currentEditWidget == numberText) {
	currentEditWidget = fromText;
    } else if (currentEditWidget == pathText ||
	currentEditWidget == toText) {
	currentEditWidget = numberText;
    } else {
	Boolean isGivenPath;

	XtVaGetValues(
	    givenPath,
	    XtNstate, &isGivenPath,
	    NULL);
	
	if (isGivenPath) {
	    currentEditWidget = pathText;
	} else {
	    currentEditWidget = toText;
	}
    }

    XawTextDisplayCaret(currentEditWidget, True);
    XtSetKeyboardFocus(form, currentEditWidget);
    XtSetKeyboardFocus(vertPane, currentEditWidget);
}

/* ARGSUSED */
static void
nextEdit(Widget w, XEvent *event, String *params, Cardinal *numParams)
{
    if (numParams != 0 && !strcmp(params[0], "True")) {
	XtCallActionProc(okButton, "set", NULL, NULL, 0);
	XtCallActionProc(okButton, "notify", NULL, NULL, 0);
	XtCallActionProc(okButton, "unset", NULL, NULL, 0);
    } else {
	XawTextDisplayCaret(currentEditWidget, False);

	if (currentEditWidget == fromText) {
	    currentEditWidget = numberText;
	} else if (currentEditWidget == pathText ||
	    currentEditWidget == toText) {
	    currentEditWidget = fromText;
	} else {
	    Boolean isGivenPath;

	    XtVaGetValues(
		givenPath,
		XtNstate, &isGivenPath,
		NULL);
	
	    if (isGivenPath) {
		currentEditWidget = pathText;
	    } else {
		currentEditWidget = toText;
	    }
	}

	XawTextDisplayCaret(currentEditWidget, True);
	XtSetKeyboardFocus(form, currentEditWidget);	
	XtSetKeyboardFocus(vertPane, currentEditWidget);
    }
}

/* ARGSUSED */
static void
buttonOnText(Widget widget, XtPointer closure, XEvent *event, Boolean *cont)
{
    if (widget != currentEditWidget) {
	XawTextDisplayCaret(currentEditWidget, False);

	currentEditWidget = widget;
	
	XawTextDisplayCaret(currentEditWidget, True);
	XtSetKeyboardFocus(form, currentEditWidget);	
	XtSetKeyboardFocus(vertPane, currentEditWidget);
    }
}

static Bool
processExplore(Bool first, Bool last, char *str, void *data)
{
    static Bool illegalCommand = False;
    static Bool ignore = False;        /* in error state ? */
    static int lineOfPart = 0;
    ExploreData *exploreData = (ExploreData*)data;
    int idx;
    int idx2;
    int x, y;
    char chType1, chType2;
    char typeChar;

    if (first) {
	if ((str && strstr(str, "is not a legal command")) || last) {
	    /* reason for this could be negative funds */
	    message(WARN, "%s", str);
	    illegalCommand = True;
	    ignore = True;
	} else {
	    ignore = False;
	    lineOfPart = 0;
	}
    }

    if (last) {
	if (illegalCommand && --exploreData->lines) {
	    /* more prompts due to illegal command follow */
	    return False;
	} else {
	    free(exploreData);
	    return True;
	}
    }

    if (ignore || illegalCommand) return False;

    if (lineOfPart == 0) {
	switch (*exploreData->cp++) {
	case 'h':
	    idx = MAP2ARR(exploreData->x,exploreData->y);
	    idx2 = MAP2ARR(exploreData->xStart,exploreData->yStart);
	    charToProduct(exploreData->civOrMil, empire.map[idx]) +=
		 exploreData->number;
	    charToProduct(exploreData->civOrMil, empire.map[idx2]) -=
		 exploreData->number;
	    empire.map[idx].status = OwnSector;
	    empire.map[idx].typePixmap = 
		getTypePixmap(MAP2ARRX(exploreData->x),
			      MAP2ARRY(exploreData->y), 
			      empire.map[idx].status,
			      False);
	    paintHexa(mapWidget, MAP2ARRX(exploreData->x),
		      MAP2ARRY(exploreData->y), False);
	    ignore = True;
	    /* format: 'Total movement cost = ##, ## mob left in ##,##' */
	    if (sscanf(str, "Total movement cost = %*d, %d mob left in %d,%d",
		       &exploreData->number, &x, &y) == 3) {
		empire.map[MAP2ARR(x, y)].mobility = exploreData->number;
	    }
	    ignore = True;
	    return False;
	    break;
	case 'y':
	    exploreData->x--;
	    exploreData->y--;
	    break;
	case 'u':
	    exploreData->x++;
	    exploreData->y--;
	    break;
	case 'g':
	    exploreData->x -= 2;
	    break;
	case 'j':
	    exploreData->x += 2;
	    break;
	case 'b':
	    exploreData->x--;
	    exploreData->y++;
	    break;
	case 'n':
	    exploreData->x++;
	    exploreData->y++;
	    break;
	case '-':
	case '0'...'9':
	    exploreData->cp--;
	    strToCoord(&exploreData->cp, &exploreData->x, &exploreData->y);
	    lineOfPart = -2;
	    break;
	default:
	    assert(True == False);
	}
	lineOfPart += 2;
    }
    
    idx = MAP2ARR(exploreData->x,exploreData->y);
    
    switch (lineOfPart++) {
    case 0:
	/* format for best route line one: 'Looking for best path to ##,##' */
	if (sscanf(str, "Looking for best path to %d,%d", &x, &y) != 2) {
	    if (!strncmp(str, "You can't go there...", 21)) {
		message(WARN, "you can't go there...");
	    }
	    /* incorrect line, perhaps not an own sector */
	    ignore = True;
	    return False;
	}
	assert(exploreData->x==x && exploreData->y==y);
	break;
    case 1:
	/* format for best route line two:
	 * Using best path 'XX', movement cost #.## */
	if (strncmp(str, "Using best path ", 15)) {
	    /* incorrect line, perhaps not an own sector */
	    ignore = True;
	    return False;
	}
	break;
    case 2:
	if (!strncmp(str, "You can't go there...", 21)) {
	    message(WARN, "you can't go there...");
	    ignore = True;
	    return False;
	}
	/* format of first line: '    * *       min gold fert  oil uran' */
	while (isspace(*str)) str++;
	chType1 = *str++;
	if (!isspace(*str++)) {
	    /* incorrect first line =>  we cannot extract further info */
	    ignore = True;
	    return False;
	}
	chType2 = *str++;
	while (isspace(*str)) str++;
	if (strncmp(str, "min gold fert  oil uran", 23)) {
	    /* incorrect first line =>  we cannot extract further info */
	    ignore = True;
	    return False;
	}
	newKnownType(exploreData->x-1, exploreData->y-1, chType1);
	newKnownType(exploreData->x+1, exploreData->y-1, chType2);
	break;
    case 3:
	/* format of second line: '   * * *      ###  ###  ###  ###  ###' */
	if (sscanf(str, " %c %c %c %d %d %d %d %d",
		   &chType1,
		   &typeChar,
		   &chType2,
		   &empire.map[idx].resource[resIdxMin],
		   &empire.map[idx].resource[resIdxGold],
		   &empire.map[idx].resource[resIdxFert],
		   &empire.map[idx].resource[resIdxOil],
		   &empire.map[idx].resource[resIdxUran]) != 8) {
	    /* incorrect second line =>  we cannot extract further info */
	    ignore = True;
	    return False;
	}
	newKnownType(exploreData->x-2, exploreData->y, chType1);
	newKnownType(exploreData->x, exploreData->y, typeChar);
	newKnownType(exploreData->x+2, exploreData->y, chType2);
	break;
    case 4:
	/* format of third line: '    * *' */
	if (sscanf(str, " %c %c", &chType1, &chType2) != 2) {
	    /* incorrect third line =>  we cannot extract further info */
	    ignore = True;
	    return False;
	}
	newKnownType(exploreData->x-1, exploreData->y+1, chType1);
	newKnownType(exploreData->x+1, exploreData->y+1, chType2);
	lineOfPart = 0;
	break;
    }

    return False;
}

static void
newKnownType(int mapX, int mapY, char typeChar)
{
    int idx = MAP2ARR(mapX, mapY);
    
    if (typeChar != empire.map[idx].typeChar) {
	if (typeChar == '?') {
	    if (empire.map[idx].status != EnemySector) {
		empire.map[idx].status = EnemySector;
		redisplaySectors(mapWidget, mapX, mapY, mapX, mapY);
	    }
	    if (empire.map[idx].typeChar != ' ') return;
	}
	empire.map[idx].typeChar = typeChar;
	empire.map[idx].typePixmap = 
	    getTypePixmap(MAP2ARRX(mapX),
			  MAP2ARRY(mapY), 
			  empire.map[idx].status,
			  False);
	redisplaySectors(mapWidget, mapX, mapY, mapX, mapY);
    }    
}

/*
 * Local Variables:
 *  mode:c
 *  c-indent-level:4
 *  c-continued-statement-offset:4
 *  c-continued-brace-offset:0
 *  c-brace-offset:0
 *  c-imaginary-offset:0
 *  c-argdecl-indent:4
 *  c-label-offset:-2
 * End:
 */
