// docs.cpp: ingame command documentation system

#include "cube.h"

void renderdocsection(void *menu, bool init);

extern hashtable<char *, ident> *idents;

struct docargument
{
    char *token, *desc, *values;
    bool vararg;
};

struct docref
{
    char *name, *ident, *url, *article;
};

struct docexample
{
    char *code, *explanation;
};

struct dockey
{
    char *alias, *name, *desc;
};

struct docident
{
    char *name, *desc;
    vector<docargument> arguments;
    cvector remarks;
    vector<docref> references;
    vector<docexample> examples;
    vector<dockey> keys;
};

struct docsection
{
    char *name;
    vector<docident *> idents;
    void *menu;
};

vector<docsection> sections;
hashtable<char *, docident> docidents; // manage globally instead of a section tree to ensure uniqueness
docsection *lastsection = NULL;
docident *lastident = NULL;

void adddocsection(char *name)
{
    if(!name) return;
    docsection &s = sections.add();
    s.name = newstring(name);
    s.menu = addmenu(s.name, NULL, true, renderdocsection);
    lastsection = &s;
}

void adddocident(char *name, char *desc)
{
    if(!name || !desc || !lastsection) return;
    name = newstring(name);
    docident &c = docidents[name];
    lastsection->idents.add(&c);
    c.name = name;
    c.desc = newstring(desc);
    lastident = &c;
}

void adddocargument(char *token, char *desc, char *values, char *vararg)
{
    if(!lastident || !token || !desc) return;
    docargument &a = lastident->arguments.add();
    a.token = newstring(token);
    a.desc = newstring(desc);
    a.values = values && strlen(values) ? newstring(values) : NULL;
    a.vararg = vararg && atoi(vararg) == 1 ? true : false;
}

void adddocremark(char *remark)
{
    if(!lastident || !remark) return;
    lastident->remarks.add(newstring(remark));
}

void adddocref(char *name, char *ident, char *url, char *article)
{
    if(!lastident || !name) return;
    docref &r = lastident->references.add();
    r.name = newstring(name);
    r.ident = ident && strlen(ident) ? newstring(ident) : NULL;
    r.url = url && strlen(url) ? newstring(url) : NULL;
    r.article = article && strlen(article) ? newstring(article) : NULL;
}

void adddocexample(char *code, char *explanation)
{
    if(!lastident || !code) return;
    docexample &e = lastident->examples.add();
    e.code = newstring(code);
    e.explanation = explanation && strlen(explanation) ? newstring(explanation) : NULL;
}

void adddockey(char *alias, char *name, char *desc)
{
    if(!lastident || !alias) return;
    dockey &k = lastident->keys.add();
    k.alias = newstring(alias);
    k.name = name && strlen(name) ? newstring(name) : NULL;
    k.desc = desc && strlen(desc) ? newstring(desc) : NULL;
}

COMMANDN(docsection, adddocsection, ARG_1STR);
COMMANDN(docident, adddocident, ARG_2STR);
COMMANDN(docargument, adddocargument, ARG_4STR);
COMMANDN(docremark, adddocremark, ARG_1STR);
COMMANDN(docref, adddocref, ARG_3STR);
COMMANDN(docexample, adddocexample, ARG_2STR);
COMMANDN(dockey, adddockey, ARG_3STR);

int stringsort(const char **a, const char **b) { return strcmp(*a, *b); }

char *cvecstr(vector<char *> &cvec, char *substr, int *rline = NULL)
{
    char *r = NULL;
    loopv(cvec) if(cvec[i]) if((r = strstr(cvec[i], substr)) != NULL) 
    { 
        if(rline) *rline = i;
        break; 
    }
    return r;
}

void docundone(int allidents)
{
    cvector inames;
    identnames(inames, !(allidents > 0));
    inames.sort(stringsort);
    loopv(inames)
    {
        docident *id = docidents.access(inames[i]);
        if(id) // search for substrings that indicate undoneness
        {
            cvector srch;
            srch.add(id->name);
            srch.add(id->desc);
            loopvj(id->remarks) srch.add(id->remarks[j]);
            loopvj(id->arguments) 
            { 
                srch.add(id->arguments[j].token);
                srch.add(id->arguments[j].desc);
                srch.add(id->arguments[j].values);
            }
            loopvj(id->references)
            {
                srch.add(id->references[j].ident);
                srch.add(id->references[j].name);
                srch.add(id->references[j].url);
            }
            if(!cvecstr(srch, "TODO") && !cvecstr(srch, "UNDONE")) continue;
        }
        conoutf(inames[i]);
    }
}

