// CSS parser along very much the same lines as the sam/html.c html parser

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

#include "css.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

extern int emsg(const char * arg_list, ...);
//#define dmsg imsg_surgeweb

Css *css_new(int _bytes)
{
	Css *h = calloc(1,sizeof(Css));
	h->work = malloc(_bytes+1);
	h->size = _bytes;
	return h;
}

void css_reset(Css *h)
{
	h->upto = h->bytes = 0;
}

void css_free(Css **h)
{
	if (!h || !*h) return ;
//	css_tag_free(&(*h)->tag);
	if ((*h)->logmsg) free((*h)->logmsg);
	free((*h)->work);
	free(*h);
	*h = NULL;
}

void css_logmsg(Css *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 isStartCssComment(s)	(s[0] == '/' && s[1] == '*')
#define StartCssCommentLength	2

#define isEndCssComment(s)		(s[0] == '*' && s[1] == '/')
#define EndCssCommentLength	2

#define isRuleOpen(s)	(s[0] == '{')
#define isRuleClose(s)	(s[0] == '}')

#define isRuleEnd(s)	(s[0] == ';')

#define isComma(s)		(s[0] == ',')

#define isEOL(s)		(iseol(*s))

#define isMediaStart(s)	(s[0] == '@' && s[1] == 'm' && s[2] == 'e' && s[3] == 'd' && s[4] == 'i' && s[5] == 'a')
#define isMediaOpen(s)	(s[0] == '{')
#define isMediaClose(s)	(s[0] == '}')


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

int css_scan(Css *h, FILE *f)
{
	css_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 css_scan_ex(h,(void*)f);
}

void css_setup(Css *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;
	h->want_selector=TRUE;
}
int css_scan_ex(Css *h, void *d)
{
	int loop_check;

//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;	
//#else
	char *p,*s;
//#endif
	int bytes_read;
	int lastupto= -2;

	// Sanity check 
	if (!h || !d) return CSS_PARAM;

	loop_check = 0;

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

	// If end of html buffer is actually full, condense the stuff we have
	if (h->bytes >= h->size-1 && h->upto > 0) {			
		safe_memmove(h->work,&h->work[h->upto],h->bytes-h->upto,h->work,h->size+1);
		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<100000){
			h->work = realloc(h->work,h->size*2+1);
			h->size = h->size*2;
//			dmsg("Css processing buffer grown to %d bytes", h->size);
		}else{
			if(!h->warned){
				emsg("Css processing buffer grow failed - MESSAGE CSS TRUNCATED FOR DISPLAY");
			}
			h->warned = TRUE;
		}
	}

	// If any data in generic inbut 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",i,buffer_bytes(d),h->bytes-h->upto);

	// Further source buffer sanity checks
	if (h->error(d)) return CSS_FILE;

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

	// Reset our tokenising parameters
	h->text = h->raw = NULL;
	h->text_len = 0;
	h->raw_len = 0;

	// Now actually start processing it, h->raw & h->raw_len used externally, locally have ptr p
	h->raw = &h->work[h->upto];							

	// Handle it if if this is a whitespace block
	for(p = &h->work[h->upto]; sisspace(*p); p++) {		
		if (iseol(*p)) {
			if (*p == '\r' && *(p+1)) p++;
			p++;
			h->upto = (int) (p - h->work);
			return CSS_EOL;
		}
		h->raw_len++;
	}
	h->upto = (int) (p - h->work);
	if (h->raw_len) return CSS_SPACE;

	// Ignore html comments
	if(isStartComment(p)) {
		h->raw = p;
		h->raw_len = StartCommentLength;
		h->text = h->raw + StartCommentLength;
		h->upto=(int) ((p - h->work) + StartCommentLength);
		return CSS_IGNORE;
	}

	// Carry on our main loop (when is this triggered???)
	if (!*p){
		emsg("SURGEWEB BADNESS");									
		return CSS_FILE;
	}

	// Handle special CSS termination characters
	h->raw = h->text = p;
	if (isMediaStart(p)){
		h->start_media=TRUE;
		h->in_media=FALSE;
		h->want_selector=FALSE;
	}

	if (!h->start_media){
		if (h->want_selector){
			// Looking for css selectors			
			if (isRuleOpen(p)){
	//			do stuff
				h->raw_len++; p++; h->upto=(int)(p - h->work);
				h->want_selector=FALSE;
				return CSS_DECL_START;
			}
			if (isComma(p)){
	//			do stuff
				h->raw_len++; p++; h->upto=(int)(p - h->work);
				return CSS_SELECTOR_COMMA;
			}
		}else{
			// Looking for css rules
			if(isRuleClose(p)){
	//				do stuff
				h->raw_len++; p++; h->upto=(int)(p - h->work);
				h->want_selector=TRUE;
				return CSS_DECL_END;
			}
		}
	}

	// Loop over each character in buffer until we have a token
	while(TRUE) {
		// End of buffer reached?
		if(p >= h->work+h->bytes){ 
			goto try_more;
		}

		//		Handle eof / eol / etc;
		if (isEOL(p)){
			h->raw_len+=1; p+=1; h->upto=(int)(p - h->work);
			return CSS_EOL;
		}

		if(isEndComment(p)) {
			h->raw = p;
			h->raw_len = EndCommentLength;
			h->text = h->raw + EndCommentLength;
			h->upto = (int)(p - h->work) + EndCommentLength;
			return CSS_IGNORE;
		}

		if (isStartCssComment(p)) {
			h->raw = p;
			h->raw_len = StartCssCommentLength;
			h->text = h->raw + StartCssCommentLength;

			h->text_len = 0;
			for(s = h->text; *s; s++) {
				if (isEndCssComment(s)) break;
				h->text_len++;
			}
			if (!*s) goto try_more;

			h->raw_len += h->text_len + EndCssCommentLength;
			h->upto = (int)(s - h->work) + EndCssCommentLength;

			return CSS_COMMENT;
		}

		if(h->start_media){
			if( isMediaOpen(p) ){
				h->start_media=FALSE;
				h->in_media=TRUE;
				h->want_selector=TRUE;
				h->text_len=h->raw_len++; p++; h->upto=(int)(p - h->work);
				return CSS_MEDIA_START;
			}
			p++;
			h->raw_len++;
			continue;
		}
		if(h->in_media){
			if( isMediaClose(p) ){
				h->in_media=FALSE;
				h->text_len=h->raw_len++; p++; h->upto=(int)(p - h->work);
				return CSS_MEDIA_END;
			}
		}

		if (h->want_selector){
			if(isRuleOpen(p) || isComma(p) || isEOL(p) || isStartComment(p) ){
//				trim_white_space_end
				for(; sisspace(p[-1]); p--) h->raw_len--;
				
				h->text_len=h->raw_len; h->upto=(int)(p - h->work);
				return CSS_SELECTOR;
			}
		}else{
			// Looking for css rules
			if(isRuleEnd(p) || isRuleClose(p) || isEOL(p) || isStartComment(p)){
				if (isRuleEnd(p)){
					p++;
					h->raw_len++;
				}else{
//					trim_white_space_end
					for(; sisspace(p[-1]); p--) h->raw_len--;
				}
				h->text_len=h->raw_len; h->upto=(int)(p - h->work);
				return CSS_DECL;
			}
		}
		p++;
		h->raw_len++;
	}

	// SHOULD NEVER GET HERE!! 
	return CSS_FILE;

try_more:
	if (bytes_read)
		goto try_again;

	return CSS_MORE;	
}

