/* Header stuff later... */

/*
working comment area:

The hard part here is going to be conditional operations, trapping events
that the scripts might care about. - perhaps we could have a cron_proc
attached to mobs (rooms/items?) to figure those out...
*/



#include <string.h>
#include <ctype.h>

#include "structs.h" /* need this? */
#include "db.h"
#include "cron.h"
#include "utils.h"
#include "interp.h"
#include "error.h"
#include "proto.h"

/* extern procedures */

struct cron_var *get_var(struct cron *c,int type,char *name);
void send_admin(char *text);
char *strdup();

/* local procedures */

struct exp *read_exp(char **input);
char *read_arg(char **input);
char *read_one_arg(char **input);
void read_bool(struct cron_quantum *c,char **input);
struct cron *new_cron();
struct script *get_script(int num);
void init_list(struct cron_list *l);
void enqueue_cron(struct cron *c,int where);
void unqueue_cron(struct cron *c);
bool eval_bool(struct cron *c,struct cron_quantum *q);

/* extern variables */

extern char log_buf[];

/* local variables */

struct cronbase cronbase;

char *cron_commands[] = {
    "nop",
    "var",
    "log",
    "actor",
    "setdoor",
    "load",
    "echo",
    "message",
    "speed",
    "restart",
    "run",
    "set",
    "if",
    "endif",
    "while",
    "endwhile",
    "wait",
    "\n"
};

void boot_cron()
{
    FILE *fl;
    int number,index;
    struct script *newscript;
    struct cron_quantum *newquant;
    char *temp,buffer[100],buf1[100],*txt;


    /* Initialize the cron lists */
    init_list(&cronbase.fast_crons);
    init_list(&cronbase.medium_crons);
    init_list(&cronbase.slow_crons);

    cronbase.scripts = NULL;

    /* load in the scripts */
    if(!(fl = fopen(CRON_FILE,"r"))) {
	perror("cron");
	exit(0);
    }

    while(1) {
	fscanf(fl," #%d\n",&number);
	temp=fread_string(fl);
	if(*temp=='$')
	    break;

	CREATE(newscript,struct script,1);

	newscript->name = temp;
	newscript->number = number;
	newscript->next_script = cronbase.scripts;
	cronbase.scripts = newscript;
	newscript->first = newscript->last = NULL;

	while(1) {
	    if(!(fgets(buffer,99,fl)))
		break;
/*log(buffer);*/

	    for(index=0;buffer[index];index++)
		if(buffer[index]=='\n') {
		    buffer[index]='\0';
		    break;
		}

	    for(txt=buffer;*txt && isspace(*txt);txt++)
		;
	    if(!*txt)            /* A blank line */
		continue;

	    if(*txt == '*') /* A comment line */
		continue;

	    if(*txt == 'S') /* End of script */
		break;


	    for(index=0;!isspace(txt[index]);index++)
		buf1[index]=txt[index];
	    buf1[index]='\0';
	    txt += index;

	    index=search_block(buf1,cron_commands,TRUE);

	    if(index==-1) {      /* An unintelligible line */
		sprintf(log_buf,
		    "CRON: Unintelligible script line:\n --> %s", buffer);
		log(log_buf);
		continue;
	    }

	    /* Insert the command */
	    CREATE(newquant,struct cron_quantum,1);
	    newquant->next = NULL;
	    if(newscript->last) {
		newscript->last->next = newquant;
		newscript->last = newquant;
	    } else
		newscript->first=newscript->last=newquant;

	    newquant->cmd = index;

	    switch(index) {
		case QC_LOG:
		    newquant->arg = read_arg(&txt);
		    break;

		case QC_ACTOR_COMMAND:
		/*	newquant->exp1 = read_exp(&txt);*/
		/*    newquant->data = atoi(txt);
		    while(isdigit(*txt)||isspace(*txt))
			txt++;*/
		    newquant->name = read_one_arg(&txt);
		    newquant->arg = read_arg(&txt);
		    break;

		case QC_SET_DOOR:
		    break;

		case QC_LOAD:
		/*	half_chop(...,buf1,buf2);*/
		    if(!strcmp(buf1,"mob"))
			newquant->data=LOAD_MOB;
		    else if(!strcmp(buf1,"obj"))
			newquant->data=LOAD_OBJ;
		    else {
			log("CRON: Invalid load");
			continue;
		    }
		    /* Which mob */
		    newquant->exp1 = read_exp(&txt);
		    newquant->exp2 = read_exp(&txt);
		    break;

		case QC_ECHO:
		    txt=one_argument(txt,buf1);
		    if(!strcmp(buf1,"room"))
			newquant->data=ECHO_ROOM;
		    else if(!strcmp(buf1,"zone"))
			newquant->data=ECHO_ZONE;
		    else {
			log("CRON: Invalid echo");
			continue;
		    }
		    newquant->exp1 = read_exp(&txt);
		    newquant->arg = read_arg(&txt);
		    break;
		case QC_MESSAGE:
		/*	...get_var?(&txt);*/
		    newquant->arg = read_arg(&txt);
		    break;
		case QC_SPEED:
		    if(!strcmp(txt,"fast"))
			newquant->data=SPEED_FAST;
		    else if(!strcmp(txt,"medium"))
			newquant->data=SPEED_MEDIUM;
		    else if(!strcmp(txt,"slow"))
			newquant->data=SPEED_SLOW;
		    break;
		case QC_RESTART: /* Args can be ignored */
		    break;
		case QC_IF:
		    read_bool(newquant,&txt);
		    break;
		case QC_WHILE:
		    read_bool(newquant,&txt);
		    break;
		default:
		    break;
	    }
	}
    }
    fclose(fl);
}

