/*  
 *                   COPYRIGHT (c) 1988-1994 BY                             *
 *        PARADIGM ASSOCIATES INCORPORATED, CAMBRIDGE, MASSACHUSETTS.       *
 *        See the source file SLIB.C for more information.                  *

 * Reorganization of files (Mar 1999) by Alan W Black <awb@cstr.ed.ac.uk>

 * Documentation support

*/
#include <cstdio>
#include "EST_cutils.h"
#include "siod.h"
#include "siodp.h"
#include "siodeditline.h"

void setdoc(LISP name,LISP doc)
{
    /* Set documentation string for name */
    LISP lpair = assq(name,siod_docstrings);
    if (lpair == NIL)
	siod_docstrings = cons(cons(name,doc),siod_docstrings);
    else
    {
	cerr << "SIOD: duplicate builtin function: " <<
	    get_c_string(name) << endl;
	cerr << "SIOD: probably an error" << endl;
	CDR(lpair) = doc;
    }
}

static LISP siod_doc(LISP args,LISP penv)
{
    /* Return documentation string for sym */
    (void)penv;
    LISP lpair,val,tmp,code;
    LISP var_docstrings;

    if (TYPE(car(args)) != tc_symbol)
	return rintern("No documentation available for non-symbol.");
    tmp = envlookup(car(args),penv);
    if NNULLP(tmp) 
	val = car(tmp);
    else
	val = VCELL(car(args));
    if EQ(val,unbound_marker)
	return rintern("Symbol is unbound.");
    else
    {
	var_docstrings = symbol_value(rintern("var-docstrings"),penv);
	lpair = assq(car(args),var_docstrings);
	if (lpair)
	    return cdr(lpair);
	else
	    rintern("No documentation available for symbol.");	    
    }
    switch (TYPE(val))
    {
      case tc_subr_0:
      case tc_subr_1:
      case tc_subr_2:
      case tc_subr_3:
      case tc_subr_4:
      case tc_lsubr:
      case tc_fsubr:
      case tc_msubr:
	lpair = assq(car(args),siod_docstrings);
	if (lpair != NIL)
	    return cdr(lpair);
	else
	    return rintern("No documentation available for builtin function.");
	break;
      case tc_closure:
	code = val->storage_as.closure.code;
	if ((TYPE(cdr(code)) == tc_cons) &&
	    (TYPE(car(cdr(cdr(code)))) == tc_string))
	    return car(cdr(cdr(code)));
	else
	    return rintern("No documentation available for user-defined function.");
      default:
	return rintern("No documentation available for symbol.");
    }
	
    return rintern("No documentation available for symbol.");
}

static LISP siod_all_function_docstrings(void)
{
    // Returns all an assoc list of ALL functions that have any form 
    // of documentation strings, internal functions or user defined.
    LISP docs = siod_docstrings;
    
    // But we need user defined function with docstrings too.
    // The docustring must start with a ( to be included
    LISP l = oblistvar;
    LISP code,val;

    // Search the oblist for functions
    for(;CONSP(l);l=CDR(l))
    {
	if (VCELL(car(l)) == NIL) continue;
	switch(TYPE(VCELL(CAR(l))))
	{
	  case tc_closure:
	    val = VCELL(CAR(l));
	    code = val->storage_as.closure.code;
	    if ((CONSP(code)) &&
		(CONSP(cdr(code))) &&
		(CONSP(cdr(cdr(code)))) &&
		(TYPE(car(cdr(cdr(code)))) == tc_string))
		docs = cons(cons(car(l),car(cdr(cdr(code)))),docs);
	  default:
	    continue;
	}
    }

    return docs;
}

static int sort_compare_docstrings(const void *x, const void *y)
{
    LISP a=*(LISP *)x;
    LISP b=*(LISP *)y;

    return EST_strcasecmp(get_c_string(car(a)),get_c_string(car(b)));
}

