/* Copyright (c) 2004-2006 Sam Trenholme
 *
 * TERMS
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * This software is provided 'as is' with no guarantees of correctness or
 * fitness for purpose.
 */

#include "../libs/JsStr.h"
#include "../libs/MaraHash.h"
#include "../MaraDns.h"
#include "Csv2_database.h"
#include "Csv2_read.h"
#include "Csv2_functions.h"

/* These are funcitons that read from a file character-by-character,
 * performing macro processing if necessary */

/* Open up a file for reading; the file is a js_string object 
 *
 * Input: Filename to open for reading
 * Output: csv2_read object pointing to the file in question, 0 on
 *         error opening up the file
 */

csv2_read *csv2_open(js_string *filename) { 
	csv2_read *out;
	int c;

	if(filename->unit_size != 1) {
		return 0;
	}

	if((out = js_alloc(sizeof(csv2_read),1)) == 0) {
		return 0;
	}

	if((out->filename = js_alloc(filename->unit_count + 3,1)) == 0) {
		js_dealloc(out);
		return 0;
	}

	if(js_js2str(filename,out->filename,filename->unit_count + 1) == 
			JS_ERROR) {
		js_dealloc(out);
		return 0;
	}

	/* Initialize all of the fields */
	out->stack = 0;
	out->stack_height = 0;
	out->mnum = 0;
	out->mplace = 0;
	out->cplace = 0;
	for(c = 0 ; c < 19 ; c++) {
		out->context[c] = 0;
	}
	out->linenum = 1;
	out->justread = -328; /* Make this a magic "beginning of file" 
                                 number */
	out->unicode = -1;
	out->ok_to_read = 1;
	out->seen_bug_msg = 0;

	if((out->reading = fopen(out->filename,"rb")) == 0) {
		js_dealloc(out);
		return 0;
	}

	return out;
}

/* Close an open file and dealloc the memory used by the pointer
 *
 * input: csv2_read pointer with the file
 *
 * output: JS_ERROR on error; JS_SUCCESS on success
 */

int csv2_close(csv2_read *file) {
	csv2_file *v, *w;
	int a = 0;
	if(fclose(file->reading) != 0) {
		return JS_ERROR;
	}

	v = file->stack;
	while(v != 0 && a < 1000) {
		w = v->next;
		fclose(v->reading);
		js_dealloc(v->filename);
		js_dealloc(v);
		v = w;
		a++;
	}

        js_dealloc(file->filename);
		
	if(js_dealloc(file) != JS_SUCCESS) {
		return JS_ERROR;
	}

	return JS_SUCCESS;
}

/* Read a single charcater from an open file; we will eventually place 
 * macro processing in this.  The character is returned as an int so
 * we can eventually interpret a utf-8 sequence as a big number (reading
 * multiple bytes in the process).  Negative numbers indicate errors.
 */

int csv2_readchar(csv2_read *file) {
	int out = -2; /* EOF by default */
	char *mstrn = "NO MACROS YET**";

	/* We don't read if we shouldn't */
	if(file->ok_to_read != 1) {
		if(file->seen_bug_msg == 0) {
		    printf("BUG: The code shouln't be trying to read from");
		    printf(" a closed stream!\n");
		    file->seen_bug_msg++;
		}
		return -2; /* Give them an EOF */
	}

	/* If we are inside a macro (right now the only supported macro
	 * is a bogus macro that says "*NO MACROS YET**"), we get 
	 * characters from the macro */
	if(file->mnum == -1) { /* "NO MACROS YET" */
		if(file->mplace < 0 || file->mplace >= 14) {
			file->mplace = 0;
			file->mnum = 0;
		}
		else {
			out = (int)mstrn[file->mplace];
			if(out == 0) {
				file->mplace = 0;
				file->mnum = 0;
			}
			else {
				file->mplace++;
				file->justread = out;
				return out;
			}
		}
	}

	/* If we are not inside a macro, we just read from the file */
	if(file->mnum == 0) {
read_char:
		out = getc(file->reading);
		file->context[file->cplace++] = out;
		if(file->cplace == 19) { file->cplace = 0; }
		if(out == '\n') {
			file->linenum++;
		}
		if(out == EOF) {
			if(file->stack != 0) {
				csv2_pop_file(file);
				goto read_char;
			}
			return -2; /* End of file */
		}
		if(out == '{') { /* Reserved for macro processing */
			/* Right now, return an error */
			csv2_error(file, "The '{' character is currently not "
			           "allowed in zone files.\nPlease use an "
				   "unquoted \\x7b "
				   "if you need this character in a txt or "
				   "raw record.\nSee the csv2_txt man page "
				   "for more information.");
			/*file->mnum = -1; / * "NO MACROS YET" * /
			file->mplace = 0;
			do {
				out = getc(file->reading);
				file->context[file->cplace++] = out;
				if(file->cplace == 19) { file->cplace = 0; }
				if(out == '\n') {
					file->linenum++;
				}
			} while(out != '}' && out != EOF &&
					!feof(file->reading));
			file->justread = '*';
			return '*';*/
		}
		else { 
			file->justread = out;
			return out;
		}
	}
	return out; /* Hmmmm...what should we *really* do here? */
}

