/**
 * @file detect_keys.c
 * @brief sub-interface to select keyboards
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <malloc.h>

#include <cdebconf/constants.h>

#include "detect_keys.h"

#define MODULE "detect-keys"

#define debug(fmt, args...) syslog(LOG_DEBUG, MODULE ": " fmt, ##args)

enum step_type { step_unknown, step_press_key,
		 step_key_present, step_key_present_plain, step_result };

struct stepdata {
    FILE *step_file;
    int current_step;
    enum step_type type;
    char *symbols;	 /* doubles as the keymap name */
    int *next_steps;     /* zero-terminated */
    int *keycodes;       /* same length */
};

static void stepdata_delete (struct stepdata *sd);

static struct stepdata *
stepdata_new (char *filename)
{
    struct stepdata *sd;

    sd = NEW(struct stepdata);
    sd->symbols = NEW(char);
    *sd->symbols = '\0';
    sd->next_steps = NEW(int);
    *sd->next_steps = -1;
    sd->keycodes = NEW(int);
    *sd->keycodes = -1;

    sd->step_file = fopen(filename, "r");
    if (sd->step_file == NULL) {
        debug("File '%s' open error: %m", filename);
	stepdata_delete(sd);
	return NULL;
    }
    sd->current_step = -1;
    sd->type = step_unknown;
    return sd;
}

static void
stepdata_delete (struct stepdata *sd)
{
    if (sd == NULL)
    	return;
    if (sd->step_file)
    	fclose(sd->step_file);
    free (sd->next_steps);
    free (sd->keycodes);
    free (sd->symbols);
    DELETE(sd);
}

/* Quick hacks to deal with integer lists */
static int
iv_len (int *vec)
{
    int len = 0;
    if (vec == NULL)
    	return 0;
    while(*vec != -1) {
	len++;
	vec++;
    }
    return len;
}


static void
iv_add (int **vec, int num)
{
    int len = iv_len(*vec);
    *vec = realloc(*vec, sizeof(int *)*(len+2));
    (*vec)[len] = num;
    (*vec)[len+1] = -1;
    return;
}

/* ditto string */


