/*  gwiz.c: (c) 2002 sibn

    This file is part of GWiz.

    GWiz 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.

    GWiz 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 GWiz; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_ttf.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include "gwiz.h"
#include "uiloop.h"
#include "prefsdat.h"
#include "text.h"
#include "maploader.h"
#include "menus.h"
#include "playerpawn.h"
#include "castle.h"
#include "playergen.h"
#include "inspect.h"
#include "joystick.h"
#include "color.h"

GwizApp gwiz;
const char *Usage = "\
GWiz, a GNU Wizards Game is available under the the GNU GPL, version 2 or\n\
later (at your option).\n\n\
Usage: %s [options]\n\n\
Options supported:\n\
  --antialias     | -a        \tAntialiased font (slower, prettier)\n\
  --depth <16|32> | -d <16|32>\tSpecify color depth\n\
  --fullscreen    | -f        \tRun in fullscreen mode\n\
  --help          | -h        \tPrint this help message\n\
  --joystick      | -j        \tEnable joystick use\n\
  --ptsize <size> | -p <size> \tSpecify the point size\n\
  --solid         | -s        \tSolid font (faster, uglier)\n\
  --version       | -v        \tPrint version information\n\
  --window        | -w        \tRun in windowed mode\n\
\n";

int main (int argc, char **argv)
{
    char *invocation;
    
    invocation = strdup (argv[0]);
    
    LoadPrefs();
    LoadJoyConfig();
    GwizGetOpts(argc, argv);
    
    /* This will have to be moved to GwizSanitizeOpts() or something */
    if (gwiz.font.ptsize != 16)
	fprintf(stderr, "Warning: -p flag unsupported.  If text overlaps, try with -p 16 (default)\n");
    
    /* Initialize the random number generator.  don't need critical
       randomness, so this is sufficient. */
    srand(time(NULL));
    
    if (gwiz.font.ptsize < 10)
	gwiz.font.ptsize = 16;
    if (gwiz.font.ptsize > 24)
	gwiz.font.ptsize = 24;
    
    Sfree (invocation);
    
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK) != 0)
	BailCall ("Unable to initialize SDL: ", SDL_GetError());
    atexit (SDL_Quit);
    
    SDL_WM_SetCaption ("GWiz: A GNU Wizards Game", NULL);
    GwizSetWindowIcon();

    gwiz.canvas = SDL_SetVideoMode(800, 600, gwiz.bpp, gwiz.vidmode);
    if (gwiz.canvas == NULL)
	BailCall ("Unable to set video mode: ", SDL_GetError());

    InitGwizJoystick();
    InitPawnNos();
    InitGwizFonts();
    InitCommonNums();
    InitClassPix();
    InitStatusPix();
    LoadWalls();
    LoadDoors();
    LoadCursor();
    InitInspectEngine(&gwiz.gi);
    InitShopLists();
    MapLoad (1);
    atexit(SavePrefs);
    SaveJoyConfig(); /* make sure it can safely restore prefs */
    
    GwizLogEvent (__FILE__, __LINE__,
		  "GWiz Init successful.  Entering main event loop");
    
    UILoop();
    
    /* we should never get this far. */
    return 1;
}

/* before you ask, "Bail" (i.e., jump ship), and "Call". "quit on demand" */
inline void BailCall (char *msg, char *errcode)
{
    fprintf (stderr, "%s %s\n", msg, errcode);
    GwizLogEvent (__FILE__, __LINE__, "Unable to perform requested task: %s",
		  SDL_GetError());
    GwizShutdown(1);
}

void LoadWalls (void)
{
    char *pix = PIXMAPSDIR;
    char *cfp;
    int i;
    cfp = MakePath (pix, "/wall1.png");
    gwiz.wall[0] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/wall2.png");
    gwiz.wall[1] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/wall3.png");
    gwiz.wall[2] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/wall4.png");
    gwiz.wall[3] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/wall5.png");
    gwiz.wall[4] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/wall6.png");
    gwiz.wall[5] = IMG_Load(cfp);
    free (cfp);
    
    for (i = 0; i < 6; i++)
	{
	    if (gwiz.wall[i] == NULL)
		BailCall("Could not load pixmap: ", SDL_GetError());
	}
    cfp = MakePath (pix, "/floor1.png");
    gwiz.floor[0] = IMG_Load (cfp);
    Sfree (cfp);
    cfp = MakePath (pix, "/floor2.png");
    gwiz.floor[1] = IMG_Load (cfp);
    Sfree (cfp);
}