static void siod_print_docstring(const char *symname, 
				 const char *docstring, FILE *fp)
{
    // Print to fp a texinfo list item for this description
    // Take the first line of the docstring as the label, and also remove
    // any indentation in the remainder of the lines
    int i,state;
    (void)symname;
    EST_String ds = docstring;
    const char *dsc;

    if (ds.contains(make_regex("\\[see .*\\]$")))
    {   // Contains a cross reference so replace it with texi xref command
	EST_String rest, ref;
	rest = ds.before(make_regex("\\[see [^\n]*\\]$"));
	ref = ds.after(rest);
	ref = ref.after("[see ");
	ref = ref.before("]");
	ds = rest + EST_String("[\\@pxref\\{") + ref + EST_String("\\}]");
    }

    dsc = ds;

    fprintf(fp,"@item ");
    for (state=0,i=0; dsc[i] != '\0'; i++)
    {
	if (((dsc[i] == '@') ||
	     (dsc[i] == '{') ||
	     (dsc[i] == '}')) &&
	    ((i == 0) ||
	     (dsc[i-1] != '\\')))
	    putc('@',fp);
	if ((dsc[i] == '\\') &&
	    ((dsc[i+1] == '@') ||
	     (dsc[i+1] == '{') ||
	     (dsc[i+1] == '}')))
	    state = state;
	else if (state == 0)
	{
	    putc(dsc[i],fp);
	    if (dsc[i] == '\n')
		state = 1;
	}
	else if (state == 1)
	    if (dsc[i] != ' ')
	    {
		putc(dsc[i],fp);
		state = 0;
	    }
    }
    fprintf(fp,"\n");
}

static LISP siod_sort_and_dump_docstrings(LISP type,LISP filefp)
{
    // sort docstrings then dump them to filefp as a texinfo list
    LISP *array,l,docstrings;
    int num_strings;
    int i;

    if (streq(get_c_string(type),"function"))
	docstrings = siod_all_function_docstrings();
    else if (streq(get_c_string(type),"features"))
	docstrings = symbol_value(rintern("ff_docstrings"),NIL);
    else
	docstrings = symbol_value(rintern("var-docstrings"),NIL);
	
    num_strings = siod_llength(docstrings);
    array = walloc(LISP,num_strings);
    for (l=docstrings,i=0; i < num_strings; i++,l=cdr(l))
	array[i] = car(l);
    qsort(array,num_strings,sizeof(LISP),sort_compare_docstrings);

    for (i=0; i < num_strings; i++)
	siod_print_docstring(get_c_string(car(array[i])),
			     get_c_string(cdr(array[i])),
			     get_c_file(filefp,stdout));

    wfree(array);

    return NIL;

}

const char *siod_docstring(const char *symbol)
{
    LISP doc;

    doc = siod_doc(cons(rintern(symbol),NIL),NIL);

    return get_c_string(doc);
}

const char *siod_manual_sym(const char *symbol)
{
    // For siodline 
    LISP info;

    info = leval(cons(rintern("manual-sym"),
		      cons(quote(rintern(symbol)),NIL)),NIL);

    return get_c_string(info);
}

void siod_saydocstring(const char *symbol)
{
    // This isn't guaranteed to work but might be ok sometimes

    leval(cons(rintern("tts_text"),
	       cons(cons(rintern("doc"),cons(rintern(symbol),NIL)),
		    cons(NIL,NIL))),NIL);

}

void init_subrs_doc(void)
{
 init_fsubr("doc",siod_doc,
 "(doc SYMBOL)\n\
  Return documentation for SYMBOL.");
 init_subr_2("sort-and-dump-docstrings",siod_sort_and_dump_docstrings,
 "(sort-and-dump-docstrings DOCSTRINGS FILEFP)\n\
  DOCSTRINGS is an assoc list of name and document string var-docstrings\n\
  or func-docstrings.  This very individual function sorts the list and \n\
  prints out the documentation strings as texinfo list members to FILEFP.");

}