void temp_cron_hack()
{
    /* TMEP TEMP TEMP TEMP */
    struct cron *temp_cron;
    struct char_data *mob;
    struct cron_var *temp_var;

    /* TEMP TEMP TEMP TEMP TEMP TEMP */
    /* way to force a test cron to be created before we create the new zones*/
    temp_cron=new_cron();
    mob=load_bio_to(701,VIRTUAL,real_room(3001));
    free(mob->player.name);
    free(mob->player.short_descr);
    mob->player.name = strdup("mayor");
    mob->player.short_descr = strdup("Hey, it's the mayor!");
    mob->id = 12345;

    CREATE(temp_var, struct cron_var, 1);

    temp_var->type = VAR_ACTOR;
    temp_var->num = 1;
    temp_var->value = 12345;
    temp_var->next = NULL;
    temp_var->name = "1";

    temp_cron->vars = temp_var;
    temp_cron->mode = CRON_GO;

    temp_cron->script = get_script(3000);

    temp_cron->executing = temp_cron->script->first;

    enqueue_cron(temp_cron,SPEED_FAST);
}

struct script *get_script(int num)
{
    struct script *s;

    for(s=cronbase.scripts;s;s=s->next_script)
	if(num==s->number)
	    return(s);
    return(NULL);
}

struct cron *new_cron()
{
    struct cron *new;

    CREATE(new,struct cron,1);

    new->vars=NULL;
    new->stack=NULL;

    return(new);
}

void free_cron(struct cron *c)
{
    struct cron_var *v,*temp;

    /* Eliminate vars */
    for(v=c->vars;v;v=temp) {
	temp=v->next;
	free(v);
    }

    /* Unlink the cron */
    unqueue_cron(c);

    free(c);
}

void init_list(struct cron_list *l)
{
    l->Head = (struct cron *)&l->Tail;
    l->Tail = NULL;
    l->TailPred = (struct cron *)&l->Head;
}

void enqueue_cron(struct cron *c,int where)
{
    switch(where) {
	case SPEED_FAST:
	    c->Succ = (struct cron *)&cronbase.fast_crons.Tail;
	    c->Pred = cronbase.fast_crons.TailPred;
	    cronbase.fast_crons.TailPred->Succ = c;
	    cronbase.fast_crons.TailPred = c;
	    break;
	case SPEED_MEDIUM:
	    c->Succ = (struct cron *)&cronbase.medium_crons.Tail;
	    c->Pred = cronbase.medium_crons.TailPred;
	    cronbase.medium_crons.TailPred->Succ = c;
	    cronbase.medium_crons.TailPred = c;
	    break;
	case SPEED_SLOW:
	    c->Succ = (struct cron *)&cronbase.slow_crons.Tail;
	    c->Pred = cronbase.slow_crons.TailPred;
	    cronbase.slow_crons.TailPred->Succ = c;
	    cronbase.slow_crons.TailPred = c;
	    break;
    }
}