void LoadDoors (void)
{
    char *pix = PIXMAPSDIR;
    char *cfp;
    int i;
    cfp = MakePath (pix, "/door1.png");
    gwiz.door[0] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/door2.png");
    gwiz.door[1] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/door3.png");
    gwiz.door[2] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/door4.png");
    gwiz.door[3] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/door5.png");
    gwiz.door[4] = IMG_Load(cfp);
    free (cfp);
    cfp = MakePath (pix, "/door6.png");
    gwiz.door[5] = IMG_Load(cfp);
    free (cfp);
    
    for (i = 0; i < 6; i++)
	{
	    if (gwiz.door[i] == NULL)
		BailCall("Could not load pixmap: ", SDL_GetError());
	}
}

char *MakePath (char *base, char *file)
{
    char *path;
    size_t pathlength = (strlen(base) + strlen(file) + 1);
    
    path = (char *)Smalloc (pathlength);
    if (path == NULL)
	BailCall ("Unable to allocate memory for MakePath", "");
    strncpy (path, base, pathlength);
    strncat (path, file, pathlength-1-strlen(path));
    
    return (path);
}

inline void *Smalloc (size_t size)
{
    void *ptr;
    ptr = malloc(size);
    if (ptr == NULL)
	{
	    perror("malloc");
	    GwizShutdown(1);
	}
    return ptr;
}

inline void Sfree(void *ptr)
{
    if (ptr == NULL)
	return;
    free (ptr);
    /* ptr is probably a local variable, making this dumb */
    ptr = NULL;
}

/* Unfortunately, I could not cleanly implement Srealloc() */

void LoadCursor (void)
{
    char *freeme = MakePath (PIXMAPSDIR, "/cursor.png");
    gwiz.cursor = IMG_Load (freeme);
    if (gwiz.cursor == NULL)
	BailCall ("gwiz.c: Unable to open cursor pixmap ", freeme);
    Sfree (freeme);
}

void VerifyQuit (void)
{
    char *opts[] = {
	"Continue playing",
	"Quit game",
	NULL
    };
    if (NewGwizMenu(gwiz.canvas, opts, -1, -1, 0) == 1)
	GwizShutdown(0);
}

void InitPawnNos(void)
{
    int i = 0;
    for (i = 0; i < 6; i++)
	{
	    gwiz.pawnno[i] = -1;
	    gwiz.pawn[i].ali = NEUTRAL;
	}
}

/* A "nice" shutdown call, that frees up a bunch of stuff before exiting */
void GwizShutdown (int exitcode)
{
    int i = 0;
    /* FIXME: Save party. */

    SaveJoyConfig();

    for (i = 0; i < 6; i++)
	if (gwiz.pawnno[i] > -1)
	    {
		gwiz.pawn[i].saved_x = gwiz.x;
		gwiz.pawn[i].saved_y = gwiz.y;
		gwiz.pawn[i].saved_z = gwiz.z;
		SavePawn (gwiz.pawn[i], gwiz.pawnno[i]);
	    }

    SDL_FreeSurface (gwiz.canvas);
    SDL_FreeSurface (gwiz.cursor);
    SDL_FreeSurface (gwiz.gi.area);
    SDL_FreeSurface (gwiz.windowicon);
    for (i = 0; i < 8; i++)
	{
	    SDL_FreeSurface (gwiz.classpix[i]);
	    SDL_FreeSurface (gwiz.tbord[i]);
	}
    for (i = 0; i < 6; i++)
	{
	    SDL_FreeSurface (gwiz.wall[i]);
	    SDL_FreeSurface (gwiz.door[i]);
	}
    SDL_FreeSurface (gwiz.floor[0]);
    SDL_FreeSurface (gwiz.floor[1]);
    for (i = 0; i < 26; i++)
	SDL_FreeSurface (gwiz.number[i]);
    
    TTF_CloseFont (gwiz.font.face);
    
    Sfree (gwiz.udata.home);
    /* Sfree(gwiz.udata.name); <-- unused */
    Sfree (gwiz.udata.gwiz);
    Sfree (gwiz.udata.cfg);
    Sfree (gwiz.udata.party);
    Sfree (gwiz.udata.post);
    Sfree (gwiz.udata.log);
    Sfree (gwiz.udata.monsters);
    Sfree (gwiz.udata.items);

    DestroyShopNodeList (&gwiz.shop.wpn);
    DestroyShopNodeList (&gwiz.shop.arm);
    DestroyShopNodeList (&gwiz.shop.shld);
    DestroyShopNodeList (&gwiz.shop.hlm);
    DestroyShopNodeList (&gwiz.shop.gnt);
    DestroyShopNodeList (&gwiz.shop.misc);
    DestroyShopNodeList (&gwiz.shop.scrl);

    GwizLogEvent (__FILE__, __LINE__, "Shutdown process complete, %s",
		  "now exiting.");
    
    exit (0);
}

