var fs = require('fs');
var path = require('path');
var marked = require('marked');
var cardinal = require('cardinal');
var xtend = require('xtend');
var color = require('./color');
var table = require('text-table');
var addHeader = require('table-header').add;
var chalk = require('chalk');
var wcstring = require('wcstring');
var os = require('os');
var entities = require('entities');

var defaultOptions = {
    collapseNewlines: true,
    space: '',
    hrStart: '',
    hrChar: '-',
    hrEnd: '',
    headingStart: '\n',
    headingEnd: '\n\n',
    headingIndentChar: '#',
    headingIndent: function (token) {
        return Array(token.depth + 1).join(this.headingIndentChar)
    },
    codeStart: '\n',
    codeEnd: '\n\n',
    codePad: '    ',
    codeTheme: os.platform() === 'win32' ? require('./syntaxColor_win') : require('./syntaxColor'),
    blockquoteStart: '\n',
    blockquoteEnd: '\n\n',
    blockquoteColor: 'blockquote',
    blockquotePad: '  > ',
    blockquotePadColor: 'syntax',
    listStart: '\n',
    listEnd: '\n',
    listItemStart: '',
    listItemEnd: '\n',
    listItemColor: 'ul',
    listItemPad: {first: '  * ', regular: '    '},
    listItemPadColor: 'syntax',
    orderedListItemPadTemplate: '  x. ',
    paragraphStart: '',
    paragraphEnd: '\n\n',
    width: process.stdout.columns || 80,
    maxWidth: -1,
    tableStart: '\n',
    tableSeparator: ' ',
    tableEnd: '\n\n'
};

var tokens;
var inline;
var token;
var blockDepth = 0;
var ordinal;
var orderedList = false;

function processInline(src, options) {
    var out = '';
    var cap;

    function outLink (title, href) {
        if (title) {
            out += '[' + color(title, 'strong') + '](' + color(href, 'link') + ')';
        } else {
            out += '(' + color(href, 'link') + ')';
        }
    }

    while (src) {
        // escape
        if (cap = inline.rules.escape.exec(src)) {
          src = src.substring(cap[0].length);
          out += cap[1];
          continue;
        }

        // code
        if (cap = inline.rules.code.exec(src)) {
            src = src.substring(cap[0].length);
            out += color(cap[2], 'code');
            continue;
        }

        // autolink
        if (cap = inline.rules.autolink.exec(src)) {
            src = src.substring(cap[0].length);
            out += color(cap[0], 'link');
            continue;
        }

        // url (gfm)
        if (cap = inline.rules.url.exec(src)) {
            src = src.substring(cap[0].length);
            outLink(null, cap[1]);
          continue;
        }

        // tag
        if (cap = inline.rules.tag.exec(src)) {
            src = src.substring(cap[0].length);
            out += cap[0];
            continue;
        }

        // link
        if (cap = inline.rules.link.exec(src)) {
            src = src.substring(cap[0].length);
            outLink(cap[1], cap[2]);
            continue;
        }

        // reflink, nolink
        if ((cap = inline.rules.reflink.exec(src))
                || (cap = inline.rules.nolink.exec(src))) {
            src = src.substring(cap[0].length);
            out += cap[0];
            continue;
        }

        // strong
        if (cap = inline.rules.strong.exec(src)) {
            src = src.substring(cap[0].length);
            out += color(processInline(cap[2] || cap[1]), 'strong');
            continue;
        }

        // em
        if (cap = inline.rules.em.exec(src)) {
            src = src.substring(cap[0].length);
            out += color(processInline(cap[2] || cap[1]), 'em');
            continue;
        }

        // br
        if (cap = inline.rules.br.exec(src)) {
            src = src.substring(cap[0].length);
            out += '\n';
            continue;
        }

        // del (gfm)
        if (cap = inline.rules.del.exec(src)) {
            src = src.substring(cap[0].length);
            out += color(processInline(cap[1]), 'del');
            continue;
        }

        // text
        if (cap = inline.rules.text.exec(src)) {
            src = src.substring(cap[0].length);
            out += cap[0];
            continue;
        }

        if (src) {
          throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
        }
    }

    return out;
}