void unqueue_cron(struct cron *c)
{
    c->Succ->Pred = c->Pred;
    c->Pred->Succ = c->Succ;
    c->Pred = c->Succ = NULL;
}

char *read_one_arg(char **input)
{
    int i;
    char *new=NULL,*start;

    while(isspace(**input))
	(*input)++;

    start=*input;

    for(i=0;**input && **input!='+' && **input!=' ';i++,(*input)++)
	;

    if(i) {
	CREATE(new,char,i+1);
	strncpy(new,start,i);
	new[i]='\0';
    }

    return(new);
}

char *read_arg(char **input)
{
    int i;
    char *new=NULL,*start;

    while(isspace(**input))
	(*input)++;

    start=*input;

    for(i=0;**input && **input!='+' && **input!=';';i++,(*input)++)
	;

    if(i) {
	CREATE(new,char,i+1);
	strncpy(new,start,i);
	new[i]='\0';
    }

    return(new);
}

void read_bool(struct cron_quantum *c,char **input)
{
    char *i;

    while(isspace(**input))
	(*input)++;

    if(!strncasecmp(*input,"true",4)) {
	c->data = BOOL_TRUE;
	*input +=4;
	return;
    } else if(!strncasecmp(*input,"false",5)) {
	c->data = BOOL_FALSE;
	*input +=5;
	return;
    }
return;

    c->exp1 = read_exp(input);

    while(isspace(**input))
	(*input)++;

    /* Now bite off the operator, if it exists */

    i=*input;

    if(!strncmp(i,"<>",2)) {
	c->data = BOOL_NOT_EQUAL;
	i +=2;
    } else if(!strncmp(i,"=",1)) {
	c->data = BOOL_EQUAL;
	i++;
    }

    c->exp2 = read_exp(input);
}

int read_op(char **input)
{
    return OP_PLUS; /* not being used, so it doesn't matter */
}

bool end_exp(char **text)
{
    char *check;

    check=*text;

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

    if(*check==':' || *check=='\n' || *check=='\r' || *check=='\0') {
	*text=++check;
	return(TRUE);
    }

    return(FALSE);
}

struct exp *read_term(char **input)
{
    struct exp *new;
    int i;
    char buf[100];

    while(isspace(**input))
	(*input)++;

    if(isdigit(**input) || **input=='-') {
	i=atoi(*input);
	CREATE(new,struct exp,1);
	new->type=EXP_CONSTANT;
	new->data=i;

	/* Now, wind the pointer past the number */
	if(**input=='-')
	    (*input)++;
	while(isdigit(**input))
	    (*input)++;

	/* And return our value */
	return(new);
    } else if(**input=='$') {
	i=0;
	while(isalpha(**input)) {
	    buf[i]=**input;
	    i++; (*input)++;
	}
	buf[i]='\0';
	new->type = EXP_VARIABLE;
	new->name = strdup(buf);
    } /*other types here */

    return(NULL);
}

struct exp *read_factor(char **input)
{
    struct exp *new,*res;

    if(**input=='(') {
	res=read_exp(input);
	/* check for matching paren */
	return(res);
    }

    res=read_term(input);

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

    if(**input=='*' || **input=='/') {
	CREATE(new,struct exp,1);
	new->left=res;
	new->right=read_exp(input);
	new->type=EXP_OPERATOR;
	new->data=(**input=='*' ? OP_MULT : OP_DIVIDE);
	return(new);
    }
    return(res);
}

struct exp *read_exp(char **input)
{
    struct exp *new,*res;

    res=read_term(input);

    if(end_exp(input))
	return(res);

    if(**input=='+' || **input=='-') {
	CREATE(new,struct exp,1);
	new->left=res;
	new->right=read_exp(input);
	new->type=EXP_OPERATOR;
	new->data=(**input=='+' ? OP_PLUS : OP_MINUS);
    } else {
	log("error in read_exp");
	return(res);
    }
    return(new);
}

