/* qlam.c 
:w | !gcc % -o %< -Wall -Wfatal-errors -g
!./%< qlam
(c) 2023 Mourad Arnout marnout à free.fr
*/

#include "qlam.h"

int main(int argc, char *argv[]) 
{
	// make hdict
	for(int i=0; i<27; i++)	hdict[i] = NULL;
	mkdict(hdict, "html");

	if(argc == 1) printf("Usage: %s NAME [NAME] ...\n", argv[0]);
	else {
		str_t src, dest;
		char *p = basename(argv[1]), *q;
		for(int c=1; c<argc; c++)
		{
			p = basename(argv[c]);
			if((q = strchr(p, '.')) != NULL) *q = 0;
			sprintf(src, "qlm/%s.qlm", p);
			sprintf(dest, "%s.html", p);
			mkhtml(src, dest);
		}
	}
	freedict(hdict);
}

int
mkhtml(char *src, char *dest)
{
	FILE *qlm = fopen(src, "r");
	FILE *html = fopen(dest, "w");
	lstr_t line, l;
	char *p, *q; // arg1, arg2
	str_t hid, num;
	int hnum[5] = {0, 0, 0, 0, 0};
	fprintf(html,
"<!DOCTYPE html>\n"
"<html lang=\"fr\">\n"
"<head>\n"
"	<meta charset=\"utf-8\">\n"
"	<meta name=\"generator\" content=\"qlam-3.0\">\n"
"	<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
"	<link rel=\"icon\" href=\"favicon.png\">\n"
"	<link rel=\"stylesheet\" href=\"css/style.css\">\n"
"	<link rel=\"stylesheet\" href=\"css/header.css\">\n"
	);

	while(fgets(line, Lsize, qlm) != NULL)
	{
		*strchrnul(line, '\n') = '\0';
		switch(*line)
		{
			case '#' :
			//#' ' 
			if(line[1] == ' ') fprintf(html, "<!-- %s -->\n", line + 2);
			// header <h2> .. <h6> with id 
			else if(line[1]>'1' && line[1]<'7' && line[2]==' ') {
				int k = line[1] - '2'; 
				hnum[k]++;
				for(int j=k+1; j<5; j++) hnum[j] = 0;
				p = hid; q = num;
				if(k < 3)
					for(int j=0; j<3 && hnum[j]; j++)
						p += sprintf(p, "%d_", hnum[j]);
				else for(int j=0; j<5; j++)
						p += sprintf(p, "%d_", hnum[j]);
				fprintf(html, "\n<h%d id=\"%s\">%s</h%d>\n", 
						k+2, hid, line + 2, k+2);
			}
			//#article
			else if((p = qtag(line, "#article")) != NULL) {
				str_t article, name;
				strcpy(name, p);
				sprintf(article, "qlm/%s.qlm", p);
				FILE *fp = fopen(article, "r");
				assert(fp != NULL);
				while(fgets(line, Lsize, fp) && !(q = qtag(line, "#title")));
				*strchrnul(q, '\n') = '\0';
				fprintf(html, "<h5><a href=\"%s.html\">%s</a></h5>", name, q);
				while(fgets(line, Lsize, fp) && !(p = qtag(line, "#abstract")));
				while(fgets(line, Lsize, fp) && *line != '\n') {
					*strchrnul(line, '\n') = '\0';
					fputs("<p>", html); 
					mkstr(html, line, strlen(line)); 
					fputs("</p>\n", html);
				}
				fclose(fp);
			}
			//#code
			else if((p = qtag(line, "#code"))) {
				//wrd_t lang;	// e.g. "c", "cpp", "js"
				//strcpy(lang, p);
				mkpre(html, qlm, p);
			}
			//#html
			else if((p = qtag(line, "#html"))) {
				while(fgets(line, Lsize, qlm) && *line != '\n') 
					fputs(line, html);
			}
			//#include
			else if((p = qtag(line, "#include")) != NULL) {
				FILE *fp = fopen(p, "r");
				assert(fp != NULL);
				while(fgets(l, Lsize, fp)) fprintf(html, l);
				fclose(fp);
			}
			//#js
			else if((p = qtag(line, "#js")) != NULL) {
				if(*p)
					fprintf(html, "<script src=\"script/%s.js\"></script>\n", p);
				else {
					fprintf(html, "<script>\n");
					while(fgets(line, Lsize, qlm) && line[0] != '\n') 
						fprintf(html, "\t%s", line);
					fprintf(html, "%s\n", "</script>");
				}
			}
			//#meta
			else if((p = qtag(line, "#meta")) != NULL) {
				q = strchr(line, ':'); *q = '\0';
				fprintf(html, "<meta name=\"%s\" content=\"%s\">\n", p, q + 1);
			}
			//#note
			else if((p = qtag(line, "#note")) != NULL) {
				fprintf(html, "(%s)\n", p);
				fprintf(html, "<div class=\"note\" id=\"fn%s\">\n", p);
				while(fgets(line, Lsize, qlm) && line[0] != '\n') {
					*strchrnul(line, '\n') = '\0';
					fputs("<p>", html);
					mkstr(html, line, strlen(line));
					fputs("</p>\n", html);
				}
				fputs("</div>\n", html);
			}
			//#style
			else if((p = qtag(line, "#style")) != NULL) {
				if(*p)
				fprintf(html, "<link rel=\"stylesheet\" href=\"css/%s.css\">\n",
					p);
				else {
					fprintf(html, "<style>\n");
					while(fgets(line, Lsize, qlm) && line[0] != '\n') 
						fprintf(html, line);
					fprintf(html, "%s\n", "</style>");
				}
			}
			//#table
			else if((p = qtag(line, "#table")) != NULL) {
				if(*p) fprintf(html, "<table class=\"%s\">\n", p);
				else fputs("<table>\n", html);
				mktable(html, qlm);
				fputs("</table>\n", html);
			}
			//#title
			else if((p = qtag(line, "#title")) != NULL) {
				str_t title; strcpy(title, p);
				fprintf(html, "<title>%s</title>\n", title);
				fprintf(html, "</head>\n<body>\n");
				FILE *fp = fopen("include/header.html", "r");
				while(fgets(line, Lsize, fp)) fprintf(html, "%s", line);
				fclose(fp);
				fgets(line, Lsize, qlm);
				*strchrnul(line, '\n') = '\0';
				fprintf(html, "<p class=\"date\">%s</p>\n", line);
				fprintf(html, "<h1>%s</h1>\n", title);
			}
			
			break;

			case '-' :
			case '+' :
				char c = *line;
				fprintf(html, "<%s>\n", c == '-' ? "ul" : "ol");
				do {
					*strchrnul(line, '\n') = '\0';
					fputs("\t<li>", html);
					mkstr(html, line + 2, strlen(line) - 2);
					fputs("\t</li>\n", html);
					
				} while(fgets(line, Lsize, qlm) && *line == c);
				
				fprintf(html, "</%s>\n", c == '-' ? "ul" : "ol");
			break;

			case '?' :
				fputs("<dl>\n", html);
				do {
					*strchrnul(line, '\n') = '\0';
					fprintf(html, "\t<dt>%s</dt>\n", line + 2);
					fputs("\t<dd>\n", html);
					while(fgets(line, Lsize, qlm) && *line == '\t') {
						*strchrnul(line, '\n') = '\0';
						mkstr(html, line, strlen(line));
						fputs("<br>\n", html);
					}
					fputs("\t</dd>\n", html);
				} while(*line == '?');
				fputs("</dl>\n", html);
			break;

			case '<' :
				fprintf(html, "%s\n", line);
			break;

			default: 
				if(*line) {
					fputs("<p>", html);
					mkstr(html, line, strlen(line));
					fputs("</p>\n", html);
				}
		} // switch *line
	} // while getline
	fputs("<br><br><hr>\n", html);
	fputs("Réalisé avec <a href=\"http://arad.free.fr/qlam.html\">Qlam</a> - LGPL\n",
		html);
	fputs("</body>\n</html>", html);
	printf("%s → %s\n", src, dest);
	return 0;
}

