/**
 * Filename    : Generator.js
 * Author      : Robert Cerny
 * Created     : 2006-11-04
 * Last Change : 2006-11-26
 *
 * Description:
 *   Generates documentation for a script of this library. The
 *   documentation should adhere to the rules presented by example in
 *   this file.
 *
 * Usage:
 *   var jsDocGenerator = Generator();
 *   var sourceCodeDoc = jsDocGenerator.create(sourceCodeString);
 *
 * History:
 *   2006-11-04 Created.
 *
 * License:
 */

CERNY.require("CERNY.js.doc.Generator",
              "CERNY.js.String",
              "CERNY.js.Array");

CERNY.js.doc.Generator = function(obj) {

    var self = CERNY.object(obj);
    self.create = CERNY.js.doc.Generator_create;
    self.log = CERNY.Logger("CERNY.js.doc");

    // The sequence of regexps and according replacement values to
    // clean up multi line text.
    var multilineText = [{re: /\r\n/g,
                     value: ""},
        {re: /\s*\*\s*/g,
         value: " "},
        {re: /\s+/g,
         value: " "},
        {re: /\s+$/,
         value: ""}];

    // Fixing the appearance of multi line code
    var multilineCode = [{re: /\*/g,
                          value: ""},
        {re: /^\r\n/,
         value: ""},
        {re: / *\r\n$/,
         value: ""}];

    return self;
};

/**
 * Create documentation for sourceCode.
 *
 * sourceCode - a string containing the source code to document
 * returns - an object with the defined documentation pattern
 */
CERNY.js.doc.Generator_create = function (sourceCode) {
    var doc = {}, attr, value, ascpect, matches, nextFix, log = this.log;
    log.debug("start create");
    log.debug("sourceCode: " + sourceCode.length);

    // The documentation pattern.
    // Is redefined here every time, because rhino seems to have a bug
    // with regular expressions.
    this.docPattern = {
        // The first expression in the require statement  is the current product
        name: {regexp: new RegExp("CERNY\.require.*(CERNY.*)\u0022")},
        author: {regexp: new RegExp("author.*: +(.*)", "gmi")},
        creationDate: {regexp: new RegExp("created.*:\s*(.+)", "gmi")},
        modificationDate: {regexp: new RegExp("last change.*:\s*(.+)", "gmi")},
        documentation: {regexp: new RegExp("description:([\\w\\W]*?) \\* [a-zA-Z]", "mi")},
        history: {regexp: new RegExp("history:([\\w\\W]*?) \\*$", "gmi")},
        uses: {regexp: new RegExp("^(CERNY\.require[^\\)]*)\\)", "gmi")}
        // usage: {regexp: /usage:([\w\W]*) \* history:/gmi,
        //         fixes: multilineCode}
    };


    // Iterate over all attributes in the pattern.
    for (attr in this.docPattern) {
        log.debug("attr: " + attr);

        if (this.docPattern.hasOwnProperty(attr)) {
            aspect = this.docPattern[attr];

            // Apply the regular expression
            matches = aspect.regexp.exec(sourceCode);
            log.debug("matches: " + matches);
            if (matches) {
                value = matches[1];
                log.debug("value: " + value);

                // Apply the fixes
                if (aspect.fixes) {
                    for (var i = 0; i < aspect.fixes.length; i++) {
                        nextFix = aspect.fixes[i];
                        value = value.replace(nextFix.re, nextFix.value);
                    }
                }

                // Set the respective attribute in the documentation
                // object
                doc[attr] = value;
            }
        }
    }
    doc.history = CERNY.js.doc.Generator.extractHistory(doc.history);
    doc.uses = CERNY.js.doc.Generator.extractUses(doc.uses);
    doc.documentation = CERNY.js.doc.Generator.extractDocumentation(doc.documentation);
    doc.functions = CERNY.js.doc.Generator.extractFunctions(sourceCode);
    log.debug("end create");
    return doc;
};

/*
 * Extract the history from the history string in the source code.
 * Cannot handle multiline entries yet.
 *
 * historyString - a string containing the history
 * return - an array of CERNY.js.doc.HistoryEntry
 */
CERNY.js.doc.Generator.extractHistory = function(historyString) {
    if (!isNonEmptyString(historyString)) {
        return [];
    }

    var history = [];
        matches = historyString.match(new RegExp("([0-9]{4}-[0-9]{2}-[0-9]{2} .+)","gmi"));
        log = CERNY.Logger("CERNY.js.doc");

    log.debug("historyStringtring: " + historyString);
    log.debug("start extractHistory");

    if (matches) {
        matches.map(function(match) {
            var segments, date, entry;
            log.debug("match: " + match);

            segments = match.match(new RegExp("([0-9]{4}-[0-9]{2}-[0-9]{2}) (.*)"));
            date = segments[1];
            log.debug("date: " + date);
            entry = segments[2];
            log.debug("entry: " + entry);

            history.push({date: date, entry: entry});
        });
    }

    log.debug("end extractHistory");
    return history;
}