/* Now how to evaluate this thing */
int eval_exp(struct cron *c,struct exp *exp)
{
    int result;
    struct cron_var *var;

    if(!exp) { /* Should we get this at all? */
	log("BUG: Null expression passed to eval_exp!");
	return(0);
    }

    switch(exp->type) {
	case EXP_CONSTANT:
	    return(exp->data);
	    break;
	case EXP_OPERATOR:
	    switch(exp->data) {
		case OP_PLUS:
		    return(eval_exp(c,exp->left) +
		       eval_exp(c,exp->right));
		    break;
		case OP_MINUS:
		    return(eval_exp(c,exp->left) -
		       eval_exp(c,exp->right));
		    break;
		case OP_MULT:
		    return(eval_exp(c,exp->left) *
		       eval_exp(c,exp->right));
		    break;
		case OP_DIVIDE:
		    result=eval_exp(c,exp->right);
		    if(result == 0) {
			sprintf(log_buf,"CRON: Divide by zero in %s",c->script->name);
			log(log_buf);
			return(0);
		    }
		    return(eval_exp(c,exp->left) / result);
		    break;
		case OP_MODULUS:
		    return(eval_exp(c,exp->left) %
		       eval_exp(c,exp->right));
		    break;
		default:
		    break;
	    }
	    break;
	case EXP_FUNCTION:
	    switch(exp->data) {
		case FUNC_ROOM_OF:
		    break;
		case FUNC_ZONE_OF:
		    break;
		default:
		    break;
	    }
	    break;
	case EXP_VARIABLE:
	    var = get_var(c,VAR_NUM,exp->name);
	    return(var->value);
	    break;
	default:
	    break;
    }
    return(NULL);
}

bool eval_bool(struct cron *c,struct cron_quantum *q)
{
    int res1,res2;

    if(q->data!=BOOL_TRUE && q->data!=BOOL_FALSE) {
        res1 = eval_exp(c,q->exp1);
        res2 = eval_exp(c,q->exp2);
    }

    switch(q->data) {
	case BOOL_EQUAL:
	    return(res1==res2);
	    break;
	case BOOL_NOT_EQUAL:
	    return(res1!=res2);
	    break;
	case BOOL_GREATER:
	    return(res1>res2);
	    break;
	case BOOL_LESS:
	    return(res1<res2);
	    break;
	case BOOL_EQUAL_GREATER:
	    return(res1>=res2);
	    break;
	case BOOL_EQUAL_LESS:
	    return(res1<=res2);
	    break;
	case BOOL_TRUE:
	    return TRUE;
	    break;
	case BOOL_FALSE:
	    return FALSE;
	    break;
	default:
	    log("CRON: Bad comparator in eval_bool!");
	    break;
    }
    return(FALSE);
}

/* Support routine for do_cstat */

void cstat_list(struct char_data *ch,struct cron_list *l)
{
    int total=0,wacky=0,running=0,waiting=0,suspended=0;
    struct cron *c;
    char buf[MAX_STRING_LENGTH];

    for(c=l->Head;c->Succ;c=c->Succ) {
	total++;
	switch(c->mode) {
	    case CRON_GO:
		running++;
		break;
	    case CRON_WACKY:
		wacky++;
		break;
	    case CRON_WAIT:
		waiting++;
		break;
	    case CRON_SUSPENDED:
		suspended++;
		break;
	}
    }
    
    sprintf(buf,"%5d  %5d  %5d  %5d  %5d\n",running,waiting,suspended,
	wacky,total);
    send_to_char(buf,ch);
}

/* A command for showing stats about the cron system */
int do_cstat(struct char_data *ch,char *argument,int cmd)
{
    int i;
    char buf[MAX_STRING_LENGTH];
    struct script *s;

    send_to_char("Cron Stats    Run    Wait   Susp   Wacky  Total\n\n",ch);

    send_to_char("Fast crons:   ",ch);
    cstat_list(ch,&cronbase.fast_crons);

    send_to_char("Medium crons: ",ch);
    cstat_list(ch,&cronbase.medium_crons);

    send_to_char("Slow crons:   ",ch);
    cstat_list(ch,&cronbase.slow_crons);

    for(i=0,s=cronbase.scripts;s;s=s->next_script)
	i++;
    sprintf(buf,"\nThere are %d scripts available.\n\r",i);
    send_to_char(buf,ch);
    return OKAY;
}