// mkstr
void
mkstr(FILE *html, char *str, int size)
{
	const char ctrl[] = "";
	enum {em, strong, kbd, underline, code};
	const tiny_t tag[] = {"i", "strong", "kbd", "u", "code"};
	bool flag[] = {false, false, false, false, false};
	char *p, *q;
	int wdlen;

	for(char *ptr=str; *ptr && ptr < str + size; ptr++)
	{
		// typo style tag
		if((p = strchr(ctrl, *ptr)) != NULL)
		{
			int i = p - ctrl;
			if(flag[i]) fprintf(html, "</%s>", tag[i]);
			else fprintf(html, "<%s>", tag[i]);
			flag[i] = !flag[i];
		}
		// link
		else if(*ptr == '[' && 
				(p = strstr(ptr, "->")) != NULL && 
				(q = strchr(p + 1, ']')) != NULL
				)
		{
			*p = *q = 0;
			fprintf(html, "<a href=\"%s\">%s</a>", p + 2, ptr + 1);
			ptr = q;
		}
		// span box
		else if(*ptr == '#' && ptr[1] == '[' && (p = strchr(ptr + 2, ']')))
		{
			*p = 0;
			fprintf(html, "<span class=\"box\">%s</span>", ptr + 2);
			ptr = p + 1;
		}
		// foot note
		else if(*ptr == '#' && ptr[1] == '(' && (p = strchr(ptr + 2, ')')))
		{
			*p = 0;
			fprintf(html, "<a class=\"nte\" href=\"#fn%s\">(%s)</a>",
				ptr + 2, ptr + 2);
			ptr = p + 1;
		}
		else if(!flag[code] && *ptr == '<' && (q = strchr(ptr+1, '>')) != NULL)
		{
			p = ptr[1] == '/' ? ptr+2 : ptr+1;
			wdlen = indict(p, hdict);
			if(wdlen > 0) {
				fprintf(html, "%.*s", (int)(q - ptr), ptr);
				ptr = q - 1;
			} else fputs("&lt;", html);
		}
		else if(*ptr == '\\')
		{
			switch(ptr[1])
			{
				case '<': fputs("&lt;", html); ptr++; break;
				case '>': fputs("&gt;", html); ptr++; break;
				case '"': fputs("&quot;", html); ptr++; break;
				case '&': fputs("&amp;", html); ptr++; break;
				case '#': fputs("&num;", html); ptr++; break;
				case '$': fputs("&dollar;", html); ptr++; break;
				case '[': fputs("&#91;", html); ptr++; break;
				case ']': fputs("&#93;", html); ptr++; break;
				case 't': fputs("&emsp;", html); ptr++; break;
				default: fputc(*ptr, html);
			}
		}
		// special html entities i.e. < > " &
		else if(flag[code])
		{
			switch(*ptr)
			{
				case '<': fputs("&lt;", html); break;
				case '>': fputs("&gt;", html); break;
				case '"': fputs("&quot;", html); break;
				case '&': fputs("&amp;", html); break;
				default: fputc(*ptr, html);
			}
		}
		// other
		else fputc(*ptr, html);
	}
	// garbage trailing typo style tags
	if(strlen(str) == size && str[size] == '\0')
	{
		for(int i=0; i<strlen(ctrl); i++)
		{
			if(flag[i])
			{
				fprintf(html, "</%s>", tag[i]);
				flag[i] = !flag[i];
			}
		}
	}
}