void GwizSetWindowIcon (void)
{
    char *freeme;
    
    freeme = MakePath (PIXMAPSDIR, "/gwiz_icon.xpm");
    gwiz.windowicon = IMG_Load (freeme);
    if (gwiz.windowicon == NULL)
	fprintf(stderr, "Unable to load pixmap: %s\n", SDL_GetError());
    else
	SDL_WM_SetIcon (gwiz.windowicon, NULL);
    Sfree (freeme);
}

void GwizLogEvent (char *file, int line, char *fmt, ...)
{
    FILE *log;
    va_list ap;
    char *p;
    char *sval;
    int ival;
    double dval;

    if (!gwiz.logging)
	return;
    
    if (!gwiz.freshlog)
	{
	    log = fopen (gwiz.udata.log, "w");
	    gwiz.freshlog = 1; /* Append to log, instead of writing */
	} else {
	    log = fopen (gwiz.udata.log, "a");
	}

    fprintf (log, "%s, line %d: ", file, line);

    va_start (ap, fmt);
    for (p = fmt; *p; p++)
	{
	    if (*p != '%')
		{
		    fputc (*p, log);
		    continue;
		}
	    switch (*++p)
		{
		case 'd':
		    ival = va_arg(ap, int);
		    fprintf (log, "%d ", ival);
		    break;
		case 'f':
		    dval = va_arg(ap, double);
		    fprintf (log, "%f ", dval);
		    break;
		case 's':
		    sval = va_arg(ap, char *);
		    fprintf (log, "%s ", sval);
		    break;
		default:
		    fputc (*p, log); /* catch %% cases */
		    break;
		}
	}
    fprintf (log, "\n");

    va_end(ap);
    
     fclose (log);
}

void GwizGetOpts (int argc, char **argv)
{
    char *invocation;
    int next_option;
    const char *short_options = "ad:fhjlp:svw";
    const struct option long_options[] = {
	{"antialias",  0, NULL, 'a'},
	{"depth",      1, NULL, 'd'},
	{"fullscreen", 0, NULL, 'f'},
	{"help",       0, NULL, 'h'},
	{"joystick",   0, NULL, 'j'},
	{"log",        0, NULL, 'l'},
	{"ptsize",     1, NULL, 'p'},
	{"solid",      0, NULL, 's'},
	{"version",    0, NULL, 'v'},
	{"window",     0, NULL, 'w'},
	{NULL,         0, NULL, 0}
    };

    invocation = strdup (argv[0]);

        do 
        {
	    next_option = getopt_long(argc, argv, short_options,
				      long_options, NULL);
	    switch (next_option)
		{
		case 'a':
		    gwiz.font.aa = 1;
		    break;
		case 'd':
		    gwiz.bpp = atoi(optarg);
		    if ((gwiz.bpp != 16) && (gwiz.bpp != 32))
			gwiz.bpp = 32;
		    break;
		case 'f':
		    gwiz.vidmode = (gwiz.vidmode | SDL_FULLSCREEN);
		    break;
		case 'h':
		    fprintf(stderr, Usage, invocation);
		    exit (0);
		    break;
		case 'j':
		    if (gwiz.joy.enabled == FALSE)
			gwiz.joy.enabled = TRUE;
		    else
			gwiz.joy.enabled = FALSE;
		case 'l':
		    if (gwiz.logging == FALSE)
			gwiz.logging = TRUE;
		    else
			gwiz.logging = FALSE;
		    break;
		case 'p':
		    gwiz.font.ptsize = atoi(optarg);
		    break;
		case 's':
		    gwiz.font.aa = 0;
		    break;
		case 'v':
		    fprintf(stderr, "Gee Whiz version %s\n", VERSION);
		    exit(0);
		    break;
		case 'w':
		    gwiz.vidmode = (SDL_HWSURFACE | SDL_DOUBLEBUF);
		    break;
		case '?':
		case ':':
		    fprintf(stderr, Usage, invocation);
		    exit (0);
		    break;
		}
	} while (next_option != -1);

	Sfree (invocation);
}