/* Actually fetch the Next Thing To DO */
static int
read_step (int step, struct stepdata *data)
{
    char buf[256];
    char *p;

    data->type = step_unknown;
    *data->symbols = '\0';
    *data->next_steps = -1;
    *data->keycodes = -1;

    while(1) {
	p = fgets(buf, sizeof(buf), data->step_file);

	/* If we're leaving the currently-requested step, return OK 
	 * on enf-of-file or when the step number changes.
	 */
	if (p == NULL) { /* error or end of file */
	    if (feof(data->step_file) && (data->current_step == step))
	    	return DC_OK;
	    return DC_NOTOK;
	}
	p[strlen(p)-1] = '\0'; /* kill the newline */

	if (strncmp(buf,"STEP ",5) == 0) {
	    if (data->current_step == step) {
		data->current_step = atoi(buf+5);
		return DC_OK;
	    }
	    data->current_step = atoi(buf+5);
	} else if(data->current_step != step) {
	    /* ignore everything non-interesting */
	    continue;
	} else if (strncmp(buf,"PRESS ",6) == 0) {
	    /* PRESS symbol */
	    int len;
	    if (data->type == step_unknown)
	    	data->type = step_press_key;
	    else if (data->type != step_press_key) {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }

	    len = strlen(data->symbols);
	    data->symbols = realloc(data->symbols,len+strlen(buf)-4);
	    if (len)
	    	strcat(data->symbols, " ");
	    strcat(data->symbols, buf+6);
	} else if (strncmp(buf,"CODE ",5) == 0) {
	    /* CODE keycode nextstep */
	    char *skip;
	    if (data->type != step_press_key) {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    
	    skip = strchr(buf+5,' ');
	    iv_add (&data->next_steps, atoi(skip));
	    iv_add (&data->keycodes, atoi(buf+5));

	} else if (strncmp(buf,"FIND ",5) == 0) {
	    /* FIND symbol */
	    int len;

	    if (data->type == step_unknown)
	    	data->type = step_key_present;
	    else {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    len = strlen(buf+5);
	    data->symbols = realloc(data->symbols, len+1);
	    strcpy(data->symbols, buf+5);
	} else if (strncmp(buf,"FINDP ",6) == 0) {
	    /* FINDP symbol */
	    int len;

	    if (data->type == step_unknown)
	    	data->type = step_key_present_plain;
	    else {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    len = strlen(buf+6);
	    data->symbols = realloc(data->symbols, len+1);
	    strcpy(data->symbols, buf+6);
	} else if (strncmp(buf,"YES ",4) == 0) {
	    /* YES stepnr */
	    if (data->type != step_key_present &&
		data->type != step_key_present_plain) {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    while (iv_len(data->next_steps) < 2)
		iv_add(&data->next_steps,-2);
	    data->next_steps[1] = atoi(buf+4);
	} else if (strncmp(buf,"NO ",3) == 0) {
	    /* NO stepnr */
	    if (data->type != step_key_present &&
		data->type != step_key_present_plain) {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    if (iv_len(data->next_steps) < 1)
		iv_add(&data->next_steps,-2);
	    data->next_steps[0] = atoi(buf+3);

	} else if (strncmp(buf,"MAP ",4) == 0) {
	    /* MAP name */
	    int len;

	    if (data->type == step_unknown)
	    	data->type = step_result;
	    else {
		debug("Line sequence error: %s", buf);
		return DC_NOTOK;
	    }
	    len = strlen(buf+4);
	    data->symbols = realloc(data->symbols, len+1);
	    strcpy(data->symbols, buf+4);

	} else {
	    /* duh */
	    debug("Unknown line: %s", buf);
	    return DC_NOTOK;
	}
    }
}

int
detect_keys(struct frontend *obj, struct detect_keys_frontend *methods,
	    char *filename, char **result)
{
    int step = 0;
    int res;
    struct stepdata *data;

    data = stepdata_new(filename);
    if (!data)
	return DC_NOTOK;
		
    if (data->step_file == NULL) {
	stepdata_delete(data);
	return DC_NOTOK;
    }

    while(1) {
            syslog(LOG_INFO,"BLURB5");
	res = read_step(step, data);
            syslog(LOG_INFO,"BLURB6");
	if (res != DC_OK) {
	    stepdata_delete(data);
	    return res;
	}
	if (data->type == step_press_key) {
	    int code,i,len;

	    len = iv_len(data->keycodes);
	    code = -1;
	    res = (*methods->press_key)(obj,data->symbols,data->keycodes,&code);
            syslog(LOG_INFO,"BLURB");
            
	    if (res != DC_OK) {
		stepdata_delete(data);
		return res;
	    }
	    for (i=0; i < len; i++) {
		if (data->keycodes[i] == code)
		    break;
	    }
            syslog(LOG_INFO,"BLURB2");
	    if (i == len) {
		stepdata_delete(data);
		syslog(LOG_ERR, "Keycode not found: %d", code);
		return DC_NOTOK;
	    }
            syslog(LOG_INFO,"BLUR3");
	    step = data->next_steps[i];
            syslog(LOG_INFO,"BLURB4");

	} else if (data->type == step_key_present ||
		   data->type == step_key_present_plain) {
	    bool is_present;
            syslog(LOG_INFO,"BLURB7");
	    res = (*methods->is_key_there)(
		obj, data->symbols, data->type == step_key_present_plain,
		&is_present);
            syslog(LOG_INFO,"BLURB8");
	    if (res != DC_OK) {
		stepdata_delete(data);
		return res;
	    }
	    step = data->next_steps[is_present ? 1 : 0];

	} else if (data->type == step_result) {
            syslog(LOG_INFO,"BLURB9");
	    *result = data->symbols;
	    data->symbols = NULL; /* to avoid having it freed ... */
	    stepdata_delete(data);
	    syslog(LOG_INFO, "return %s", *result);
	    return DC_OK;

	} else {
	    syslog(LOG_ERR, "Unknown step type: %d",data->type);
	    stepdata_delete(data);
	    return DC_NOTOK;
	}
    }
    /* NOTREACHED */
}