/** check if s starts with w
returns a pointer to the argument of the key word if any or NULL otherwise
*/
char *
qtag(char *s, char *w) 
{
	size_t wl = strlen(w);
	char *p;
	if(!strncmp(s, w, wl)) { // match
		p = s + wl; // move in s at the end of w
		while(*p == ' ') p++; // trim spaces if anay
		return p; // pointer at the argument
	}
	else return NULL; // line don't match
}

void
mktable(FILE *html, FILE *qlm)
{
	lstr_t line;
	char *p, *q;	
	if(fgets(line, Lsize, qlm) && *line != '\n') {
		*strchrnul(line, '\n') = '\0';
		fputs("<tr>\n<th>", html);
		p = line;
		while((q = strchr(p, '|')) != NULL) {
			mkstr(html, p, q - p);
			fputs("</th><th>", html);
			p = q + 1;
		}
		mkstr(html, p, strlen(p));
		fputs("</th></tr>\n", html);
	}
	while(fgets(line, Lsize, qlm) && *line != '\n') {
		*strchrnul(line, '\n') = '\0';
		fputs("<tr><td>", html);
		p = line;
		while((q = strchr(p, '|')) != NULL) {
			mkstr(html, p, q - p);
			fputs("</td><td>", html);
			p = q + 1;
		}
		mkstr(html, p, strlen(p));
		fputs("</td></tr>\n", html);
	}

}

// dictionaries
void
mkdict(dict_t dict, char *name)
{
	wrd_t w;
	token_t *token, *new;
	int i;
	sprintf(path, "include/%s.dict", name);
	FILE *fp = fopen(path, "r");
	assert(fp);
	for(int i=0; i<27; i++)	dict[i] = NULL;
	while(fgets(w, Wsize, fp))
	{
		*strchrnul(w, '\n') = '\0';
		new = (token_t *)malloc(sizeof(token_t));
		new->data = (char *)malloc(strlen(w) + 1);
		strcpy(new->data, w);
		new->next = NULL;
		i = *w - 'a';
		if(i<0 || i > 26) i = 26;
		if(dict[i] == NULL) {
			dict[i] = new;
			token = dict[i];
		} else {
			token->next = new;
			token = token->next;
		}
	}
	fclose(fp);
}

int
indict(char *s, dict_t dict)
{
	int size = strspn(s, ALPHA);
	if(size >= Wsize || size == 0) return 0;
	wrd_t w = {'\0'};
	memcpy(w, s, size);
	int i = *w - 'a'; 
	if(i < 0 || i > 26) i = 26;
	token_t *t = dict[i];
	while(t != NULL && strcmp(t->data, w) < 0) t = t->next;
	if(t != NULL && eq(t->data, w)) return size;
	return 0;
}