function processToken(options) {
    var type = token.type;
    var text = token.text;
    var content;

    function getOption(key) {
        var value = options[key];
        return typeof value === 'function' ? value(token) : value;
    }

    if (text && type != 'code') {
        text = entities.decodeHTML(text);
    }

    switch (type) {
        case 'space': {
            return options.space;
        }
        case 'hr': {
            var hrStr = new Array(options.width).join(options.hrChar) + '\n';
            return options.hrStart + color(hrStr, type) + options.hrEnd;
        }
        case 'heading': {
            text = blockFormat(processInline(text), {
                block_color: type,
                pad: options.headingIndent(token) + ' ',
                pad_color: 'syntax',
                width: options.width
            })

            return options.headingStart + text + options.headingEnd;
        }
        case 'code': {
            content = '';

            if (token.lang === 'raw') {
                return text + '\n';
            }

            try {
                content = cardinal.highlight(text, {
                    theme: chalk.supportsColor
                            ? options.codeTheme
                            : require('cardinal/themes/empty')
                });
            }
            catch (e) {
                content = color(text, type);
            }

            content = content.replace(/^/gm, getOption('codePad'));

            return options.codeStart + content + options.codeEnd;
        }
        case 'table': {
            content = tableFormat(token, options);
            return options.tableStart + content + options.tableEnd;
        }
        case 'blockquote_start': {
            content = '';
            blockDepth++;

            while (next().type !== 'blockquote_end') {
                content += processToken(options);
            }
            content = blockFormat(content, {
                block_color: options.blockquoteColor,
                pad: options.blockquotePad,
                pad_color: options.blockquotePadColor,
                width: options.width
            });

            blockDepth--;
            return options.blockquoteStart + content + options.blockquoteEnd;
        }
        case 'list_start': {
            content = '';
            orderedList = token.ordered;
            ordinal = 1;

            while (next().type !== 'list_end') {
                content += processToken(options);
            }

            return options.listStart + content + options.listEnd;
        }
        case 'loose_item_start':
        case 'list_item_start': {
            content = '';

            while (next().type !== 'list_item_end') {
                if (type === 'text') {
                    content += text;
                } else {
                    content += processToken(options);
                }
            }

            var pad = options.listItemPad;
            if (orderedList) {
                var first = options.orderedListItemPadTemplate.replace('x', String(ordinal));
                var regular = ' '.repeat(first.length);
                pad = { first: first, regular: regular };
                ++ordinal;
            }

            content = blockFormat(
                processInline(content),
                {
                    block_color: options.listItemColor,
                    pad: pad,
                    pad_color: options.listItemPadColor,
                    width: options.width
                }
            );

            return options.listItemStart + content + options.listItemEnd;
        }
        case 'paragraph': {
            if (blockDepth > 0) {
                return text;
            }
            text = blockFormat(
                processInline(text),
                {
                    block_color: type,
                    pad: options.paragraphPad,
                    pad_color: options.paragraphPadColor,
                    width: options.width
                }
            );
            return options.paragraphStart + text + options.paragraphEnd;
        }
        default: {
            if (text) {
                return text;
            }
        }
    }
}

function next() {
    return token = tokens.shift();
}

function blockFormat(src, opts) {
    opts = opts || {};

    var retLines = [];

    src = wcstring(src).wrap(opts.width, opts.pad, function (padStr) {
        if (opts.pad_color) {
            return color(padStr, opts.pad_color)
        }
        return padStr
    });

    return color(src, opts.block_color);
}

function tableFormat (token, options) {
    var aligns = token.align.map(function (a) {
        return (a===null) ? 'l' : a[0];
    });
    var rows = token.cells.map(function (row) {
        return row.map(processInline);
    });
    var headers = token.header.map(function (s) {
        return processInline('**'+s+'**');
    });
    addHeader(rows, headers, { stringLength: getStringWidth });
    return table(rows, {
        align: aligns,
        stringLength: getStringWidth,
        hsep: options.tableSeparator
    });
}

/**
 * Returns the number of columns required to display the given string.
 */
function getStringWidth(str) {
  return wcstring(str).width();
}

exports.parse = function(text, options) {
    tokens = marked.lexer(text);
    inline = new marked.InlineLexer(tokens.links);
    options = xtend(defaultOptions, options);

    var outputArr = [];
    var output;

    if (options.maxWidth !== -1 && options.width > options.maxWidth) {
        options.width = options.maxWidth
    }

    while (next()) {
        outputArr.push(processToken(options));
    }

    if (options.collapseNewlines) {
        output = outputArr.join('').replace(/\n\n\n/g, '\n\n');
    }

    tokens = null;
    token = null;

    return output;
}

exports.parseFile = function(file, options) {
    var filePath = path.resolve(__dirname, file);
    var ret = '';

    try {
        var text = fs.readFileSync(filePath).toString();
        ret = exports.parse(text, options);
    }
    catch (e) {
        throw e;
    }

    return ret;
}