#ifdef __junk__
void css_write_tag(Tag *tag, FILE *f, void(*fn)(FILE *,Tag *,char *,char *,int))
{
	Wordlist *w;
	Pair *p;

	if (tag->isendtag) fprintf(f,"</%s",tag->name);
	else fprintf(f,"<%s",tag->name);
	for(p = 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 css_write_tag_simple(Css *h, Tag *tag, void *d)
{
	Wordlist *w;
	Pair *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 = 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 css_tag_copy(Tag *dst, Tag *t) 
{
	ncpy(dst->name,t->name,NAMELEN-1);
	pair_copy(&dst->attributes,t->attributes);
	pair_reverse(&dst->attributes);
	word_copy(&dst->flags,t->flags);
	word_reverse(&dst->flags);
	dst->isendtag = t->isendtag;
}

void css_tag_free(Tag *tag) {
	pair_free(&tag->attributes);
	word_free(&tag->flags);
	tag->isendtag = FALSE;
	*tag->name = 0;
}
#endif

//must match enum in css.h
char *css_strings[]={"CSS_PARAM","CSS_FILE","CSS_BUF","CSS_END","CSS_SELECTOR","CSS_SELECTOR_COMMA","CSS_DECL_START","CSS_DECL","CSS_DECL_END","CSS_COMMENT","CSS_SPACE","CSS_EOL","CSS_IGNORE","CSS_MORE"};

void css_log(Css *h,char*txt, char*tag, int k, int cur_buffer_size, int fn(const char * arg_list, ...))
{
	char bf[BFSZ];
	static int count=0;
	if (fn==NULL) return;	
	ncpy(bf,tag,BFSZ-1);
	strip_eol(bf);
	fn("%s%s [%s] tag count=%d buffer(external)=%d buffer(internal)=%d [%d]", txt, bf, css_strings[k], ++count, cur_buffer_size,h->bytes-h->upto, h->bytes-h->upto+lib_strlen(bf));
}