CERNY.js.doc.Generator.extractUses = function(usesString) {
    if (!isNonEmptyString(usesString)) {
        return [];
    }

    var uses = [],
    log = CERNY.Logger("CERNY.js.doc");

    // Remove duplicate whitespace from string
    usesString = usesString.replace(new RegExp("\\s+", "gm"), "");
    log.debug("usesString: " + usesString);

    var segments = usesString.split(new RegExp(","));

    log.debug("start extractUses");
    log.debug("segments: " + segments);

    // The first segment is the require call and the requiring script, so we skip it
    segments.shift();

    segments.map(function(match) {
        uses.push(match.replace(new RegExp("\"", "g"), ""));
    });

    log.debug("end extractUses");
    return uses;
}

CERNY.js.doc.Generator.extractDocumentation = function(docString) {
    if (!isNonEmptyString(docString)) {
        return [];
    }

    var documentation =[],
        log = CERNY.Logger("CERNY.js.doc");

    log.debug("start extractDocumentation");
    log.debug("docString: " + docString);

    // Functions come with a leading start comment marker
    docString = docString.replace(new RegExp("/\\*", "g"), "");

    // Remove the leading comment markers
    docString = docString.replace(new RegExp(" \\*   ", "g"), "");

    // Split the paragraphs
    documentation = docString.split(new RegExp("\\*$", "m"));

    // Clean up the paragraphs
    documentation = documentation.map(function(doc) {
        return CERNY.js.doc.Generator.cleanMultilineDoc(doc);
    });

    // Documentation has unnecessary content at the end and the
    // beginning
    if (documentation[0] == "") {
        documentation.shift();
    }
    documentation.pop();
    log.debug("documentation.length: " + documentation.length);
    log.debug("end extractDocumentation");
    return documentation;
}

CERNY.js.doc.Generator.extractFunctions = function(sourceCode) {
    var functions = [],
        matches,
        log = CERNY.Logger("CERNY.js.doc");

    log.debug("start extractFunction");

    matches = sourceCode.match(new RegExp("\r\n\/\\*\\*([^°]+?) = function", "gmi"));
    if (matches) {
        log.debug("matches.length: " + matches.length);
        matches.map(function(funcDocString) {
            var nameMatch, parameterMatches, parameterName, parameterDoc;
            funcDoc = {};

            log.debug("funcDocString: " + funcDocString);

            // Name
            nameMatch = funcDocString.match(/(CERNY[^ ]+) = function/);

            // Handle the prototype enhancements
            if (!nameMatch) {
                nameMatch = funcDocString.match(/(\w(\.prototype)?\.[^ ]+) = function/);
            }
            log.debug("nameMatch: " + nameMatch[1]);
            funcDoc.name = nameMatch[1];

            // Documentation
            funcDoc.documentation = CERNY.js.doc.Generator.extractDocumentation(funcDocString);

            // Parameters and return value
            funcDoc.parameters = [];
            parameterMatches = funcDocString.match(/ \* [^ ]+ - *?/mg);
            if (parameterMatches) {
                parameterMatches.map(function(parameterStr) {
                    var name = parameterStr.match(/ \* ([^ ]+) -/)[1],
                        parameterDoc = {}, re, documentation;

                    log.debug("name: " + name);
                    re = new RegExp(name + " - ([^°]+?) (\\* [a-z]|\\*\\/)", "m");
                    documentation = funcDocString.match(re)[1];
                    documentation = CERNY.js.doc.Generator.cleanMultilineDoc(documentation);
                    log.debug("documentation: " + documentation);

                    if (name == "return") {
                        funcDoc.returnValue = documentation;
                    } else {
                        parameterDoc.name = name;
                        parameterDoc.documentation = documentation;
                        funcDoc.parameters.push(parameterDoc);
                    }
                });
            }

            functions.push(funcDoc);
        });
    }

    log.debug("end extractFunction");
    return functions;
}

CERNY.js.doc.Generator.cleanMultilineDoc = function(doc) {
    return doc.replace(/\r\n/g, " ").replace(/ \* /g, " ").replace(/ +/g, " ").replace(/^ /, "").replace(/ $/, "");
}
