#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#define DMID 14
#include "debug.h"

#define DO_DEFS
#include "html.h"
#include "buffer.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

void log_startstop(const char * arg_list, ...);

extern int emsg(const char * arg_list, ...);
extern int imsg(const char * arg_list, ...);
#ifdef _extra_debug_
#define dmsg imsg_surgeweb
#else
extern int dmsg(const char * arg_list, ...);
#endif
/*
void assign(char **to, char *value)
{
	if (*to) free(*to);
	*to = strdup(value);
}
*/

Html *html_new_r(int _bytes, char *file, int line)
{
	Html *h = calloc(1,sizeof(Html));
	h->work = d_mallocid(DMID,_bytes+1,file,line);
	h->size = _bytes;
	return h;
}

void html_reset(Html *h)
{
	h->upto = h->bytes = 0;
}

void html_free(Html **h)
{
	if (!h || !*h) return ;
	html_tag_free(&(*h)->tag);
	if ((*h)->logmsg) free((*h)->logmsg);
	free((*h)->work);
	free(*h);
	*h = NULL;
}

void html_logmsg(Html *h, char *logmsg) {
	if (h->logmsg) free(h->logmsg);
	h->logmsg = strdup(logmsg);
}

//A name consists of a letter followed by letters, digits, periods, or hyphens
#define isNameChar(c)	(sisalnum(c) || c == '.' || c == '-' || c == '_' || c == '|' || c == ':' || c == '?')

#define isStartComment(s)	(s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-')
#define StartCommentLength	4

#define isEndComment(s)		(s[0] == '-' && s[1] == '-' && s[2] == '>')
#define EndCommentLength	3

#define isStartTag(s)	(s[0] == '<' && (sisalpha(s[1]) || s[1]=='?'))
#define isEndTag(s)		(s[0] == '<' && s[1] == '/' && sisalpha(s[2]))
#define isTag(s)		(isStartTag(s) || isEndTag(s))

#define DOCTYPE_LEN		10
#define isDoctype(s)	(strncmpnc(s,"<!DOCTYPE ",DOCTYPE_LEN)==0)

#define StartScriptLength	 7
#define isStartScript(s)	(strncmpnc(s,"<script",StartScriptLength)==0)

#define EndScriptLength	 8
#define isEndScript(s)	(strncmpnc(s,"</script",EndScriptLength)==0)


int atEnd(Html *h, char *s)
{
	if (h->bytes == h->size) return FALSE;
	if (s != &h->work[h->bytes]) return FALSE;
	return TRUE;
}

int html_scan(Html *h, FILE *f)
{
	html_setup(h,
		(size_t(*)(char *,size_t,size_t,void *))fread,
		(size_t(*)(const char *,size_t,size_t,void *))fwrite,
		(int(*)(void *))ferror,
		(int(*)(void *))feof);
	return html_scan_ex(h,(void*)f);
}