void docinvalid()
{
    cvector inames;
    identnames(inames, true);
    inames.sort(stringsort);
    enumerateht(docidents) if(!strchr(docidents.enumc->data.name, ' ') && !identexists(docidents.enumc->data.name)) 
        conoutf(docidents.enumc->data.name);
}

void docfind(char *search)
{
    enumerateht(docidents)
    {
        docident &i = docidents.enumc->data;
        cvector srch;
        srch.add(i.name);
        srch.add(i.desc);
        loopvk(i.remarks) srch.add(i.remarks[k]);
        
        char *r;
        int rline;
        if((r = cvecstr(srch, search, &rline)))
        {
            const int matchchars = 200;
            string match;
            s_strncpy(match, r-srch[rline] > matchchars/2 ? r-matchchars/2 : srch[rline], matchchars/2);
            conoutf("%-20s%s", i.name, match);
        }
    }
}

char *xmlstringenc(char *d, const char *s, size_t len)
{
    if(!d || !s) return NULL;
    struct spchar { char c; char repl[8]; } const spchars[] = { {'&', "&amp;"}, {'<', "&lt;"}, {'>', "gt;"}, {'"', "&quot;"}, {'\'', "&apos;"}};

    char *dc = d;
    const char *sc = s;

    while(*sc && (size_t)(dc - d) < len - 1)
    {
        bool specialc = false;
        loopi(sizeof(spchars)/sizeof(spchar)) if(spchars[i].c == *sc) 
        {
            specialc = true;
            size_t rlen = strlen(spchars[i].repl);
            if(dc - d + rlen <= len - 1)
            {
                memcpy(dc, spchars[i].repl, rlen);
                dc += rlen;
                break;
            }
        }
        if(!specialc) memcpy(dc++, sc, 1);
        *dc = 0;
        sc++;
    }
    return d;
}

