#!/usr/local/bin/pike

import Stdio;
import spider;
import Array;

mapping(int:mapping) links=([]);

string p1(string data);

/* Note: destructive ! */
string *srt(string *foo)
{
  sort(map(foo,lower_case),foo);
  return foo;
}

string quote_param(string s)
{
  string ret="";
  while(sscanf(s,"%[-a-zA-Z0-9/()*,.]%c%s",string a, int c,s)==3)
    ret+=sprintf("%s%%02x",a,c);

  return ret+s;
}

string unquote_param(string s)
{
  string *tmp=s/"%";
  for(int e=1;e<sizeof(tmp);e++)
    if(sscanf(tmp[e],"%2x%s",int x, string rest))
      tmp[e]=sprintf("%c%s",x,rest);

  return tmp*"";
}

string code_params(mapping s)
{
  string ret="";
  foreach(indices(s), string data)
    ret+=" "+data+"="+quote_param(s[data]);
  return ret;
}

/* Index handling */

void _add_to(mapping foo, string bar, string full)
{
  mapping m;
  if(!bar)
  {
    if(foo[0])
    {
      foo[0]|=({full});
    }else{
      foo[0]=({full});
    }
    return;
  }
  sscanf(bar,"%s.%s",bar,string rest);
  if(!(m=foo[bar])) m=foo[bar]=([]);
  _add_to(m, rest,full);
}

void add_to(string bar, string full)
{
  sscanf(lower_case(unquote_param(bar)),"%*[_ ]%c",int c);
  mapping m;
  if(!(m=links[c])) m=links[c]=([]);
  _add_to(m, bar, full);
}


string anchor(mixed a, mixed b, mixed c)
{
  if(b->name)
  {
    if(c[strlen(c)-10..]=="<!--END-->")
      werror("Warning: Anchor not ended "+b->name+".\n");

    add_to(b->name, b->name);
    string *foo=b->name/".";
    for(int e=1;e<sizeof(foo);e++)
      add_to(foo[e], foo[..e]*".");
  }

  if (a=="anchor")
     return "<a name="+b->name+">"+c+"</a name="+b->name+">";

  return 0;
}

string mklink(string lnk) {   return "<a href=#"+lnk+">"+unquote_param(lnk)+"</a>"; }

string genindex(mapping m, string prefix)
{
  string ret="<dl>\n";
  if(m[0])
  {
    foreach(srt(m[0]), string lnk)
      ret+="<dt>"+mklink(lnk)+"\n";
  }
  m_delete(m,0);
  foreach(srt(indices(m)), string ind)
    {
      if(m[ind][0] &&
	 sizeof(m[ind])==1 &&
	 sizeof(m[ind][0])==1 &&
	m[ind][0][0]==prefix+ind)
      {
	foreach(srt(m[ind][0]), string lnk)
	  ret+="<dt>"+mklink(lnk)+"\n";
      }else{
	ret+="<dt>"+unquote_param(prefix+ind)+"\n"+
	"<dd>\n"+
	genindex(m[ind], prefix+ind+".");
      }
    }
  ret+="</dl>\n";
  return ret;
}

/* chapter handling */

mixed *tod=({});
string prefix="";

string chapter(string tag, mapping params, string data);
string section(string tag, mapping params, string data);
string appendix(string tag, mapping params, string data);
string appendixes(string tag, mapping params, string data);

#define TAGS \
  (["chapter":chapter,"section":section,\
   "appendix":appendix,"appendixes":appendixes])

mapping aliases=([]);

string rtag(string name,
	    string title,
	    string outtitle,
	    string xlink,
	    string sectionname,
	    mapping params,
	    string data)
{
  if(data[strlen(data)-10..]=="<!--END-->")
    werror("Warning: "+name+" tag not ended "+title+".\n");

  string *save=tod;
  string save_prefix=prefix;
  tod=({});
  prefix+=xlink+".";
  data+="\n<!--END-->";
  data=parse_html(data,([]),TAGS);
  prefix=save_prefix;
  tod=save+({prefix+xlink,sectionname||(prefix+xlink),title,tod});

  if(params->alias)
    {
      aliases[params->alias]=
	({
	  prefix+xlink, 
	  sectionname || ( name + " " + prefix+xlink )
	})
	;
    }


  if(data[strlen(data)-10..]=="<!--END-->")
    data=data[..strlen(data)-11];

  string tmp;
  switch(sizeof(prefix/"."))
  {
  case 0:
  case 1: tmp="h1"; break;
  case 2: tmp="h2"; break;
  case 3:
  default: tmp="h3"; break;
  }

  return
    "<a name="+ prefix+xlink +">\n"+
    "<"+tmp+">"+outtitle+"</"+tmp+">"+
    data+"\n"+
    "</a> <!-- "+name+" "+prefix+xlink+" -->\n"+
    "<p>\n";
}
	    

string section(string tag, mapping params, string data)
{
  int c = sizeof(tod)/4 +1;
  return rtag("section",
	      params->title,
	      prefix+c+" "+params->title,
	      (string)c,
	      0,
	      params,
	      data);
}

string chapter(string tag, mapping params, string data)
{
  int c = sizeof(tod)/4+1;
  return rtag("chapter",
	      params->title,
	      "Chapter "+prefix+c+", "+params->title,
	      (string)c,
	      0,
	      params,
	      data);
}

string appendixes(string tag, mapping params, string data)
{
  return rtag("appendixes",
	      "",
	      "Appendixes",
	      "Appendix",
	      "Appendixes",
	      params,
	      data);

}