void html_setup(Html *h, size_t(*read)(char *,size_t,size_t,void *), size_t(*write)(const char *,size_t,size_t,void *), int(*error)(void *), int(*eof)(void *)) {
	h->read = read;
	h->write = write;
	h->error = error;
	h->eof = eof;
}
int html_bounds(Html *h, char *p, char *whence)
{
	int f=FALSE;
	if (p<h->work) {emsg("html_bounds: p is less than work\n"); f = TRUE; }
	if (p>h->work+h->bytes) {emsg("html_bounds: p is greater than work + bytes\n");  f = TRUE; }
	if (h->upto>h->bytes) {emsg("html_bounds: upto is greater than bytes\n");  f = TRUE; }
	if (h->upto>h->bytes) {emsg("html_bounds: upto is greater than bytes\n");  f = TRUE; }
	if (f) {
#ifdef DO_MAIL
		log_startstop("html_bounds(2.0) work=%p p=%p, upto=%d bytes=%d size=%d %s\n",h->work,p,h->upto,h->bytes,h->size,whence);
#endif
		lib_abort();
	}
	return true;
}
int html_scan_ex(Html *h, void *d)
{
	char name[BFSZ],value[BFSZ*2];//, tmp[BFSZ];
	int loop_check, nattrib;
//MW: not sure what this should be actually... char=>debugger complains when dealing with multilanguage character sets
//	unsigned char=>unix compiler comains, lets deal with it later...
//#ifdef MARIJN_DEBUG
//	unsigned char *p,*s,q;	
//#else
	char *p,*s,q;
//#endif
	int bytes_read,i,j;
	int lastupto= -2;

	// MW: lets add some comments, sanity check 

	if (!h || !d) return HTML_PARAM;

	loop_check = 0;

	// Try again if we hit the end of our internal processing buffer
try_again:

	// MW: If end of html buffer is actually full, condense the stuff we have

//printf("bytes %d size %d upto %d\n",h->bytes,h->size,h->upto);

	html_bounds(h,h->work,"start");
	if (h->bytes >= h->size-1 && h->upto > 0) {			
//printf("DOING COMPRESS bytes %d size %d upto %d\n",h->bytes,h->size,h->upto);
		safe_memmove(h->work,&h->work[h->upto],h->bytes-h->upto,h->work,h->size+1);  // CRASH HERE!!! upto is past bytes...
// Null termination is lost here... 
		h->bytes -= h->upto;
		h->upto = 0;
		dmsg("compressed %d bytes in buffer(internal)=%d",h->bytes-h->upto,h->bytes-h->upto);
	}

	
	// No space left "at the inn" so, grow our internal processing buffer if we need to
	// This is most likely to occur for:
	//  - Long indivitual lines of text > 1KB
	//  - Large html comments > 1KB - in particular the formatting comment blocks in microsoft generated emails
	if (h->size-h->bytes==0){
		if (h->size<2000000){
			h->work = realloc(h->work,h->size*2+1);
			h->size = h->size*2;
			dmsg("Html processing buffer grown to %d bytes", h->size); // chrisp changed to imsg...
		}else{
			imsg("Html processing buffer grow failed - DATA WILL PROBABLY GET LOST"); // chrisp chaged to imsg.
		}
	}

	// MW: if any data in generic input buffer, get as much as we can to our own h->work buffer
	bytes_read = (int) h->read(&h->work[h->bytes],1,h->size-h->bytes,d);	
	lastupto = h->upto;
	if (bytes_read < 0) bytes_read = 0;
	h->bytes += bytes_read;
	h->work[h->bytes] = 0;
	//dmsg("moved %d bytes from buffer(external)=%d to buffer(internal)=%d",bytes_read,buffer_bytes(d),h->bytes-h->upto);


	// MW: several further source buffer sanity checks
	if (h->error(d)) return HTML_FILE;

	if (h->eof(d)) {
		if (h->upto == h->bytes) 
			return HTML_END;
		if (loop_check++ == 10) {
			emsg("html_scan_ex: loop check (%s)",h->logmsg?h->logmsg:"UNKNOWN");
			h->upto = h->bytes = 0;
			return HTML_END;
		}
	}		


	html_tag_free(&h->tag);
	h->text = h->raw = NULL;
	h->text_len = 0;
	h->raw_len = 0;

	// MW: now actually start processing it, h->raw + h->raw_len used externally, locally have ptr p
	h->raw = &h->work[h->upto];							
	// MW: handle it if we start with at a space
	for(p = &h->work[h->upto]; sisspace(*p); p++) {		
		if (iseol(*p)) {
			if (*p == '\r' && *(p+1)=='\n'){
				p++;
			}
			p++;
			h->upto = (int) (p - h->work);  // CXP1 a
			html_bounds(h,p,"step1");	// check p and upto are both valid (within bytes of h->work) 
			return HTML_EOL;
		}
		h->raw_len++;
	}

	h->upto = (int) (p - h->work); // CXP1 b
	html_bounds(h,p,"step2");	// check p and upto are both valid (within bytes of h->work) 
	if (h->raw_len) return HTML_SPACE;

	// MW: When does this get hit??
	if (!*p){  // this is a null end of message. 
		goto try_more;									
	}

	// MW: We are pointing at doctype info
	if (isDoctype(p)) {									
		h->raw_len = DOCTYPE_LEN;
		for(s = &p[DOCTYPE_LEN]; *s && *s != '>'; s++) {
			h->raw_len++;
		}
		if (!*s) goto try_more;
		h->raw_len++;
		// MW: Make sure we know where to start again next time
		h->upto = (int) (s - h->work) + 1;					 // CXP1 c
		html_bounds(h,s,"step3");	// check p and upto are both valid (within bytes of h->work) 


		return HTML_DOCTYPE;
	}

	// Strip scripts out here
	if (isStartScript(p)) {
		int extra_len;
		h->raw = p;
		h->raw_len = StartScriptLength;
		h->text = h->raw + StartScriptLength;

		h->text_len = 0;
		extra_len=0;
		for(s = h->text; *s; s++) {
			if (isEndScript(s)) {
				for(s+=EndScriptLength; *s; s++) {
					if(*s=='>') {
						s++;
						goto break_out;
					}
					extra_len++;
				}
				goto break_out;
			}
			h->text_len++;
		}
break_out:
		if (!*s) goto try_more;

		h->raw_len += h->text_len + EndScriptLength + extra_len + 1;
		h->upto = (int) (s - h->work) + /*EndScriptLength-*/ - extra_len; // Was not catching untidy script closes
		html_bounds(h,s,"step4");	// check p and upto are both valid (within bytes of h->work) 

		return HTML_SCRIPT;
	}

	// MW: deal with actual html tags definitions
	if (isTag(p)) {										
		h->raw = p;
		

		if (!isEndTag(p)) s = p+1;
		else {
			h->tag.isendtag = TRUE;
			s = p+2;
		}


		for(i = 0; isNameChar(*s); s++) {
			h->tag.name[i++] = *s;
			if (i>=NAMELEN-5) break; // to prevent crashes...CHRISP
			h->raw_len++;
		}

		if (!*s) goto try_more;

		h->tag.name[i] = 0;


		if(!sisspace(*s) && *s != '>'){
			// Not really a valid html tag after but lets see what we can do with it... 
			// It is most likely to have been <user@domain.com>, in which case we correct it
			// else truncate it. ie <abc#@!@dskfjsdfk> -> <abc>
			int is_address=FALSE;
			if (*s=='@')
				is_address=TRUE;
			for(; *s && *s != '>'; s++)
				h->raw_len++;
			if (!*s) goto try_more;
			h->upto = (int) (s - h->work) + 1; // CXP1 e
			html_bounds(h,s,"step5");	// check p and upto are both valid (within bytes of h->work) 
			if (is_address){
				h->raw++;
				return HTML_INVALID_EMAIL;
			}
			return HTML_TAG;
		}

		// FAULT MUST BE AFTER HERE i THINK (CHRISP) 
		// MW: only do special allocation etc if tag has attributes; loop for each attribute
		nattrib=0;	// Make sure we apply a sensible limit
		for(; *s && *s != '>'; ) {
			int truncated = FALSE;
			for(; sisspace(*s); s++)
				h->raw_len++;

			if (*s == '>') break;

			for(i = 0; isNameChar(*s); s++) {
				if(i>200){ 
					truncated=TRUE;
					continue;
				}
				name[i++] = *s;
				h->raw_len++;
			}
			name[i] = 0;
			if(truncated)
				dmsg("*** exessively long attribute name - truncated");

			for(; sisspace(*s); s++)
				h->raw_len++;


			if (*s != '=') {
				if (*s == '/') { // <br /> continues here
					s++; continue;
				}
				if (name[0]) {
					nattrib++;
					if(nattrib<20)
						word_add_atend(&h->tag.flags,name);
					continue;
				}
				if(!*s && nattrib<20) 
					goto try_more;
				// MW: fall through here if the html is invalid in some way
				// chrisp, if this flls through, and *s == 0, then we miss the end!!!!
				if (!*s) goto try_more;
			}
			h->raw_len++;
			if (*s!=0) s++;

			for(; sisspace(*s); s++)
				h->raw_len++;

			q = 0;
			if (*s == '\"' || *s == '\'') {
				q = *s;
				h->raw_len++;
				s++;					
			}

			for(i = 0; *s; ) {
				if (q) { if (*s == q) {s++; break;} }
				else if (sisspace(*s) || *s == '>') break;
				value[i++] = *s++;
				if(i>(BFSZ*2)-10){
//					emsg("*** ERROR writing outside buffer *** (crash i==1024!!!) (i==%d)",i);
// BEGIN TEMPORARY HACK TO STOP CRASHING		
//					dmsg("*** ERROR END OF PROCESSING BUFFER HIT (truncating attribute)",i);
					i--;
					h->raw_len--;
// END TEMPORARY HACK TO STOP CRASHING		
				}
				h->raw_len++;
			}
			if (!*s) goto try_more;

			value[i] = 0;
			nattrib++;

			// In the situaltion where tags are encoded as eg: 
			//		style='font: 7pt/normal "Times New Roman"; font-size-adjust: none;'
			// we will output in double quotes so change inner single quotes to double quotes ;-)
			// I think this should not cause any issues.
			for(j=0;j<i;j++){
				if(value[j]=='"') value[j]='\'';
			}

			if(nattrib<20)
				mpair_add_atend(&h->tag.attributes,name,value);
			continue;
		}


		if (!*s) goto try_more;

		h->upto = (int) (s - h->work) + 1; // CXP1 e
		html_bounds(h,s,"step6");	// check p and upto are both valid (within bytes of h->work) 

		return HTML_TAG;
	}


	if (isStartComment(p)) {
		h->raw = p;
		h->raw_len = StartCommentLength;
		h->text = h->raw + StartCommentLength;

		h->text_len = 0;
		for(s = h->text; *s; s++) {
			if (isEndComment(s)) {
				//printf("Comment detected %d\n", s - h->text);
				break;
			}
			h->text_len++;
		}
		if (!*s) {
			// A possible future optimisation is to not start at the beginning of the string next time. 
			// IN the case of CSS in an html comment (as generated by Zix) this results in 500 kB of css in a single
			// html comment block. As we are processing in 1KB chunks, this menas we are scanning the string for end of commment 
			// tag 500 times. Bit nasty but not a total killer. Would rather get current fix (already a huge efficiency boost 
			// in the case) in and leave this as a further improvement for future optimisation.
			goto try_more;
		}

		h->raw_len += h->text_len + EndCommentLength;
		h->upto = (int) (s - h->work) + EndCommentLength; // CXP1 f
		html_bounds(h,s,"step7");	// check p and upto are both valid (within bytes of h->work) 

		return HTML_COMMENT;
	}


	// MW: ok by process of elimination it must be text that we are left with :-)
	h->raw = h->text = p;								
	h->text_len = 0;
	for(s = h->text; *s; s++) {
		if (isStartComment(s) || isTag(s) || iseol(*s)) break;
		h->text_len++;
	}
	//MW: Ignore, buffer full here, we are growing the buffer next call instead if it is too small
	if (!*s /*&& h->bytes < h->size*/) goto try_more;	

	for(; s != h->text && sisspace(s[-1]); s--)
		h->text_len--;

	h->raw_len = h->text_len;
	h->upto = (int) (s - h->work); // CXP1 g
	html_bounds(h,s,"step8");	// check p and upto are both valid (within bytes of h->work) 

	
	return HTML_TEXT;


try_more:
	if (bytes_read)
		goto try_again;


	return HTML_MORE;	
}

