(function () {

function serializeRules(rules, builtinPostprocessors) {
    return "[\n    " + rules.map(function(rule) {
      return serializeRule(rule, builtinPostprocessors);
    }).join(",\n    ") + "\n]";
}

function dedentFunc(func) {
    var lines = func.toString().split(/\n/);

    if (lines.length === 1) {
        return [lines[0].replace(/^\s+|\s+$/g, '')];
    }

    var indent = null;
    var tail = lines.slice(1);
    for (var i = 0; i < tail.length; i++) {
        var match = /^\s*/.exec(tail[i]);
        if (match && match[0].length !== tail[i].length) {
            if (indent === null ||
                match[0].length < indent.length) {
                indent = match[0];
            }
        }
    }

    if (indent === null) {
        return lines;
    }

    return lines.map(function dedent(line) {
        if (line.slice(0, indent.length) === indent) {
            return line.slice(indent.length);
        }
        return line;
    });
}

function tabulateString(string, indent, options) {
    var lines;
    if(Array.isArray(string)) {
      lines = string;
    } else {
      lines = string.toString().split('\n');
    }

    options = options || {};
    tabulated = lines.map(function addIndent(line, i) {
        var shouldIndent = true;

        if(i == 0 && !options.indentFirst) {
          shouldIndent = false;
        }

        if(shouldIndent) {
            return indent + line;
        } else {
            return line;
        }
    }).join('\n');

    return tabulated;
}

function serializeSymbol(s) {
    if (s instanceof RegExp) {
        return s.toString();
    } else if (s.token) {
        return s.token;
    } else {
        return JSON.stringify(s);
    }
}

function serializeRule(rule, builtinPostprocessors) {
    var ret = '{';
    ret += '"name": ' + JSON.stringify(rule.name);
    ret += ', "symbols": [' + rule.symbols.map(serializeSymbol).join(', ') + ']';
    if (rule.postprocess) {
        if(rule.postprocess.builtin) {
            rule.postprocess = builtinPostprocessors[rule.postprocess.builtin];
        }
        ret += ', "postprocess": ' + tabulateString(dedentFunc(rule.postprocess), '        ', {indentFirst: false});
    }
    ret += '}';
    return ret;
}

var generate = function (parser, exportName) {
    if(!parser.config.preprocessor) {
        parser.config.preprocessor = "_default";
    }

    if(!generate[parser.config.preprocessor]) {
        throw new Error("No such preprocessor: " + parser.config.preprocessor)
    }

    return generate[parser.config.preprocessor](parser, exportName);
};

generate.js = generate._default = generate.javascript = function (parser, exportName) {
    var output = "// Generated automatically by nearley\n";
    output +=  "// http://github.com/Hardmath123/nearley\n";
    output += "(function () {\n";
    output += "function id(x) {return x[0]; }\n";
    output += parser.body.join('\n');
    output += "var grammar = {\n";
    output += "    ParserRules: " +
        serializeRules(parser.rules, generate.javascript.builtinPostprocessors)
        + "\n";
    output += "  , ParserStart: " + JSON.stringify(parser.start) + "\n";
    output += "}\n";
    output += "if (typeof module !== 'undefined'"
        + "&& typeof module.exports !== 'undefined') {\n";
    output += "   module.exports = grammar;\n";
    output += "} else {\n";
    output += "   window." + exportName + " = grammar;\n";
    output += "}\n";
    output += "})();\n";
    return output;
};
generate.javascript.builtinPostprocessors = {
    "joiner": "function joiner(d) {return d.join('');}",
    "arrconcat": "function arrconcat(d) {return [d[0]].concat(d[1]);}",
    "nuller": "function(d) {return null;}",
    "id": "id"
}

generate.cs = generate.coffee = generate.coffeescript = function (parser, exportName) {
    var output = "# Generated automatically by nearley\n";
    output +=  "# http://github.com/Hardmath123/nearley\n";
    output += "do ->\n";
    output += "  id = (d)->d[0]\n";
    output += tabulateString(dedentFunc(parser.body.join('\n')), '  ') + '\n';
    output += "  grammar = {\n";
    output += "    ParserRules: " +
        tabulateString(
                serializeRules(parser.rules, generate.coffeescript.builtinPostprocessors),
                '      ',
                {indentFirst: false})
    + ",\n";
    output += "    ParserStart: " + JSON.stringify(parser.start) + "\n";
    output += "  }\n";
    output += "  if typeof module != 'undefined' "
        + "&& typeof module.exports != 'undefined'\n";
    output += "    module.exports = grammar;\n";
    output += "  else\n";
    output += "    window." + exportName + " = grammar;\n";
    return output;
};
generate.coffeescript.builtinPostprocessors = {
    "joiner": "(d) -> d.join('')",
    "arrconcat": "(d) -> [d[0]].concat(d[1])",
    "nuller": "() -> null",
    "id": "id"
}


if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    module.exports = generate;
} else {
    window.generate = generate;
}
})();