SDL_Surface *NewGwizSurface (int width, int height)
{
    SDL_Surface *tmp = NULL;
    SDL_Surface *ret = NULL;

    tmp = SDL_CreateRGBSurface (SDL_SWSURFACE, width, height, gwiz.bpp,
				gwiz.rmask, gwiz.gmask, gwiz.bmask, 
				gwiz.amask);
    ret = SDL_DisplayFormat (tmp);
    if ((tmp == NULL) || (ret == NULL))
	/* Don't QUIT here, because we might have a special error handler
	   after this function is called (and if we don't, it'll crash right
	   quick anyway) */
	fprintf (stderr, "gwiz.c: unable to create surface: %s\n",
		 SDL_GetError());
    SDL_FreeSurface (tmp);
    SDL_FillRect (ret, NULL, gwiz.bgc);
    return (ret);
}

/* Remember to free this string */
char *ItoA (int num, int places)
{
    char string[places+1]; /* room for NULL */

    snprintf (string, sizeof(char)*places, "%d", num);
    string[places+1] = '\0'; /* just to be safe */
    return (strdup (string));
}

inline int CenterHoriz (SDL_Surface *first, SDL_Surface *second)
{
    return (first->w/2 - second->w/2);
}

inline int CenterVert (SDL_Surface *first, SDL_Surface *second)
{
    return (first->h/2 - second->h/2);
}

unsigned long int HashString (char *input)
{
    int digits = strlen (input);
    int first_divisor = 13;
    int second_divisor = 11;
    int multiplier = 31;
    int largeprime = 991889; /* large prime number */
    int test = 1;
    int i;

    if (input[0] == '\0')
	return (0);

    for (i = 0; i < digits; i++)
	{
	    if (input[i] == '\0')
		break;
	    test = test * (31 + input[i]) * multiplier;
	    test = test / first_divisor;
	    test = test * multiplier;
	    test = test / second_divisor;
	}
    return (test % largeprime);
}

void WipeCanvas(void)
{
    const int WIPE_BORDER_WIDTH = 4;
    SDL_Rect top;
    SDL_Rect btm;
    SDL_Rect lft;
    SDL_Rect rgt;
    int maxiterations = (gwiz.canvas->w/2)/4 - WIPE_BORDER_WIDTH*2;
    int i;

    top.x = 0;
    top.y = 0;
    top.h = WIPE_BORDER_WIDTH;
    top.w = gwiz.canvas->w;
    btm.x = 0;
    btm.y = gwiz.canvas->h - WIPE_BORDER_WIDTH;
    btm.h = WIPE_BORDER_WIDTH;
    btm.w = gwiz.canvas->w;
    lft.x = 0;
    lft.y = 0;
    lft.h = gwiz.canvas->h;
    lft.w = WIPE_BORDER_WIDTH;
    rgt.x = gwiz.canvas->w - WIPE_BORDER_WIDTH;
    rgt.y = 0;
    rgt.h = gwiz.canvas->h;
    rgt.w = WIPE_BORDER_WIDTH;

    for (i = 0; i < maxiterations; i++)
	{
	    top.y += WIPE_BORDER_WIDTH;
	    btm.y -= WIPE_BORDER_WIDTH;
	    lft.x += WIPE_BORDER_WIDTH;
	    rgt.x -= WIPE_BORDER_WIDTH;

	    SDL_FillRect (gwiz.canvas, &top, gwiz.bgc);
	    SDL_FillRect (gwiz.canvas, &btm, gwiz.bgc);
	    SDL_FillRect (gwiz.canvas, &lft, gwiz.bgc);
	    SDL_FillRect (gwiz.canvas, &rgt, gwiz.bgc);

	    SDL_Flip (gwiz.canvas);
	}
}