void html_write_tag(Tag *tag, FILE *f, void(*fn)(FILE *,Tag *,char *,char *,int))
{
	Wordlist *w;
	MPair *p;

	if (tag->isendtag) fprintf(f,"</%s",tag->name);
	else fprintf(f,"<%s",tag->name);
	for(p = mpair_first(tag->attributes); p; p = p->next) {
		fprintf(f," %s=\"",p->var);
		fn(f,tag,p->var,p->val,lib_strlen(p->val));
		fprintf(f,"\"");
	}
	for(w = tag->flags; w; w = w->next) {
		fprintf(f," %s",w->word);
	}
	fprintf(f,">");
}

void html_write_tag_simple(Html *h, Tag *tag, void *d)
{
	Wordlist *w;
	MPair *p;

	h->write("<",1,1,d);
	if (tag->isendtag) h->write("/",1,1,d);
	h->write(tag->name,1,lib_strlen(tag->name),d);

	for(p = mpair_first(tag->attributes); p; p = p->next) {
		h->write(" ",1,1,d);
		h->write(p->var,1,lib_strlen(p->var),d);
		h->write("=\"",1,2,d);
		h->write(p->val,1,lib_strlen(p->val),d);
		h->write("\"",1,1,d);
	}
	for(w = tag->flags; w; w = w->next) {
		h->write(" ",1,1,d);
		h->write(w->word,1,lib_strlen(w->word),d);
	}
	h->write(">",1,1,d);
}

void html_tag_copy(Tag *dst, Tag *t) 
{
	ncpy(dst->name,t->name,NAMELEN-1);
	mpair_copy(&dst->attributes,t->attributes);
	mpair_reverse(&dst->attributes);
	word_copy(&dst->flags,t->flags);
	word_reverse(&dst->flags);
	dst->isendtag = t->isendtag;
}

void html_tag_free(Tag *tag) {

	mpair_free(&tag->attributes);
	word_free(&tag->flags);
	tag->isendtag = FALSE;
	*tag->name = 0;

}
void html_log(Html *h,char*txt, char*tag, int k, struct BUFFER *buffer, int fn(const char * arg_list, ...))
{
	char bf[BFSZ];
	static int count=0;
	if (fn==NULL || tag==NULL) return;	
	ncpy(bf,tag,BFSZ-1);
	strip_eol(bf);
	fn("%s [%s #%d] [%s]  buffer(external)=%d [sz=%d] buffer(internal)=%d {%d} [sz=%d]", txt, html_strings[k], ++count, bf, buffer_bytes(buffer), buffer_size(buffer),h->bytes-h->upto, h->bytes-h->upto+lib_strlen(bf), h->size);
}


