#!/Users/min/projects/dao/dao

load sys;

const latex_head = 
@[latex]
\documentclass[12pt,a4paper]{article}
\usepackage[latin1]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{scalefnt}
\usepackage{titlesec}
\usepackage{hyperref}

\usepackage{cite}
\usepackage{graphicx}
\usepackage{wrapfig}
\usepackage{xcolor}
\usepackage{fancyvrb}

%\usepackage{tikz}
%\usepackage{pgflibraryarrows}
%\usepackage{pgflibrarysnakes}
%\usetikzlibrary{decorations.markings}
%\usetikzlibrary{patterns,fadings}

\addtolength{\hoffset}{-4em}
\addtolength{\textwidth}{8em}
\addtolength{\voffset}{-4em}
\addtolength{\textheight}{8em}
\setlength{\parindent}{2em}
\linespread{1.1} % also in front page

\renewcommand\FancyVerbTab{\textcolor{tabcolor}{$\mid$}}

\newcommand*{\justifyheading}{\centering}
\titleformat{\section}
  {\normalfont\Huge\bfseries\justifyheading}{\thesection}{1em}{}
\titleformat{\subsection}
  {\normalfont\Large\bfseries}{\thesubsection}{1em}{}

\usepackage{listings}

\lstset{ %
language=C,                     % choose the language of the code
basicstyle=\small\ttfamily,       % the size of the fonts that are used for the code
numbers=left,                   % where to put the line-numbers
numberstyle=\small\ttfamily,      % the size of the fonts that are used for the line-numbers
stepnumber=1,                   % the step between two line-numbers. If it's 1 each line will be numbered
numbersep=5pt,                  % how far the line-numbers are from the code
backgroundcolor=\color{white},  % choose the background color. You must add \usepackage{color}
showspaces=false,               % show spaces adding particular underscores
showstringspaces=false,         % underline spaces within strings
showtabs=false,                 % show tabs within strings adding particular underscores
frame=single,                  % adds a frame around the code
tabsize=2,                    % sets default tabsize to 2 spaces
captionpos=b,                   % sets the caption-position to bottom
breaklines=true,                % sets automatic line breaking
breakatwhitespace=false,        % sets if automatic breaks should only happen at whitespace
escapeinside={\%*}{*)}          % if you want to add a comment within your code
}
@[latex]

const open_doc =
@[latex]
\date{}
\begin{document}
\scalefont{1.1}
\maketitle
\vspace{10em}
@[latex];

const make_toc =
@[latex]
\clearpage
\tableofcontents
\clearpage
@[latex];

const make_fig =
@[latex]
\begin{figure}[!h]
\includegraphics[$(size)]{$(figure)}
$(caption)
\end{figure}
@[latex]

class TextSection
{
	var tag = '';
	var name = '';
	var output = '';
	var subsections : list<TextSection> = {};

	routine TextSection( tag = '', name = '' ){
		self.tag = tag;
		self.name = name;
	}
}

class WikiParser
{
	routine Parse( source :string );
	routine ParseBlock( section :TextSection, source :string );
	routine Formatting( source :string );
	routine HandleIndentedCodes( source :string );
	routine HandleTable( source :string );
}

routine ToHex( self :string ) => string
{
	const hex_digits = '0123456789ABCDEF';
	hex = '';
	self.change( '^[\n]+', '' )
	self.change( '[\n]+ $', '' )
	self.iterate::{
		hex += hex_digits[ X / 16 ];
		hex += hex_digits[ X % 16 ];
	}
	return hex;
}
routine FromHex( self :string ) => string
{
	ch0 = '0'[0];
	chA = 'A'[0];
	decoded = '';
	for( i = 0 : 2 : self.size()-1 ){
		ch1 = self[i];
		ch2 = self[i+1];
		ch1 = (ch1 >= chA) ? 10 + ch1 - chA : ch1 - ch0;
		ch2 = (ch2 >= chA) ? 10 + ch2 - chA : ch2 - ch0;
		decoded += ch1*16 + ch2;
	}
	return decoded;
}
routine Convert( self :string ) => string
{
	self.change( '<min>__(.*)__', '%1' );
	self.replace( '_', '\\_' );
	self.replace( '&', '\\&' );
	self.replace( '%', '\\%' );
	self.replace( '#', '\\#' );
	self.replace( '<', '$<$' );
	self.replace( '>', '$>$' );
	self.change( '<min>%*%*(.*)%*%*', '{\\bf %1}' );
	self.change( '<min>([^:])//(.*)//', '%1{\\it %2}' );
	self.change( '<min>\'\'(.*)\'\'', '\\texttt{%1}' );
	self.change( '~~LASTMOD~~', sys.ctimef( 0, '%Y-%M-%D' ) );
	return self;
}


