#include "xmlfast.h"

static struct entityref entities; // tree of entity candidates. generated by calculate

#define mkents(er,N) \
do { \
	er->more = safemalloc( sizeof(struct entityref) * N ); \
	memset(er->more, 0, sizeof(struct entityref) * N); \
	er->children = N; \
} while (0)

#define MAX_ENTITY_LENGTH 5
#define ENTITY_COUNT 5
entity entitydef[] = {
	 { "lt",     "<"  }
	,{ "gt",     ">"  }
	,{ "amp",    "&"  }
	,{ "apos",   "'"  }
	,{ "quot",   "\"" }
};

#define case_wsp   \
		case 0xa  : context->line_number++; \
		case 0x9  :\
		case 0xd  :\
		case 0x20

#ifndef XML_DEBUG
#define XML_DEBUG 0
#endif

#if XML_DEBUG
#define WHERESTR    " at %s line %d.\n"
#define WHEREARG    __FILE__, __LINE__
#define debug(...)   do{ fprintf(stderr, __VA_ARGS__); fprintf(stderr, WHERESTR, WHEREARG); } while(0)
#else
#define debug(...)
#endif

#define DOCUMENT_START 0
#define LT_OPEN        1
#define COMMENT_OPEN   2
#define CDATA_OPEN     3
#define PI             4
#define CONTENT_WAIT   5
#define TAG_OPEN       6
#define TAG_CLOSE      7
#define TEXT_READ      8
#define TEXT_DATA      9
#define TEXT_INITWSP  10
#define TEXT_WSP      11
#define DOCUMENT_ABORTED 12

static char *STATE[DOCUMENT_ABORTED+1] = {
	 "DOCUMENT_START"
	,"LT_OPEN"
	,"COMMENT_OPEN"
	,"CDATA_OPEN"
	,"PI"
	,"CONTENT_WAIT"
	,"TAG_OPEN"
	,"TAG_CLOSE"
	,"TEXT_READ"
	,"TEXT_DATA"
	,"TEXT_INITWSP"
	,"TEXT_WSP"
	,"DOCUMENT_ABORTED"
};

void calculate(char *prefix, unsigned char offset, entity *strings, struct entityref *ents);
void calculate(char *prefix, unsigned char offset, entity *strings, struct entityref *ents) {
	unsigned char counts[256];
	unsigned char i,x,len;
	unsigned int total = 0;
	char pref[MAX_ENTITY_LENGTH];
	struct entityref *curent;
	entity *keep;
	memset(&counts,0,256);
	for (i = 0; i < ENTITY_COUNT; i++) {
		len = strlen(strings[i].str);
		if ( len > offset && strncmp( strings[i].str, prefix, offset ) == 0) {
			counts[ strings[i].str[offset] ]++;
		} else
		if ( len == offset ) {
			
		}
	}
	for (i = 0; i < 255; i++) { if (counts[i]) total++; }
	strncpy(pref,prefix,offset+1);
	if (total == 0) {
		keep = 0;
		for (x = 0; x < ENTITY_COUNT; x++) {
			if ( strncmp( strings[x].str, prefix, offset ) == 0) {
				keep = &(strings[x]);
				break;
			}
		}
		if (keep) {
			ents->entity = keep->val;
			ents->length = strlen(ents->entity);
		} else {
			fprintf(stderr,"not found keep for entity generator\n");
			return;
		}
		return;
	}
	pref[offset+1] = '\0';
	mkents(ents,total);
	curent = ents->more;
	for (i = 0; i < 255; i++) {
		if (counts[i]) {
			curent->c = i;
			pref[offset] = i;
			calculate(pref,offset+1,strings,curent);
			++curent;
		}
	}
	return;
}

void init_entities() {
	calculate("",0,entitydef,&entities);
	return;
}