void docwritebaseref(char *ref, char *schemalocation, char *transformation)
{
    s_sprintfd(outfile)("docs%cautogenerated_base_reference.xml", PATHDIV);
    FILE *f = fopen(outfile, "w");
    if(!f) return;
    char desc[] = "<description>TODO: Description</description>";

    fprintf(f, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");    
    fprintf(f, "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n", transformation && strlen(transformation) ? transformation : "transformations/cuberef2xhtml.xslt");
    fprintf(f, "<cuberef name=\"%s\" version=\"v0.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"%s\" xmlns=\"http://cubers.net/Schemas/CubeRef\">\n", ref && strlen(ref) ? ref : "Unnamed Reference", schemalocation && strlen(schemalocation) ? schemalocation : "http://cubers.net/Schemas/CubeRef schemas/cuberef.xsd");
    fprintf(f, "\t%s\n", desc);
    fprintf(f, "\t<sections>\n");
    fprintf(f, "\t\t<section name=\"Main\">\n");
    fprintf(f, "\t\t\t%s\n", desc);
    fprintf(f, "\t\t\t<identifiers>\n");
    
    string name;
    enumerateht(*idents)
    {
        ident &id = idents->enumc->data;
        if(id.type != ID_COMMAND) continue;
        fprintf(f, "\t\t\t\t<command name=\"%s\">\n", xmlstringenc(name, id.name, _MAXDEFSTR));
        fprintf(f, "\t\t\t\t\t%s\n", desc);
        if(id.narg != ARG_NONE && id.narg != ARG_DOWN)
        {
            fprintf(f, "\t\t\t\t\t<arguments>\n");
            if(id.narg == ARG_VARI) fprintf(f, "\t\t\t\t\t\t<variableArgument token=\"...\" description=\"TODO\"/>\n");
            else
            {
                int base = id.narg >= ARG_1EST ? ARG_1EST : (id.narg >= ARG_1EXP ? ARG_1EXP : (id.narg >= ARG_1STR ? ARG_1STR : ARG_1INT));
                loopj(id.narg-base+1) fprintf(f, "\t\t\t\t\t\t<argument token=\"%c\" description=\"TODO\"/>\n", (char)(*"A")+j);
            }
            fprintf(f, "\t\t\t\t\t</arguments>\n");
        }
        fprintf(f, "\t\t\t\t</command>\n");
    }
    enumerateht(*idents)
    {
        ident &id = idents->enumc->data;
        if(id.type != ID_VAR) continue;
        fprintf(f, "\t\t\t\t<variable name=\"%s\">\n", xmlstringenc(name, id.name, _MAXDEFSTR));
        fprintf(f, "\t\t\t\t\t<description>TODO</description>\n");
        fprintf(f, "\t\t\t\t\t<value %s description=\"TODO\" minValue=\"%i\" maxValue=\"%i\" defaultValue=\"%i\" %s/>\n", id.min>id.max ? "" : "token=\"N\"", id.min, id.max, *id.storage, id.min>id.max ? "readOnly=\"true\"" : "");
        fprintf(f, "\t\t\t\t</variable>\n");
    }

    fprintf(f, "\t\t\t</identifiers>\n\t\t</section>\n\t</sections>\n</cuberef>\n");
    fclose(f);
}

COMMAND(docundone, ARG_1INT);
COMMAND(docinvalid, ARG_NONE);
COMMAND(docfind, ARG_1STR);
COMMAND(docwritebaseref, ARG_3STR);
VAR(docvisible, 0, 1, 1);
VAR(docskip, 0, 0, 1000);

void toggledoc() { docvisible = !docvisible; }
void scrolldoc(int i) { docskip += i; if(docskip < 0) docskip = 0; }

int numargs(char *args)
{
    if(!args || !strlen(args)) return -1;

    int argidx = -1;
    char *argstart = NULL;

    for(char *t = args; *t; t++)
    {
        if(!argstart && *t != ' ') { argstart = t; argidx++; }
        else if(argstart && *t == ' ') if(t-1 >= args) 
        {
            switch(*argstart)
            {
                case '[': if(*(t-1) != ']') continue; break;
                case '"': if(*(t-1) != '"') continue; break;
                default: break;
            }
            argstart = NULL;
        }
    }
    return argidx;
}

void renderdoc(int x, int y)
{
    if(!docvisible) return;

    char *exp = getcurcommand();
    if(!exp || *exp != '/' || strlen(exp) < 2) return;

    char *c = exp+1;
    size_t clen = strlen(c);
    for(size_t i = 0; i < clen; i++) // search first matching cmd doc by stripping arguments of exp from right to left
    {
        char *end = c+clen-i;
        if(!*end || *end == ' ')
        {
            string cmd;
            s_strncpy(cmd, c, clen-i+1);
            docident *ident = docidents.access(cmd);
            if(ident)
            {
                const int linemax = VIRTW*4/3;
                cvector doclines;

                char *label = doclines.add(newstringbuf(ident->name)); // label
                loopvj(ident->arguments)
                {
                    s_strcat(label, " ");
                    s_strcat(label, ident->arguments[j].token);
                }
                doclines.add(NULL);
                
                cvector desc;
                text_block(ident->desc, linemax, desc); // desc
                loopvj(desc) doclines.add(desc[j]);
                doclines.add(NULL);

                if(ident->arguments.length() > 0) // args
                {
                    extern int commandpos;
                    char *args = strchr(c, ' ');
                    int arg = -1;

                    if(args)
                    {
                        args++;
                        if(commandpos >= 0)
                        {
                            if(commandpos >= args-c)
                            {
                                string a;
                                s_strncpy(a, args, commandpos-(args-c)+1);
                                args = a;
                                arg = numargs(args);
                            }
                        }
                        else arg = numargs(args);
                        
                        if(arg >= 0) // multipart idents need a fixed argument offset
                        {
                            char *c = cmd;
                            while((c = strchr(c, ' ')) && c++) arg--;
                        }
                        
                        // fixes offset for var args
                        if(arg >= ident->arguments.length() && ident->arguments.last().vararg) arg = ident->arguments.length() - 1;
                    }

                    loopvj(ident->arguments) 
                    {
                        docargument *a = &ident->arguments[j];
                        if(!a) continue;
                        char *argstr = doclines.add(new string);
                        s_sprintf(argstr)("\f%d%-8s%s %s%s%s", j == arg ? 4 : 5, a->token, a->desc,
                            a->values ? "(" : "", a->values ? a->values : "", a->values ? ")" : "");
                    }

                    doclines.add(NULL);
                }

                if(ident->remarks.length()) // remarks
                {
                    loopvj(ident->remarks) 
                    {
                         cvector remarks;
                         text_block(ident->remarks[j], linemax, remarks);
                         loopvk(remarks) doclines.add(remarks[k]);
                    }
                    doclines.add(NULL);
                }

                if(ident->examples.length()) // examples
                {   
                    doclines.add(newstring(ident->examples.length() == 1 ? "Example:" : "Examples:"));
                    loopvj(ident->examples)
                    {
                        cvector lines;
                        text_block(ident->examples[j].code, linemax, lines);
                        text_block(ident->examples[j].explanation, linemax, lines);
                        loopvk(lines) doclines.add(lines[k]);
                    }
                    doclines.add(NULL);
                }

                if(ident->keys.length()) // default keys
                {
                    doclines.add(newstring(ident->keys.length() == 1 ? "Default key:" : "Default keys:"));
                    loopvj(ident->keys)
                    {
                        dockey &k = ident->keys[j];
                        s_sprintfd(line)("%-10s %s", k.name ? k.name : k.alias, k.desc ? k.desc : "");
                        doclines.add(newstring(line));
                    }
                    doclines.add(NULL);
                }

                if(ident->references.length()) // references
                {
                    struct category { string label; string refs; }
                    categories[] = {{"related identifiers", ""} , {"web resources", ""}, {"wiki articles", ""}, {"other", ""}};
                    loopvj(ident->references)
                    {
                        docref &r = ident->references[j];
                        char *ref = r.ident ? categories[0].refs : (r.url ? categories[1].refs : (r.article ? categories[2].refs : categories[3].refs));
                        s_strcat(ref, r.name);
                        if(j < ident->references.length()-1) s_strcat(ref, ", ");
                    }
                    loopj(sizeof(categories)/sizeof(category))
                    {
                        if(!strlen(categories[j].refs)) continue;
                        char *line = doclines.add(newstringbuf(categories[j].label));
                        s_strcat(line, ": ");
                        s_strcat(line, categories[j].refs);
                    }
                }

                int screenlines = (VIRTH*2/3/FONTH)-1;
                if(docskip) docskip = min(docskip, doclines.length() - screenlines); // normalize
                bool more = docskip < doclines.length() - screenlines;
               
                for(int j = docskip; j < min(doclines.length(), docskip+screenlines); j++)
                {
                    if(doclines[j]) draw_textf("%s", x, y+j*FONTH, doclines[j]);
                }
                doclines.deletecontentsa();

                if(more) draw_textf("\f4more (F3)", x, y+screenlines*FONTH); // footer
                if(docskip > 0) draw_textf("\f4less (F2)", x, y+(screenlines+1)*FONTH);
                draw_textf("\f4disable doc reference (F1)", x, y+(screenlines+2)*FONTH);
                return;
            }
        }
    }
}

void *docmenu = NULL;

struct msection { char *name; string cmd; };

int msectionsort(const msection *a, const msection *b)
{
    return strcmp(a->name, b->name);
}

void renderdocsection(void *menu, bool init)
{
    static vector<msection> msections;
    msections.setsize(0);
    
    loopv(sections)
    {
        if(sections[i].menu != menu) continue;
        loopvj(sections[i].idents)
        {
            docident &id = *sections[i].idents[j];
            msection &s = msections.add();
            s.name = id.name;
            s_sprintf(s.cmd)("saycommand [/%s ]", id.name);
        }
        msections.sort(msectionsort);
        loopv(msections) { menumanual(menu, i, msections[i].name, msections[i].cmd); }
        return;
    }
}

struct maction { string cmd; };

void renderdocmenu(void *menu, bool init)
{
    static vector<maction> actions;
    actions.setsize(0);

    loopv(sections)
    {
        maction &a = actions.add();
        s_sprintf(a.cmd)("showmenu [%s]", sections[i].name);
        menumanual(menu, i, sections[i].name, a.cmd);
    }
}