routine WikiParser::Parse( source :string )
{
	const pat_nowiki = '<min>( %< %s* nowiki %s* %> (.*) %< %s* / %s* nowiki %s* %> | %%%% (.*) %%%%)';
	blocks = source.extract( pat_nowiki, $both );
	source = '';
	blocks.iterate::{ [block]
		parts = block.capture( pat_nowiki );
		if( parts ){
			source += 1;
			source += 'nowiki';
			source += 1;
			source += (parts[2] + parts[3]).ToHex();
			source += 2;
		}else{
			source += block;
		}
	}

	const pat_marking = '<min> %< %s* (%w+) %s* %> (.*) %< %s* / %s* %1 %s* %>';
	blocks = source.extract( pat_marking, $both );
	source = '';
	blocks.iterate::{ [block]
		parts = block.capture( pat_marking );
		if( parts ){
			source += 1;
			source += parts[1];
			source += 1;
			source += parts[2].ToHex();
			source += 2;
		}else{
			source += block;
		}
	}

	const pat_headline = '<min>(^ | [^=]) (={2,6}) ([^=].*) %2';
	const pat_headline2 = '<min>(^) %s* (={2,6}) %s* ([^=].*) %2';

	blocks = source.extract( pat_headline, $both );

	top_section = TextSection( '================================' );
	cur_sections = { top_section };
	first_sect = top_section;
	all_sections = { top_section };
	while( blocks.size() ){
		block = blocks.pop( $front );
		tokens = block.capture( pat_headline2 );
		if( tokens.size() == 0 ){
			ParseBlock( cur_sections.back(), block );
			skip;
		}
		tag = tokens[2];
		title = tokens[3];
		while( cur_sections.size() and cur_sections.back().tag <= tag ) cur_sections.pop();
		if( cur_sections.size() ==0 ){
			io.write( 'ERROR: too long headline for \"', block, '\"!\n' );
			return '';
		}
		cur_section = cur_sections.back();
		sub_section = TextSection( tag, title );
		cur_section.subsections.append( sub_section );
		cur_sections.push( sub_section );
		if( first_sect == top_section ) first_sect = sub_section;
		all_sections.push( sub_section );
	}
	title = '';
	tag_section = '';
	if( cur_sections.size() > 1 && cur_sections[1] == first_sect ){
		title = first_sect.name;
		if( first_sect.subsections.size() ) tag_section = first_sect.subsections.back().tag;
	}
	all_sections.pop( $front );
	all_sections.pop( $front );
	front = '\\begin{center}\n' + first_sect.output + '\n\\end{center}\n';
	output = all_sections.reduce('')::{ [sect,text]
		headline = '';
		if( sect.tag == tag_section ){
			headline = '\n\\clearpage\n\\section{' + sect.name.Convert() + '}\n\n';
		}else if( sect.tag < tag_section ){
			headline = '\n\n\\subsection{' + sect.name.Convert() + '}\n\n';
		}
		return text + headline + sect.output;
	};

	const pat_hex = '<min>{{\1}}(%w+){{\1}}([0-9A-F]+){{\2}}';
	blocks = output.extract( pat_hex, $both );
	output = '';
	blocks.iterate::{ [block]
		parts = block.capture( pat_hex );
		if( parts ){
			switch( parts[1] ){
			case 'code', 'file' : output += '\\begin{lstlisting}\n';
			case 'sup' : output += '$^';
			case 'sub' : output += '$_';
			case 'nowiki', 'del', 'latex' :
			}
			output += parts[2].FromHex();
			switch( parts[1] ){
			case 'code', 'file' : output += '\n\\end{lstlisting}\n';
			case 'sup' : output += '$';
			case 'sub' : output += '$';
			case 'nowiki', 'del', 'latex' :
			}
		}else{
			output += block;
		}
	}
	head = '\\title{' + title.Convert() + '}\n';
	head += open_doc;
	head += front;
	head += make_toc;
	output = head + output + '\\end{document}'
	output.change( '[\n]{2,}', '\n\n' );
	#io.writeln( '=========================================' );
	#io.writeln( all_sections );
	#io.writeln( output );
	return output;
}
routine WikiParser::ParseBlock( section :TextSection, source :string )
{
	const pat_list = '^({{  }}+ ( [%*%-] ))';
	lines = source.split( '\n' );
	blocks = { lines.pop( $front ) + '\n' };
	while( lines ){
		line = lines.pop( $front );
		line2 = line[];
		line2.trim();
		match = line.match( pat_list );
		if( match.start >= 0 or line2 == '' ){
			blocks.push( line + '\n' );
		}else{
			blocks[ blocks.size() - 1 ] += line + '\n';
		}
	}
	blocks.push( '' );
	heads = { '' }
	output = '';
	while( blocks ){
		block = blocks.pop( $front );
		head = heads.back();
		head2 = '';

		match = block.match( pat_list );
		if( match.start >= 0 ) head2 = match.substring;

		if( head2.size() > head.size() ){
			begin = '\\begin{itemize}\n ';
			if( head2[ head2.size()-1 ] == '-'[0] ) begin = '\\begin{enumerate}\n ';
			output += begin;
			heads.push( head2 );
		}else if( head2.size() < head.size() ){
			end = '\\end{itemize}\n';
			if( head[ head.size()-1 ] == '-'[0] ) end = '\\end{enumerate}\n';
			output += end;
			heads.pop();
		}
		if( head2.size() ){
			output += '\\item ';
			output += Formatting( block[ head2.size() : ] );
		}else{
			while( heads.size() > 1 ){
				head = heads.pop();
				end = '\\end{itemize}\n';
				if( head[ head.size()-1 ] == '-'[0] ) end = '\\end{enumerate}\n';
				output += end;
			}
			output += Formatting( block );
		}
	}
	while( heads.size() > 1 ){
		head = heads.pop();
		end = '\\end{itemize}\n';
		if( head[ head.size()-1 ] == '-'[0] ) end = '\\end{enumerate}\n';
		output += end;
	}
	section.output = output;
}