static inline char * eat_wsp(parser_state * context, char *p) {
	while (1) {
		switch (*p) {
			case 0: return p;
			case_wsp :
				break;
			default:
				return p;
		}
		p++;
	}
}

static inline char * eatback_wsp(parser_state * context, char *p) {
	while (1) {
		switch (*p) {
			case 0: return p;
			case_wsp :
				break;
			default:
				return p;
		}
		p--;
	}
}

static inline char *parse_entity (parser_state * context, char *p) {
	//return p+1;
	struct entityref *cur_ent;
	char *at;
	at = p;
	unsigned int i;
	if (*(p+1) == '#') {
		p+=2;
		wchar_t chr = 0;
		if (*p == 'x') {
			p++;
			while(1) {
				if (*p >= '0' && *p <= '9') {
					chr *= 16;
					chr += (*p++ - '0');
				}
				else
				if (*p >= 'a' && *p <= 'f') {
					chr *= 16;
					chr += (*p++ - 'a' + 10);
				}
				else
				if (*p >= 'A' && *p <= 'F') {
					chr *= 16;
					chr += (*p++ - 'A' + 10);
				}
				else
					break;
			}
		}
		else {
			while(*p >= '0' && *p <= '9') {
				chr *= 10;
				chr += (*p++ - '0');
			}
		}
		if ( *p == ';' ) p++;
		if (chr > 0 && chr <= 0xFFFF) {
			if (context->cb.uchar) context->cb.uchar(context->ctx, chr);
		} else {
			if (context->cb.warn) {
				char back = *p;
				*p = 0;
				context->cb.warn(context->ctx,"Bad entity value %s",at);
				*p = back;
			}
			if (context->cb.bytespart) context->cb.bytespart(context->ctx, at, p - at);
		}
		return p;
	}
	cur_ent = &entities;
	next_ent:
		if (*p == 0) return 0;
		p++;
		if (*p == ';') {
			if (cur_ent && cur_ent->entity) {
				p++;
				goto ret;
			} else {
				goto no_ent;
			}
		}
		for (i=0; i < cur_ent->children; i++) {
			if (cur_ent->more[i].c == *p) {
				cur_ent = &cur_ent->more[i];
				goto next_ent;
			}
		}
		if (cur_ent && cur_ent->entity) {
			goto ret;
		}
	
	no_ent:
		if (p == at) p++;
		if (context->cb.bytespart) context->cb.bytespart(context->ctx, at, p - at);
		return p;
	
	ret:
		if (context->cb.bytespart) context->cb.bytespart(context->ctx, cur_ent->entity, cur_ent->length);
		return p;
}

/*
static void print_chain (xml_node *chain, int depth) {
	int i;
	xml_node * node;
	printf(":>> ");
	for (i=0; i < depth; i++) {
		node = &chain[i];
		printf("%s",node->name);
		if (i < depth-1 )printf(" > ");
	}
	printf("\n");
}
*/