/* Get the last character we read again; we make this a function so
 * that this code is written in an OO-style
 * This is not a unicode-aware function; it will return the last octet
 * read, nothing more
 * This will return -328 if we are at the top of the file */
int csv2_justread(csv2_read *file) {
	if(file->ok_to_read != 1) {
		printf("BUG: Don't run csv2_justread on a closed file!\n");
		return -1;
	}
	return file->justread;
}

/* Print out an error; we always close the file on error */
int csv2_error(csv2_read *file, char *why) {
	int c, lastcr;
	printf("Error: ");
	printf("%s\n",why);
	printf("Error is on line ");
	/* If a newline generates an error, this will be confusing
	 * because the parser will report the error being on the next
	 * line number.  We work around this thusly.  */
	if(file->justread == '\n') {
		file->linenum--;
		file->justread = '\0';
	}
	printf("%d",file->linenum);
	printf(" in file ");
	printf("%s\n",file->filename);

	/* Show them the context of the error */
	printf("context of error: ");
	
	c = file->cplace;

	/* If we have any CRs before the character, we don't show
	 * any of the context before the CR; this will hopefully make
	 * the context message useful */
	lastcr = -1;
	do {
		c++; 
		c %= 19;
		if(file->context[c] == '\n' && c != file->cplace) {
			lastcr = c;
		}
	} while(c != file->cplace);
	if(lastcr != -1) {
		c = lastcr + 1;
		c %= 19;
	}

	/* If we're near the beginning of the line */
	if(file->cplace == ((c + 1) % 19) ||
			file->cplace == ((c + 2) % 19) ||
			file->cplace == ((c + 3) % 19) ||
			file->cplace == ((c + 4) % 19)) {
		printf("<near beginning of line> ");
	}

	/* Show them the context */
	do {
		if(file->context[c] >= ' ') {	
			printf("%c",file->context[c]);
		}
		else if(file->context[c] == '\t') {
			printf("   ");
		} else if(file->context[c] > 0) {
			printf("~");
		}
		c++;
		c %= 19;
	} while(c != file->cplace);

	printf(" (closing this file)\n");
	/*csv2_close(file);*/
	file->ok_to_read = 0;
	return JS_SUCCESS;
}

/* Take the current file being read, push that file on the stack, and open
 * up a new file.  If opening the new file fails, we just keep reading
 * from the old file */

int csv2_push_file(csv2_read *file, js_string *filename) {
	csv2_file *o;
	char *nf;
	FILE *nfd;

	if(file->stack_height > 7) {
		return 0;
	}

	if(filename == 0) {
		return 0;
	}

	if(filename->unit_size != 1) {
		return 0;
	}

	if((nf = js_alloc(filename->unit_count + 3,1)) == 0) {
		return 0;
	}

	if(js_js2str(filename,nf,filename->unit_count + 1) == 
			JS_ERROR) {
		js_dealloc(nf);
		return 0;
	}

	if((nfd = fopen(nf,"rb")) == 0) {
		js_dealloc(nf);
		return 0;
	}

	if((o = js_alloc(1,sizeof(csv2_file) + 1)) == 0) {
		js_dealloc(nf);
		fclose(nfd);
		return 0;
	}

	o->filename = file->filename;
	o->reading = file->reading;
	o->next = file->stack;
	file->stack = o;
	file->filename = nf;
	file->reading = nfd;
	file->stack_height++;

	return JS_SUCCESS;

}

/* This does the opposite of the push operation above: This closes the file
 * we're currently reading and pops the file from the top of the stack.
 */

int csv2_pop_file(csv2_read *file) {
	csv2_file *v;
	fclose(file->reading);
	
	js_dealloc(file->filename);
	v = file->stack;

	if(v == 0) {
		csv2_error(file,"Trying to pop from empty stack");
		return 0;
	}

	file->reading = v->reading;
	file->filename = v->filename;
	file->stack = v->next;
	file->stack_height--;
	if(file->stack_height < 0) {
		csv2_error(file,"Trying to pop from empty stack");
		return 0;
	}
	js_dealloc(v);

	return JS_SUCCESS;
}
	
	
/* Method to set the "last read" unicode character in the input stream */
int csv2_set_unicode(csv2_read *file, int32 in) {
	if(file->ok_to_read != 1) {
		printf("BUG: Don't run csv2_set_unicode on a closed file!\n");
		return -1;
	}
	file->unicode = in;
        return 0;
}

/* Method to get the "last read" unicode character in the input stream */
int32 csv2_get_unicode(csv2_read *file) {
	if(file->ok_to_read != 1) {
		printf("BUG: Don't run csv2_get_unicode on a closed file!\n");
		return -1;
	}
	return file->unicode;
}

