#include <time.h>

#if !SCREWS_MODULE
 #include "main.h"
 extern char *b;
#endif


#include "Cache.h"
#include "SubLang.h"

sublang_t *sublang;
int nlangs;

long int filelen=0;
int donotexec=0;
char *dip=DIP;
char *buf=0;
char *p=0;

void *resolve_sym(void *handle, const char *symbol)
{
	void *sym;
	
	if(!(sym = dlsym(handle, symbol)))
		printf("%s\n", dlerror());
	return sym;
}

void hsml_init()
{
	int i,j;
	char *mod_path = getenv("HSML_LANG_MOD");
	char def_mod_path[] = ".";
	char path[1023];
	char *langs[]={"c","perl","python","brainfuck",0};//,"lua","java","csharp",0};
	int bytes;
	char *cwd;
	void **ptr;
	
	if(!mod_path) mod_path = def_mod_path;
	
	sublang = (sublang_t *)calloc(sizeof(langs)/sizeof(char**),sizeof(sublang_t));
	
	for(i=0, j=0; langs[i]; i++)
	{
		if(mod_path[0] != '/') bytes = snprintf(path, 1023, "%s/%s/lang_%s.so", cwd = getcwd(NULL, 0), mod_path, langs[i]);
		else bytes = snprintf(path, 1023, "%s/lang_%s.so", mod_path, langs[i]);
		free(cwd);
		
		if(bytes >= 1023){
			continue;
		}
		
		if(!(sublang[j].module = dlopen(path, RTLD_NOW)))
		{printf("%s\n", dlerror());	continue;}

		if(!(sublang[j].lang = resolve_sym(sublang[j].module, "lang"))) continue;

		if(!(sublang[j].file = resolve_sym(sublang[j].module, "file"))) continue;
				
		if(!(sublang[j].init = resolve_sym(sublang[j].module, "init"))) continue;

		if(!(sublang[j].get_path_from_env = resolve_sym(sublang[j].module, "get_path_from_env"))) continue;

		if(!(sublang[j].show_config = resolve_sym(sublang[j].module, "show_config"))) continue;

		if(!(sublang[j].print_begin = resolve_sym(sublang[j].module, "print_begin"))) continue;
		
		if(!(sublang[j].print_end = resolve_sym(sublang[j].module, "print_end"))) continue;

		if(!(sublang[j].print_end_nl = resolve_sym(sublang[j].module, "print_end_nl"))) continue;

		if(!(sublang[j].code_begin = resolve_sym(sublang[j].module, "code_begin"))) continue;

		if(!(sublang[j].code_end = resolve_sym(sublang[j].module, "code_end"))) continue;

		if(!(sublang[j].codevar = resolve_sym(sublang[j].module, "codevar"))) continue;

		if(!(sublang[j].exec = resolve_sym(sublang[j].module, "exec"))) continue;

		if(!(sublang[j].cache_exec = resolve_sym(sublang[j].module, "cache_exec"))) continue;
		
		if(!(ptr = resolve_sym(sublang[j].module, "cache"))) continue;
		*(char *)ptr = cache;
		
		if(!(ptr = resolve_sym(sublang[j].module, "cacherule"))) continue;
		*(char *)ptr = cacherule;
		
		if(!(ptr = resolve_sym(sublang[j].module, "pwd"))) continue;
		*(char **)ptr = pwd;
		
		if(!(ptr = resolve_sym(sublang[j].module, "tmp"))) continue;
		*(char **)ptr = tmp;
		
		if(!(ptr = resolve_sym(sublang[j].module, "cache_output_add"))) continue;
		*ptr = (void *)&cache_output_add;
		
		if(!(ptr = resolve_sym(sublang[j].module, "cache_print"))) continue;
		*ptr = (void *)&cache_print;
		
		if(!(ptr = resolve_sym(sublang[j].module, "cache_exec_add"))) continue;
		*ptr = (void *)&cache_exec_add;
		
		sublang[j].init();
		j++;
	}
	nlangs = j;
	for(i = 0; i < j; i++)
	{
		sublang[i].show_config();	
	}
}

void show_lang_config()
{
	int i;
	
	for(i=0; sublang[i], i<=nlangs; i++)
		sublang[i].show_config();
}

bool
isOk(file1,file2)
	char *file1; /* Source File */
	char *file2; /* Binary File */
{
	
	struct stat f1,f2;
	if (
	stat(file1,&f1) |
	stat(file2,&f2)
	   ) return false; /* ErrOccRrd */
	if (f1.st_mtime>f2.st_mtime)
		return false;
	return true;
}