static inline char *parse_attrs(char *p, parser_state * context) {
	void * ctx = context->ctx;
	xml_callbacks * cb = &context->cb;
		char state = 0;
		/*
		 * state=0 - default, waiting for attr name or /?>
		 * state=1 - reading attr name
		 * state=2 - reading attr value
		 */
		char wait = 0;
		char loop = 1;
		char *at,*end;
		p = eat_wsp(context, p);
		while(loop) {
			switch(state) {
				case 0: // waiting for attr name
					//printf("Want attr name, char='%c'\n",*p);
					while(state == 0) {
						switch(*p) {
							case 0   : printf("Document aborted\n");return 0;
							case_wsp : p = eat_wsp(context, p); break;
							case '>' :
							case '?' :
							case '/' : return p;
							default  : state = 1;
						}
					}
					break;
				case 1: //reading attr name
					at = p;
					end = 0;
					//printf("Want = (%c)\n",*p);
					while(state == 1) {
						switch(*p) {
							case 0   : printf("Document aborted\n");return 0;
							case_wsp :
								end = p;
								p = eat_wsp(context, p);
								if (*p != '=') {
									printf("No = after whitespace while reading attr name\n");
									return 0;
								}
							case '=':
								if (!end) end = p;
								if (cb->attrname) cb->attrname( ctx, at, end - at );
								p = eat_wsp(context, p + 1);
								state = 2;
								break;
							default: p++;
						}
					}
					break;
				case 2:
					wait = 0;
					//printf("Want quote (%c)\n",*p);
					while(state == 2) {
						switch(*p) {
							case 0   : printf("Document aborted\n");return 0;
							case '\'':
							case '"':
								if (!wait) { // got open quote
									//printf("\tgot open quote <%c>\n",*p);
									wait = *p;
									p++;
									at = p;
									break;
								} else
								if (*p == wait) {  // got close quote
									//printf("\tgot close quote <%c>\n",*p);
									state = 0;
									if(cb->bytes) cb->bytes( ctx, at, p - at );
									p = eat_wsp(context, p+1);
									break;
								}
							case '&':
								if (wait) {
									//printf("Got entity begin (%s)\n",buffer);
									if (p > at && cb->bytespart) cb->bytespart( ctx, at, p - at );
									if( p = parse_entity(context, p) ) {
										at = p;
										break;
									}
								} else {
									printf("Not waiting for & in state 2\n");
									return 0;
								}
							default: p++;
						}
					}
					break;
				default:
					printf("default, state=%d, char='%c'\n",state, *p);
					return 0;
			}
		}
		return p;
}

