#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void error(char *s);

struct SpellcheckState {
	int in[2], out[2];
	int pid;
	FILE * tochild, * fromchild;
	char * linebuffer;
	int linebuffer_len;
	char ** suggestions;
	int suggestions_count;
	int suggestions_len;
};

int Spellchecker_initialise(struct SpellcheckState ** scs_ptr) {
	struct SpellcheckState * scs;
	scs = malloc(sizeof(struct SpellcheckState));
	scs->linebuffer = malloc(4096);
	scs->linebuffer_len = 4096;
	scs->suggestions = NULL;
	scs->suggestions_count = 0;
	scs->suggestions_len = 0;
	/* In a pipe, xx[0] is for reading, xx[1] is for writing */
	if (pipe(scs->in) < 0) error("pipe in");
	if (pipe(scs->out) < 0) error("pipe out");

	if ((scs->pid=fork()) == 0) {
		/* This is the child process */
		
		/* Close stdin, stdout, stderr */
		close(0);
		close(1);
		close(2);
		/* make our pipes, our new stdin,stdout and stderr */
		dup2(scs->in[0],0);
		dup2(scs->out[1],1);
		dup2(scs->out[1],2);
		
		/* Close the other ends of the pipes that the parent will use, because if
		 * we leave these open in the child, the child/parent will not get an EOF
		 * when the parent/child closes their end of the pipe.
		 */
		close(scs->in[1]);
		close(scs->out[0]);
		
		/* TODO: Get this from a configuration file */
		/* Over-write the child process with the hexdump binary */
		execl("/usr/bin/aspell", "aspell", "pipe", (char *)NULL);
		
		/* Report Error */
		return 1;
	}
	
	/* This is the parent process */
	/* Close the pipe ends that the child uses to read from / write to so
	 * the when we close the others, an EOF will be transmitted properly.
	 */
	close(scs->in[0]);
	close(scs->out[1]);

	//read(scs->out[0], scs->linebuffer

	scs->tochild = fdopen(scs->in[1], "a");
	scs->fromchild = fdopen(scs->out[0], "r");

	/* Welcome Banner */
	fgets(scs->linebuffer, scs->linebuffer_len, scs->fromchild);
	
	*scs_ptr = scs;
	return 0;
}

/**
 * 
 * @return zero if it is alpha only, count of non alpha characters
 */
static int Spellchecker_isAlphaOnly(char * word) {
	int len, i, alnum;
	alnum = 0;
	len = strlen(word);
	for (i = 0; i < len; i ++) {
		if (!((word[i] >= 'a' && word[i] <= 'z') || (word[i] >= 'A' && word[i] <= 'Z') || word[i] == '\'' || word[i] == '-')) {
			alnum ++;
		}
	}
	return alnum;
}

int Spellchecker_suggest(struct SpellcheckState * scs, char * word) {
	int len;
	int retval;
	int i;
	int totallen;
	char * buf;

	if (scs->suggestions != NULL) {
		free(scs->suggestions);
		scs->suggestions = NULL;
		scs->suggestions_count = 0;
		scs->suggestions_len = 0;
	}
	scs->suggestions_count = 0;
	
	retval = Spellchecker_isAlphaOnly(word);
	if (retval) {
		scs->suggestions_count = retval;
		return -1;
	}

	fprintf(scs->tochild, "%s\n", word);
	fflush(scs->tochild);
	/* terminated by \0 fo us ;-) */
	buf = fgets(scs->linebuffer, scs->linebuffer_len, scs->fromchild);
	if (buf == NULL) {
		return -2;
	}
	len = strlen(scs->linebuffer);

	scs->suggestions = malloc(128 * sizeof(char *));
	scs->suggestions_len = 128;

	if (buf[0] == '&') {
		char * colonLoc;
		char * bufStartPos;
		bufStartPos = 0;
		colonLoc = strchr(buf, ':');
		colonLoc++;
		i = colonLoc - buf;
		while (buf[i] != '\n') {
			if (buf[i] == ',') {
				buf[i] = '\0';
				i++;
				scs->suggestions[scs->suggestions_count++] = bufStartPos;
			} else if (buf[i] == ' ') {
				i++;
				bufStartPos = buf + i;
			} else {
				i++;
			}
		}
		if (buf[i] == '\n') {
			buf[i] = '\0';
			scs->suggestions[scs->suggestions_count++] = bufStartPos;
		}
		totallen = i + 1;
		buf = fgets(scs->linebuffer + totallen, scs->linebuffer_len - totallen, scs->fromchild);
		//fprintf(stdout, "length: %d\n", strlen(buf));
	}
	return scs->suggestions_count;
}

int Spellchecker_finalise(struct SpellcheckState * scs) {
	fclose(scs->tochild);
	fclose(scs->fromchild);
	close(scs->in[1]);
	close(scs->out[0]);
	scs->tochild = NULL;
	scs->fromchild = NULL;
	free(scs->linebuffer);
	scs->linebuffer_len = 0;
	if (scs->suggestions != NULL) { free(scs->suggestions); }
	scs->suggestions = NULL;
	scs->suggestions_count = 0;
	scs->suggestions_len = 0;
}

void main(int argc, char * argv[]) {
	struct SpellcheckState * app_scs;
	int res, i;

	res = Spellchecker_initialise(&app_scs);
	fprintf(stdout, "Spellchecker_initialise(): %d\n", res);

	res = Spellchecker_suggest(app_scs, argv[1]);
	fprintf(stdout, "Spellchecker_suggest(): %d\n", res);
	if (res > 0) {
		fprintf(stdout, "Incorrect Spelling, %d suggestions.\n", app_scs->suggestions_count);
		for (i = 0; i < app_scs->suggestions_count; i++) {
			fprintf(stdout, "%d:%s, ", i, app_scs->suggestions[i]);
		}
		fprintf(stdout, "\n");
	} else if (res == 0) {
		fprintf(stdout, "Correct Spelling!\n");
	} else {
		fprintf(stdout, "Some kind of error: %d\n", res);
	}

	res = Spellchecker_suggest(app_scs, argv[2]);
	fprintf(stdout, "Spellchecker_suggest(): %d\n", res);
	if (res > 0) {
		fprintf(stdout, "Incorrect Spelling, %d suggestions.\n", app_scs->suggestions_count);
		for (i = 0; i < app_scs->suggestions_count; i++) {
			fprintf(stdout, "%d:%s, ", i, app_scs->suggestions[i]);
		}
		fprintf(stdout, "\n");
	} else if (res == 0) {
		fprintf(stdout, "Correct Spelling!\n");
	} else {
		fprintf(stdout, "Some kind of error: %d\n", res);
	}
	
	Spellchecker_finalise(app_scs);
	fprintf(stdout, "Spellchecker_finalise()\n");
}

void error(char *s) {
	perror(s);
	exit(1);
}