void
subLang_doExec(file,lang)
	char *file;
	int lang;
{
	// call lang module exec func
	sublang[lang].exec(file, buf, tmp);
	
	/* XXX type another message ;) */
	fprintf(stderr,"Error executing script. Check language environs with '-l\n");
	sublang[lang].show_config();
	perror("PERROR MSG: ");
}

/* TODO : tokenizeBUffer doesn't cleans all params */
char *
tokenizeBuffer(buf)
	char *buf;
{
	// XXX must strdup, etc. tokenizeBuffer, return malloqed buffer
	char *p=buf;

	/* Clear $, etc.. */
	while(1)
	{
		p=strchr(p,'\"');
		if (!p) break;
		p[0]='\'';
	}
	return buf;
}

int
SubLang_include(files,lang)
	char *files;
    int lang;
{
	FILE *fd;
	char *next, *tmpbuf, *end = p+3, *old_b;
	int b_dif;
	long flen;
	int i=0;
	
	if(files[0] == ' ') files++;

	while(next = strchr(files,' '))
	{
		next[0] = '\0';
		next++;
		
		while(sublang[i].file)
		{
		if (strstr(files,sublang[i].file))
			break;
		i++;
		}
		if (sublang[i].file)
		{
			if (i!=lang)
			{
			sublang[i].print_begin(buf);
			strcat(buf,
				"<font color='red'>"
				"WARNING: Cannot include files written in other language"
				"</font>\n");
			sublang[i].print_end(buf);
			continue;
			}
		}
		
		fd=fopen(files,"r");

		if(fd) {
			fseek(fd,0,SEEK_END);
			flen = ftell(fd);
			fseek(fd,0,SEEK_SET);
			filelen += flen;
			old_b = b;
			b = (char *)realloc(b,filelen+1);
			b_dif = b-old_b;
			p += b_dif;
			next += b_dif;
			end += b_dif;
			tmpbuf = strdup(end);
			fread(end,flen,1,fd);
			end = end+flen;
			strcpy(end,tmpbuf);
			free(tmpbuf);
			fclose(fd);
		} else {
			sublang[i].print_begin(buf);
			strcat(buf,"Cannot include file '");
			strcat(buf,files);
			strcat(buf,"'");
			sublang[i].print_end(buf);
		}
		files = next;
	}
	return 1;
}