int appnum = 'A';

string appendix(string tag, mapping params, string data)
{
  string c=sprintf("%c",appnum++);
  return rtag("appendix",
	      params->title,
	      "Appendix "+c+", "+params->title,
	      c,
	      "Appendix "+c,
	      params,
	      data);
}


string gencontents(mixed *tod)
{
  if(!sizeof(tod)) return "";
  string ret="<dl>\n";
  for(int e;e<sizeof(tod);e+=4)
  {
    ret+="<dt><b><a href=#"+tod[e]+">"+tod[e+1]+"</b> "+tod[e+2]+"</a>\n";

    string tmp=gencontents(tod[e+3]);
    if(strlen(tmp)) ret+="<dd>\n"+tmp;

  }
  ret+="</dl>";
  return ret;
}

/* Stage 2 */
mapping replacements=([]);

string do_replace(string what)
{
  return replacements[lower_case(what)];
}

string encaps1(string word)
{
  return "<font size=+1>"+word[0..0]+"</font><font size=-1>"+word[1..]+"</font>";
}

string encaps(string tag, mapping params, string data)
{
  return "<b>"+map((data/" ")-({""}),encaps1)*" "+"</b>";
}

string link(string tag, mapping params, string data)
{
  mixed tmp=aliases[params->to];
  if(!tmp)
    {
      werror("Unresolved <link> :"+params->to+"\n");
      return 0;
    }

  return "<a href=#"+tmp[0]+">"+tmp[1]+"</a>";
}

string box(string tag, mapping params, string data)
{
  return "<center><table border=1>\n"+
    data+
    "</table></center>\n";
}

string do_include(string tag, mapping params, string data)
{
  return Stdio.read_file(params->file);
}

string end_img_tag(string ret, mapping params)
{
  if(params->align) ret+=" align="+quote_param(params->align);
  if(params->alt) ret+=" alt="+quote_param(params->align);

  return ret+">";
}

int gifnum;
mapping gifcache=([]);

string mkgif(object o)
{
  string g=o->togif();
  if(gifcache[g]) return gifcache[g];

  gifnum++;
  string gifname="illustration"+gifnum+".gif";
  rm(gifname);
  write_file(gifname,g);

  gifcache[g]=gifname;
  return gifname;
}

mapping gifcache2=([]);
mapping srccache=([]);

string illustration(string tag, mapping params, string data)
{
  string src=params->src;
  object img=Image.image();

  float dpi=75.0;
  if(params->dpi) dpi=(float)params->dpi;
  if(params->scale) dpi/=(float)params->scale;
  float scale=75.0/dpi;

  string key=encode_value(({src,scale,data}));

  string file;
  if(!(file=gifcache2[key]))
  {
    mixed e;
    if(params->src) 
       img=srccache[src] ||
	  (srccache[src]=img->fromppm(Process.popen("anytopnm "+src)));
    if(scale!=1.0) img=img->scale(scale);
    if (e=catch 
    {
       img=compile_string("import Image;\n"
			  "object `()(object src){ "+data+" ; }")()(img);
       file=mkgif(img);
       gifcache2[key]=file;
    })
    {
	werror("error while compiling and running\n"+data+"\n");
	if (params->__from__) 
	   werror("from "+params->__from__+":\n");
	werror(master()->describe_backtrace(e)+"\n");
	return "<!-- failed to illustrate -->";
    }
    params-=(["__from__":0]);
  }

  return end_img_tag("<img src="+file,params);
}

string image(string tag, mapping params, string data)
{
  string ret;
  if(params->xfig)	
  {
    Process.system("fig2dev -L ps "+params->xfig+".fig ___tmp.ps;echo showpage >>___tmp.ps");
    object o=Image.image()->fromppm(
      Process.popen("gs -q -sDEVICE=pbmraw -r225 -g2500x2500 -sOutputFile=- ___tmp.ps"));

    o=o->autocrop()->scale(1.0/3)->rotate(-90);
    o=Image.image(o->xsize()+40, o->ysize()+40, 255,255,255)->paste(o,20,20);
    string gifname=mkgif(o);
    rm("___tmp.ps");
    ret="<img src="+quote_param(gifname);
  }
  else if(params->src)
  {
    return illustration(tag,params,"return src");
  }
  else
  {
    return "";
    werror("Unknown image type\n");
  }

  return end_img_tag(ret,params);
}

int main()
{
  string data=Stdio.stdin.read(0x7fffffff);

  data+="\n<!--END-->";

  /* Preprocess */
  data=parse_html(data,(["include":do_include]),([]));

  /* Pass 1, find links for the index */
  data=parse_html(data, ([]) , (["a":anchor,"anchor":anchor]));

  /* Pass 2, parse chapters and sections */
  data=parse_html(data, ([]) , TAGS);

  /* Build index */
  string index="<dl>\n";
  foreach(sort(indices(links)), int letter)
  {
    index+="<dt><font size=+2>"+
      (letter=='`'?"Operators": upper_case(sprintf("%c",letter)))+
      "</font>\n"+
      "<dd>\n"+
      genindex(links[letter],"");
  }
  index+="</dl>\n";
  
  replacements->index=index;

  /* Build table of contents */
  replacements["table-of-contents"]=gencontents(tod);


  /* Pass 3, insert generated data */
  write(parse_html(data,
		   ([
		     "index":do_replace,
		     "table-of-contents":do_replace,
		     "link":link,
		     "image":image,
		     ]),
		   ([
		     "box":box,
		     "encaps":encaps,
		     "illustration":illustration,
		     ])));
}