void
freedict(dict_t dict)
{
	token_t *token, *next;
	for(int i=0; i<27; i++)	
	{
		token = dict[i];
		while(token != NULL) {
			free(token->data);
			next = token->next;
			free(token);
			token = next;
		}
	}
}
void
mkpre(FILE *html, FILE *qlm, char *lang)
{
	char cmnts[][8] = {"//", "/*", "*/", "#", "\"\"\"", "<!--", "-->"};
	char *cmnt[3];
	size_t lcmnt[3];
	#define iscmnt(s, i) *lang && *cmnt[(i)] && !strncmp(s, cmnt[(i)], lcmnt[(i)])
	int len;
	dict_t dict;
	bool lflag = false, nflag = false;
	if(*lang == '#') { nflag = true; lang++; } // number lines
	if(*lang == '!') { lflag = true; lang++; } // litteral bold
	if(*lang)
	{
		mkdict(dict, lang);
		for(int i=0; i<3; i++)	cmnt[i] = "";
		if(eq(lang, "c") || eq(lang,"cpp") || eq(lang, "js") || eq(lang, "scad"))
		{
			for(int i=0; i<3; i++)	cmnt[i] = cmnts[i];
		} else if(eq(lang, "bash")) {
			cmnt[0] = cmnts[3];
		} else if(eq(lang, "html")) {
			cmnt[1] = cmnts[5]; cmnt[2] = cmnts[6];
		} else if(eq(lang, "py")) {
			cmnt[0] = cmnts[3]; cmnt[1] = cmnt[2] = cmnts[4];
		} 
		for(int i=0; i<3; i++) lcmnt[i] = strlen(cmnt[i]);

	}
	if(nflag) fputs("<pre class=\"n\">\n", html); 
	else fputs("<pre>\n", html);
	lstr_t line;
	char *s, *p, *q, *r; 
	bool emflag, bflag, cflag = false;
	while(fgets(line, Lsize, qlm) != NULL && *line != '\n')
	{
		*strchrnul(line, '\n') = '\0';
		emflag = false; bflag = false; 
		s = line;
		fputs("<code>", html);
		if(cflag) fputs("<em>", html);
		while(*s) 
		{
			r = s + strcspn(s, ALPHA);
			for(p=s; *p && p < r; p++)
			{
				if((cflag || emflag) && *p == '/') 
					fprintf(html, "&sol;");
				else if(iscmnt(p, 0)) {
					fprintf(html, "<em>%s", cmnt[0]);
					p += lcmnt[0] - 1;
					emflag = true;
				}
				else if(iscmnt(p, 1)) {
					// special case for python """
					if(cflag) fprintf(html, "%s</em>", cmnt[1]);
					else fprintf(html, "<em>%s", cmnt[1]);
					cflag = !cflag;
					p += lcmnt[1] - 1;
				} 
				else if(iscmnt(p, 2)) {
					fprintf(html, "%s</em>", cmnt[2]);
					p += lcmnt[2] - 1;
					cflag=false;
				} 
				else if(*p == '<') fprintf(html, "&lt;");
				else if(*p == '>') fprintf(html, "&gt;");
				else if(*p == '&') fprintf(html, "&amp;");
				else if(*p == '"') {
					if(lflag && (q = strchr(p + 1, '"')) != NULL) 
					{
						fputs("&quot;<b>", html);
						for(char *c = p + 1; *c && c < q; c++)
						switch(*c)
						{
							case '<': fputs("&lt;", html); break;
							case '>': fputs("&gt;", html); break;
							case '&': fputs("&amp;", html); break;
							default: fputc(*c, html);
						}
						fputs("</b>&quot;", html);
						r = q + 1;
						break;
					} 
					else 
					fprintf(html, "&quot;");
				}
				else if(*p == '') {
					if(bflag) fputs("</b>", html);
					else fputs("<b>", html);
					bflag = !bflag;
				}
				else if(lflag) {
					strtod(p, &q);
					if(q > p && !emflag && *(p - 1) != '_' && !isalpha(*(p - 1))) 
					{
						fprintf(html, "<b>%.*s</b>", (int)(q - p), p);
						p = q - 1;
					} 
					else 
					fputc(*p, html);
				}
				else fputc(*p, html);
			}
			s = r;
			len = strspn(s, ALPHA);
			if(*lang && !emflag &&
				(strcmp(lang, "html") || *(s-1) == '<' || *(s+len) == '>'))
			{
				if(len > 0 && len == indict(s, dict))
					fprintf(html, "<b>%.*s</b>", len, s);
				else 
					fprintf(html, "%.*s", len, s);
			}
			else fprintf(html, "%.*s", len, s);
			s += len;
		}
		if(emflag || cflag) fputs("</em>", html);
		if(bflag) fputs("</b>", html);
		fputs("</code>\n", html);
	}
	fputs("</pre>\n", html);
	
	if(*lang) freedict(dict);
}