void
execSubLang(file,fd,lang,len)
	char *file;
	FILE *fd;
	int lang;
	long int len;
{
	int i=0;
	int code=0;
	int codevar=0;
	FILE *fdi;
	long flen;
	bool newline=false;
	char *e,*e2,*c,*p2;
	char *include;
	char *tmpbuf;
	char *exe;
		
	filelen=len;

	/* Allocate */
	buf=(char *)malloc(filelen*2+10+len);
	buf[0]='\0';

	/* Concat include file */
	include=malloc(strlen(dip)+20);
	sprintf(include,"%s/include%s",dip,sublang[lang]);
	fdi=fopen(include,"r");
	if (fdi)
	{
		fseek(fd,0,SEEK_END);
		flen=ftell(fd);
		fseek(fd,0,SEEK_SET);
		/* Realocate */
		buf=(char *)realloc(buf,filelen+10+flen);
		fread(buf,len,1,fdi);
		buf[flen]=0;
		fclose(fdi);
	}

	/** BEGIN OF DOCUMENT */
	sublang[lang].code_begin(buf);
	
	/** PARSE THE TAGS */
	filelen=len;
	p=b;
	while(p<b+filelen)
	{
		e=strchr(p,'\n'); 
		if (e) {
			newline=true;
			e[0]='\0'; /* EOS */
		} else {
			e=strchr(p,'\0');
		}
		if (!code)
		{	
			c=strstr(p,"<?");
			codevar=0;
		} else {
			c=strstr(p,"?>"); 
			if (c) /* tag found in this line. continue and disable code */
			{
				c[0]=0;
				strcat(buf,p);
				p=c+2;
				code=0;
				continue;
			}
		}
		
		if (c)
		{
			c[0]='\0';
			/** Tags */
			switch(c[2])
			{
				case '-': // comment
					p=strstr(c+2,"-?>");
					if (!p)
						{
						sublang[lang].print_begin(buf);
						strcat(buf,"comment tag not closed");
						sublang[lang].print_end(buf);
						goto END_EXEC_SUBLANG;
						}
					p=p+2;
					continue;
				case '^': // header
					p=strstr(c+2,"?>");
					if (!p)
					{
						sublang[lang].print_begin(buf);
						strcat(buf,"tag not closed");
						sublang[lang].print_end(buf);
						goto END_EXEC_SUBLANG;
					}
					p[0]=0;
					p+=2;
					addHeader(c+3);
					continue;
				case '=': // printvar
					codevar=1;
					newline=false;
					break;
				case '%': // directives
					p=strstr(c+2,"?>");
					if (!p)
					{
						sublang[lang].print_begin(buf);
						strcat(buf,"tag not closed");
						sublang[lang].print_end(buf);
						goto END_EXEC_SUBLANG;
					}
					p[0]=0;
					p+=2;
					
					if(e = strstr(c+3, "cacherule="))
					{
						if(!strncmp(e+10, "never", 5)) cacherule = CACHERULE_NEVER;
						else if(!strncmp(e+10, "noinput", 7) && cacherule) cacherule = CACHERULE_NOINPUT;
						else if(!strncmp(e+10, "time", 4) && cacherule) cacherule = CACHERULE_TIME;
					}
					/* TODO parse hsml directives */
					//printf("HSML directives(%s)\n",c+3);
					continue;
				case '&': // include
					p=strstr(c+2,"?>");
					if (!p)
					{
						sublang[lang].print_begin(buf);
						strcat(buf,"tag not closed");
						sublang[lang].print_end(buf);
						goto END_EXEC_SUBLANG;
					}
					p[0]='\0';
					SubLang_include(c+3,lang);
					p+=2;
					continue;
				case '@': // include at top
					p=strstr(c+2,"?>");
					if (!p)
					{
						sublang[lang].print_begin(buf);
						strcat(buf,"tag not closed");
						sublang[lang].print_end(buf);
						goto END_EXEC_SUBLANG;
					}
					p[0]=0;

					tmpbuf=strdup(buf);
					buf=(char *)realloc(buf,filelen+strlen(c+3));
					strcpy(buf,c+3);
					strcat(buf,tmpbuf);
					free(tmpbuf);
					p=p+3;
					continue;
			default:
				if (code)
				strcat(buf,p);
				break;
			}
		}
		if (!code||!newline)
		{
			p2=tokenizeBuffer(p);
			if (strlen(p2))
			{
				sublang[lang].print_begin(buf);
				strcat(buf,p2);
				if (newline) sublang[lang].print_end(buf);
				else sublang[lang].print_end_nl(buf);
			}
		}
		if (c)
		{
			code = !code;
			p=c+2;
			c=strstr(p,"?>");
			if (c) { c[0]='\0'; c++;}
		}
		if (code)
		{
			if (codevar)
			{
				sublang[lang].codevar(buf, p);
			} else {
				strcat(buf,p);
				strcat(buf,"\n");
			}
			if (c)
			{
				code=0;
				e=c;
			}
		}
		if (e) p=e+1;
		else   p=c+1;
	}
	END_EXEC_SUBLANG:
	
	if(cache_should_output(file))
	{
		printHeaders();
		cache_print(file);
		return;
	}
	if(cache_should_exec(file))
	{
		printHeaders();
		cache_exec(file, lang);
		return;
	}
	
	if (p) strcat(buf,p);
	
	/** ENDOF DOCUMENT */
	sublang[lang].code_end(buf);
	
	/* Headers */
	printHeaders();

	/* Body */
	if (donotexec)
			printf("%s",buf);
	else
			subLang_doExec(file,lang);

	//free(buf);
}

bool
subLang(file,len)
	char *file;
	long int len;
{
	int lang=0;
	char buf[1024];
	char *token;
	FILE *fd;

	/* XXX this is temporally */
	fd=fopen(file,"r");
	
	if (!fd)
	{
		printf("ERROR\n");
	}
	
	if (strstr(file,".hsml"))
	{
		if (fd)
		{
			fgets(buf,1000,fd);
			if (!strncmp(buf,"#!",2))
			{
				buf[strlen(buf)-1]=0;
				while(sublang[lang].lang &&
						strcmp(sublang[lang].lang,buf+2))
					{ lang++; }
				/* language not found */
				if (!sublang[lang].lang)
				{
					printf("Language hashbang '%s' not found!\n",buf+2);
					return true;
				}
				b=b+strlen(buf)+1;
				len-=strlen(buf)+1;
			} else {
				return false;
			}
		}
	} else {
		
		while(sublang[lang].file)
		{
			if (strstr(file,sublang[lang].file))
				break;
			lang++;
		}

	}
	
	if (sublang[lang].file)
	{
		while(token=strchr(file,'/')) // Strip path access
			file=token+1;
		//getPathFromEnv();
		execSubLang(file,fd,lang,len);
		return true; // Found
	}
	return false;
}