routine WikiParser::HandleTable( source :string )
{
	lines = source.split( '\n' );
	lines.append( '' );
	output = '';
	last = ( size => 0, fields => {} );
	lines.iterate::{
		if( X.match( '^ [%^%|] .* [%^%|]$' ).start <0 ){
			if( last.fields.size() ){
				output += '\\hline';
				output += '\n\\end{tabular}\n\\end{table}\n\4';
			}
			last = ( size => 0, fields => {} );
			output += X + '\n';
			return;
		}
		fields = X.extract( '[%^%|]', $both )
		io.writeln( fields );
		if( X.size() != last.size or fields.size() != last.fields.size() ){ # new table:
			if( last.fields.size() ){
				output += '\\hline';
				output += '\n\\end{tabular}\n\\end{table}\n\4';
			}
			output += '\3\n\\begin{table}[!h]\n\\begin{tabular}{';
			fields.iterate::{
				if( X == '^' or X == '|' ){
					output += '|';
				}else{ # TODO alignment
					output += 'c';
				}
			}
			output += '}\n';
			output += '\\hline\n';
		}else{
			output += '\\hline\n';
		}
		fields.iterate::{
			if( X == '^' or X == '|' ){
				if( Y and (Y+1) < fields.size() ) output += ' & ';
			}else{
				output += X.Convert();
			}
		}
		output += ' \\\\\n';
		last.size = X.size();
		last.fields = fields;
	}
	blocks = output.extract( '<min>{{\3}}[^\3\4]*{{\4}}', $both );
	blocks.apply::{
		if( X.size() and X[0] == 3 and X[X.size()-1] == 4 ){
			return '\1nowiki\1' + X[1:X.size()-2].ToHex() + '\2';
		}
		return X;
	}
	return blocks.sum();
}
routine WikiParser::HandleIndentedCodes( source :string )
{
	lines = source.split( '\n' );
	blocks = { '' };
	indented = 0;
	while( lines ){
		line = lines.pop( $front ) + '\n';
		if( line.match( '^%s%s%S' ).start == 0 ){
			if( indented ){
				blocks.back() += line;
			}else{
				blocks.push( line );
				indented = 1;
			}
		}else{
			if( indented ){
				blocks.push( line );
				indented = 0;
			}else{
				blocks.back() += line;
			}
		}
	}
	blocks.apply::{
		if( X.match( '^%s%s%S' ).start != 0 ) return HandleTable( X );
		return '\1code\1' + X.ToHex() + '\2';
	}
	return blocks.sum();
}
routine WikiParser::Formatting( source :string )
{
	const pat_link = '<min>%[%[([^%|]*)(| %| (.+)) %]%]';
	const pat_figure = '<min>%{%{ %s* (| wiki) %s* : %s* ([^ \t\n%?]+) %s* (| %? ((%d+) (|[xX] (%d+) ))) %s* (| %| (.*) ) %}%}';
	source = HandleIndentedCodes( source );
	parts = source.extract( '(' + pat_link + ' | ' + pat_figure + ')', $both );
	parts.apply::{ [text]
		tokens = text.capture( '^' + pat_link );
		tokens2 = text.capture( '^' + pat_figure );
		if( tokens ){
			url = tokens[1];
			url.replace( '/', '\\slash ' );
			if( tokens[3] ){
				return '\\href{' + url + '}{' + tokens[3] + '}';
			}else{
				return '\\href{' + url + '}{' + url + '}';
			}
			desc = tokens[1];
			if( tokens[3] ) desc = tokens[3];
		}else if( tokens2 ){
			size = 'width=\\textwidth';
			file = tokens2[2];
			caption = '';
			if( tokens2[4] ){
				size = 'width=' + tokens2[5] + 'px';
				if( tokens2[7] ) size += ',height=' + tokens2[7] + 'px';
			}
			if( tokens2[9] ) caption = '\\caption{' + tokens2[9] + '}';
			return make_fig.expand( (size=>size, figure=>file, caption=>caption) );
		}
		return text.Convert();
	}
	return parts.sum();
}

routine main( file : string )
#{
Usage:

  %P  input_wiki_source_file

#}
{
	source = io.read( file );
	parser = WikiParser();
	output = parser.Parse( source );

	file.change( '%.wiki$', '' );
	fout = io.open( file + '.tex', 'w+' );
	fout.write( latex_head );
	fout.write( output );
	fout.close();
}