void parse (char * xml, parser_state * context) {
	void * ctx = context->ctx;
	xml_callbacks * cb = &context->cb;
	context->line_number = 1;
	char *p, *at, *start, *end, *search, buffer[BUFFER];
	memset(&buffer,0,BUFFER);
	unsigned int state, len;
	unsigned char textstate;
	p = xml;
	/*
	xml_node *seek, *reverse;
	context->chain_size = 64;
	context->root = context->chain = safemalloc( sizeof(xml_node) * context->chain_size );
	*/
	
	context->state = DOCUMENT_START;
	next:
	while (1) {
		switch(*p) {
			case 0: goto eod;
			case '<':
				context->state = LT_OPEN;
				p++;
				switch (*p) {
					case 0: goto eod;
					case '!':
						p++;
						if(*p == 0) goto eod;
						if ( strncmp( p, "--", 2 ) == 0 ) {
							context->state = COMMENT_OPEN;
							p+=2;
							search = strstr(p,"-->");
							if (search) {
								if (cb->comment) {
									cb->comment( ctx, p, search - p );
								}
								p = search + 3;
							} else xml_error("Comment node not terminated");
							context->state = CONTENT_WAIT;
							goto next;
						} else
						if ( strncmp( p, "[CDATA[", 7 ) == 0) {
							context->state = CDATA_OPEN;
							p+=7;
							search = strstr(p,"]]>");
							if (search) {
								if (cb->cdata) {
									cb->cdata( ctx, p, search - p);
								}
								p = search + 3;
							} else xml_error("Cdata node not terminated");
							context->state = CONTENT_WAIT;
							goto next;
						} else
						{
							printf("fuckup after <!: %c\n",*p);
							goto fault;
						}
						break;
					case '?':
						context->state = PI;
						state = 0;
						p++;
						at = p;
						while(state == 0) {
							switch(*p) {
								case 0   : context->state = DOCUMENT_ABORTED; goto eod;
								case_wsp :
									if (p > at) {
										debug("PI: want attrs");
										end = p;
										state = 1;
										break;
									} else {
										printf("CB> Bad pi opening\n");
										goto fault;
									}
								case '?':
									end = p;
									p++;
									if (*p == '>') {
										p++;
										state = 2;
									} else {
										printf("CB> PI not closed: %c\n",*p);
										goto fault;
									}
									break;
								default: p++;
							}
						}
						if (cb->piopen) cb->piopen( context->ctx, at, end - at );
						if (state == 1) {
							if (!( p = parse_attrs(p,context) )) goto fault;
							state = 2;
						}
						debug("CB> Got pi name state=%d next='%c'\n",state,*p);
						if (*p == '?' && *(p+1) == '>') {
							debug("PI correctly closed\n");
							p+=2;
							if (cb->piclose) cb->piclose( context->ctx, at, end - at );
							context->state = CONTENT_WAIT;
							goto next;
						} else {
							if (context->cb.die)
								context->cb.die(context->ctx,"Processing instruction not terminated");
							goto fault;
						}
					case '/': // </node>
						context->state = TAG_CLOSE;
						p++;
						at = p;
						search = index(p,'>');
						if (search) {
							p = search + 1;
							search = eatback_wsp(context, search-1)+1;
							len = search - at;
							// DISABLE BALANCING
							if(cb->tagclose) cb->tagclose(ctx, at, len);
							context->state = CONTENT_WAIT;
							goto next;
							// DISABLE BALANCING
/*
							if (context->depth == 0) {
								if (context->cb.warn)
									context->cb.warn(context->ctx,"Need to close tag upper than root. Ignored");
								context->state = CONTENT_WAIT;
								goto next;
							}
							if (strncmp(context->chain->name, at, len) == 0) {
								if(cb->tagclose) cb->tagclose(ctx, at, len);
								context->depth--;
								context->chain--;
							} else {
								if(len+1 > BUFFER) {
									snprintf(buffer,BUFFER,"%s",at);
								} else {
									snprintf(buffer,len+1,"%s",at);
								}
								//printf("NODE CLOSE '%s' (unbalanced)\n",buffer);
								reverse = seek = context->chain;
								while( seek > context->root ) {
									seek--;
									if (strncmp(seek->name, at, len) == 0) {
										if (context->cb.warn)
											context->cb.warn(context->ctx,"Found early opened node %s",seek->name);
										while(context->chain >= seek) {
											if(cb->tagclose) cb->tagclose(ctx, context->chain->name, context->chain->len);
											safefree(context->chain->name);
											context->chain--;
											context->depth--;
										}
										//optional feature: auto-opening tags
										//for (seek = chain+2; seek <= reverse; seek++) {
										//	//printf("Auto open %s\n",seek->name);
										//	chain++;
										//	curr_depth++;
										//	*chain = *(chain+1);
										//	if(cb->tagopen) cb->tagopen(ctx, chain->name, chain->len);
										//	//print_chain(root, curr_depth);
										//}
										//optional feature: auto-opening tags
										seek = 0;
										break;
									}
								}
								if (seek) {
									if (cb->warn)
										cb->warn(context->ctx,"Found no open node until root for '%s' at line %d, char %d. Ignored",buffer, context->line_number, p - xml);
								} else {
									// TODO ??
								}
							}
							context->state = CONTENT_WAIT;
							goto next;
*/
						} else {
							if (context->cb.die)
								context->cb.die(context->ctx,"close tag not terminated");
							goto fault;
						}
					default: //<node...>
						state = 0;
						context->state = TAG_OPEN;
						while(state < 3) {
							switch(state) {
								case 0:
									at = p;
									while(state == 0) {
										switch(*p) {
											case 0: goto eod;
											case_wsp :
												if (p > at) {
													state = 1;
													break;
												} else {
													if (context->cb.die)
														context->cb.die(context->ctx,"Bad node open");
													goto fault;
												}
											case '/':
											case '>':
												if (p > at) {
													state = 2;
													break;
												} else {
													if (context->cb.die)
														context->cb.die(context->ctx,"Bad node open");
													goto fault;
												}
											default: p++;
										}
									}
									/*
									if (context->depth + 1 > context->chain_size) {
										seek = context->root;
										context->chain_size *= 2;
										context->root = saferealloc(context->root, sizeof(xml_node) * context->chain_size);
										context->chain = context->root + (context->chain - seek);
									}
									if (context->depth++ != 0) context->chain++;
									context->chain->len = p - at;
									context->chain->name = safemalloc( context->chain->len + 1 );
									memcpy(context->chain->name, at, context->chain->len);
									context->chain->name[context->chain->len] = '\0';
									if (cb->tagopen) cb->tagopen( ctx, at, context->chain->len );
									*/
									if (cb->tagopen) cb->tagopen( ctx, at, p - at );
									break;
								case 1:
									len = p - at;
									if (search = parse_attrs(p,context)) {
										p = search;
										state = 2;
									} else {
										goto fault;
									}
								case 2:
									while(state == 2) {
										switch(*p) {
											case 0   : goto eod;
											case_wsp : p = eat_wsp(context, p);
											case '/' :
												if (cb->tagclose) cb->tagclose( ctx, at, len );
												/*safefree(context->chain->name);
												context->chain--;
												context->depth--;*/
												p = eat_wsp(context, p+1);
											case '>' : state = 3; p++; break;
											default  :
												if (context->cb.die)
													context->cb.die(context->ctx,"Bad char '%c' at the end of tag",*p);
												goto fault;
										}
									}
									context->state = CONTENT_WAIT;
									goto next;
							}
						}
				}
				break;
			default:
				context->state = TEXT_READ;
				start = at = p;
				char *lastwsp = 0;
				if (!context->save_wsp) {
					p = eat_wsp(context, p);
					if (p > at) start = at = p;
				}
				textstate = TEXT_DATA;
				while (1) {
					switch(*p) {
						case 0  :
						case '<':
							//if (p > at) {
								if (!context->save_wsp && textstate == TEXT_WSP) {
									//printf("Skip trailing whitespace chardata=%d wspdata=%d\n", lastwsp - at, p - lastwsp);
								} else {
									lastwsp = p;
								}
								if(cb->bytes) {
									if (lastwsp  > at) {
										cb->bytes(ctx, at, lastwsp - at );
									} else {
										if (p > start) cb->bytes(ctx, "", 0 ); // explicitly terminate
									}
								}
							//}
							context->state = CONTENT_WAIT;
							if (*p == 0) goto eod;
							goto next;
						case_wsp :
							if (textstate == TEXT_DATA) { lastwsp = p; }
							textstate = TEXT_WSP;
							p++;
							break;
						default:
							textstate = TEXT_DATA;
							if (*p == '&') {
								if (p > at && cb->bytespart) cb->bytespart(ctx, at, p - at);
								if( p = parse_entity(context,p) ) {
									at = p;
									break;
								} else {
									goto fault;
								}
							}
							p++;
					}
				}
				textstate = TEXT_INITWSP;
				break;
		}
	}
	printf("parse done\n");
	//safefree(context->root);
	return;
	
	eod:
		//printf("End of document, context->state=%d\n",context->state);
		switch(context->state) {
			case DOCUMENT_START:
				printf("Empty document\n");
				return;
			case LT_OPEN:
			case COMMENT_OPEN:
			case CDATA_OPEN:
			case PI:
			case TAG_OPEN:
			case TAG_CLOSE:
				if (context->cb.die)
					context->cb.die(context->ctx,"Bad document end, state = %s",STATE[context->state]);
				break;
			case TEXT_READ:
				printf("Need to call text cb at the end of document\n");
				break;
			case CONTENT_WAIT:
			/*
				if (context->depth == 0) {
					//printf("END ok\n");
				} else {
					printf("Document aborted\n");
					//print_chain(chain,curr_depth);
				}
			*/
				break;
			default:
				printf("Bad context->state %d at the end of document\n",context->state);
		}
	
	fault:
	//safefree(context->root);
	return;
}

void free_state(parser_state * context) {
	//safefree(root);
}
