/*
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "rl.h"
static int nbuf=0;
static char **cmds=NULL;
static int *ctag=NULL;
static int curidx;
static int curtag=0;
static int maxl=0;
static char *hist_add(char *, char *);

static void
hist_list(void)
{
int i;

	for(i=0;i<curidx;i++)
	{
		printf("%3d: %s\n", ctag[i], cmds[i]);
	}
}

static void
hist_setmax(int maxh)
{
	if(maxh>DEF_MAXHIST || maxh<=0)
	{ printf("%d invalid, valid range is [1..%d]\n", maxh, DEF_MAXHIST); return;}

	if(maxh!=nbuf)
	{
	char **nc;
	int *nt;

		if((nc=(char**)malloc(sizeof(char*)*maxh)))
		{
			if((nt=(int*)malloc(sizeof(int)*maxh))) 
			{
				/* move the older lists around ? */
				if(curidx) 
				{
				int i, start=0, j;

					if(maxh<curidx) start=curidx-maxh;

					for(i=0;i<start;i++) free(cmds[i]);

					for(i=start,j=0;j<maxh&&i<curidx;i++,j++)
					{
						nt[j]=ctag[i];
						nc[j]=cmds[i];
					}
					curidx=j;
					free(cmds);
					free(ctag);
				}
				cmds=nc;
				ctag=nt;
				nbuf=maxh;
			}
			else free(nc);
		}
	}
}

int 
hist_init(int maxh, int maxc)
{
	curidx=0;
	maxl=maxc;
	hist_setmax(maxh);
	return 1;
}

/*
	Add a new command to history list.
*/
static char *
hist_add(char *s1, char *s2)
{
int len;
char *pt;

	/* make some space for the new history line */
	len=strlen(s1)+(s2?strlen(s2):0);
	if(len>maxl) len=maxl;
	if(!(pt=malloc(len+1)))
	{ printf("history: memory allocation error!\n"); return NULL;}

	strcpy(pt, s1);
	if(s2) strncat(pt,s2,maxl-strlen(s1));

	/* get rid of the oldest one ? */
	if(curidx==nbuf)
	{
		/* free oldest */
		free(cmds[0]);
		/* move over pointers and indexes */
		memmove(&cmds[0], &cmds[1], (nbuf-1)*sizeof(cmds[0]));
		memmove(&ctag[0], &ctag[1], (nbuf-1)*sizeof(ctag[0]));
		curidx--;
	}

	cmds[curidx]=pt;
	ctag[curidx]=(++curtag);
	if(curidx<nbuf) curidx++;
	return pt;
}

/*
	hook for the command interface.
	get me the command at curidx-offset
*/
char *
hist_getcmd(int off)
{
	if(off<=curidx && off>=0)
	{
		return off ? cmds[curidx-off] : "";
	}
	else return 0;
}


/*
	get a previous command from the history and place it
	in the current input.

	idx is the index in the current history list.
	xtra is whatever the user entered after the history command.
*/
static char*
repeat(int idx, char *xtra)
{
char *p=hist_add(cmds[idx],xtra);

	printf("%s\n", p);
	return p;
}

/*
	scan backward and find a tg.
*/
static int
getidx(int tag)
{
int i;

	for(i=curidx;i>=0;i--) if(ctag[i]==tag) break;
	return i;
}

/*
	pass the command through to see if we need to do some history 
	gymnastics...
*/
char *
hist_cmd(char *cmd)
{
char *tok=cmd;

	while(*cmd==' '||*cmd=='\t') cmd++;

	if(!*cmd) return 0;

	/* command 'h' or 'history' */
	/* ugly but I don't want to use strtok... */
	if( ( ((cmd[0]=='h') && 
	       ( (cmd[1]==' ') || (cmd[1]=='\t') || (!cmd[1]) )))
	    || ((!strncmp(cmd,"history", 7)) && 
		( (cmd[7]==' ') || (cmd[7]=='\t') || (!cmd[7]) )))
	{
		char *pt=cmd;
		
		while(*pt) if(isdigit(*pt)) break; else pt++;

		/* check to see if we are changing the number of lines */
		if(*pt)
		{
		int n;
			sscanf(pt,"%d", &n);
			hist_setmax(n);
		}
		/* show history */
		else hist_list();

		return 0;
	}
	else if(tok[0]=='!')
	{
		/* we most have at least one entry for !! to be valid */
		if(tok[1]== '!')
		{
			if(curidx) return repeat(curidx-1, tok+2);
		}
		else
		{
		int n;

			/* number ? */
			if(sscanf(tok+1, "%d", &n)==1)
			{
			int rel=0;


				tok++;
				if(*tok=='-') {n=(-n); rel=1; tok++;}

				while(isdigit(*tok)) tok++;

				/* absolute index */
				if(!rel)
				{
				int nidx=getidx(n);

					if(nidx>=0) return repeat(nidx,tok);
				}
				/* relative backward */
				else
				{
					if(curidx>n) return repeat(curidx-n, tok);
				}
			}
			/* string match ? */
			else
			{
			char *pt=tok+1;
			int nc,i;

				while(*pt!=' '&&*pt!='\t'&&*pt) pt++;
				nc=pt-tok-1;
				for(i=curidx-1;i>=0;i--)
				{
					if(!strncmp(cmds[i], tok+1, nc))
						return repeat(i, tok+nc+1);
				}
			}
		}
		printf("Invalid history specification.\n");
		return 0;
	}
	else return hist_add(cmd,NULL);
}
