'use strict';

var encoding = require('encoding');
var sharedFuncs = require('./shared');

/**
 * Exposes general compiler function. Takes a translation
 * object as a parameter and returns binary MO object
 *
 * @param {Object} table Translation object
 * @return {Buffer} Compiled binary MO object
 */
module.exports = function(table) {
    var compiler = new Compiler(table);
    return compiler.compile();
};

/**
 * Creates a MO compiler object.
 *
 * @constructor
 * @param {Object} table Translation table as defined in the README
 */
function Compiler(table) {
    this._table = table || {};
    this._table.headers = this._table.headers || {};
    this._table.translations = this._table.translations || {};

    this._translations = [];

    this._writeFunc = 'writeUInt32LE';

    this._handleCharset();
}

/**
 * Magic bytes for the generated binary data
 */
Compiler.prototype.MAGIC = 0x950412de;

/**
 * Handles header values, replaces or adds (if needed) a charset property
 */
Compiler.prototype._handleCharset = function() {
    var parts = (this._table.headers['content-type'] || 'text/plain').split(';'),
        contentType = parts.shift(),
        charset = sharedFuncs.formatCharset(this._table.charset),
        params = [];

    params = parts.map(function(part) {
        var parts = part.split('='),
            key = parts.shift().trim(),
            value = parts.join('=');

        if (key.toLowerCase() === 'charset') {
            if (!charset) {
                charset = sharedFuncs.formatCharset(value.trim() || 'utf-8');
            }
            return 'charset=' + charset;
        }

        return part;
    });

    if (!charset) {
        charset = this._table.charset || 'utf-8';
        params.push('charset=' + charset);
    }

    this._table.charset = charset;
    this._table.headers['content-type'] = contentType + '; ' + params.join('; ');

    this._charset = charset;
};

/**
 * Generates an array of translation strings
 * in the form of [{msgid:... , msgstr:...}]
 *
 * @return {Array} Translation strings array
 */
Compiler.prototype._generateList = function() {
    var list = [];

    list.push({
        msgid: new Buffer(0),
        msgstr: encoding.convert(sharedFuncs.generateHeader(this._table.headers), this._charset)
    });

    Object.keys(this._table.translations).forEach((function(msgctxt) {
        if (typeof this._table.translations[msgctxt] !== 'object') {
            return;
        }
        Object.keys(this._table.translations[msgctxt]).forEach((function(msgid) {
            if (typeof this._table.translations[msgctxt][msgid] !== 'object') {
                return;
            }
            if (msgctxt === '' && msgid === '') {
                return;
            }

            var msgid_plural = this._table.translations[msgctxt][msgid].msgid_plural,
                key = msgid,
                value;

            if (msgctxt) {
                key = msgctxt + '\u0004' + key;
            }

            if (msgid_plural) {
                key += '\u0000' + msgid_plural;
            }

            value = [].concat(this._table.translations[msgctxt][msgid].msgstr || []).join('\u0000');

            list.push({
                msgid: encoding.convert(key, this._charset),
                msgstr: encoding.convert(value, this._charset)
            });
        }).bind(this));
    }).bind(this));

    return list;
};

/**
 * Calculate buffer size for the final binary object
 *
 * @param {Array} list An array of translation strings from _generateList
 * @return {Object} Size data of {msgid, msgstr, total}
 */
Compiler.prototype._calculateSize = function(list) {
    var msgidLength = 0,
        msgstrLength = 0,
        totalLength = 0;

    list.forEach(function(translation) {
        msgidLength += translation.msgid.length + 1; // + extra 0x00
        msgstrLength += translation.msgstr.length + 1; // + extra 0x00
    });

    totalLength = 4 + // magic number
        4 + // revision
        4 + // string count
        4 + // original string table offset
        4 + // translation string table offset
        4 + // hash table size
        4 + // hash table offset
        (4 + 4) * list.length + // original string table
        (4 + 4) * list.length + // translations string table
        msgidLength + // originals
        msgstrLength; // translations

    return {
        msgid: msgidLength,
        msgstr: msgstrLength,
        total: totalLength
    };
};

/**
 * Generates the binary MO object from the translation list
 *
 * @param {Array} list translation list
 * @param {Object} size Byte size information
 * @return {Buffer} Compiled MO object
 */
Compiler.prototype._build = function(list, size) {
    var returnBuffer = new Buffer(size.total),
        curPosition = 0,
        i, len;

    // magic
    returnBuffer[this._writeFunc](this.MAGIC, 0);

    // revision
    returnBuffer[this._writeFunc](0, 4);

    // string count
    returnBuffer[this._writeFunc](list.length, 8);

    // original string table offset
    returnBuffer[this._writeFunc](28, 12);

    // translation string table offset
    returnBuffer[this._writeFunc](28 + (4 + 4) * list.length, 16);

    // hash table size
    returnBuffer[this._writeFunc](0, 20);

    // hash table offset
    returnBuffer[this._writeFunc](28 + (4 + 4) * list.length * 2, 24);

    // build originals table
    curPosition = 28 + 2 * (4 + 4) * list.length;
    for (i = 0, len = list.length; i < len; i++) {
        list[i].msgid.copy(returnBuffer, curPosition);
        returnBuffer[this._writeFunc](list[i].msgid.length, 28 + i * 8);
        returnBuffer[this._writeFunc](curPosition, 28 + i * 8 + 4);
        returnBuffer[curPosition + list[i].msgid.length] = 0x00;
        curPosition += list[i].msgid.length + 1;
    }

    // build translations table
    for (i = 0, len = list.length; i < len; i++) {
        list[i].msgstr.copy(returnBuffer, curPosition);
        returnBuffer[this._writeFunc](list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8);
        returnBuffer[this._writeFunc](curPosition, 28 + (4 + 4) * list.length + i * 8 + 4);
        returnBuffer[curPosition + list[i].msgstr.length] = 0x00;
        curPosition += list[i].msgstr.length + 1;
    }

    return returnBuffer;
};

/**
 * Compiles translation object into a binary MO object
 *
 * @return {Buffer} Compiled MO object
 */
Compiler.prototype.compile = function() {
    var list = this._generateList(),
        size = this._calculateSize(list);

    // sort by msgid
    list.sort(function(a, b) {
        if (a.msgid > b.msgid) {
            return 1;
        }
        if (a.msgid < b.msgid) {
            return -1;
        }
        return 0;
    });

    return this._build(list, size);
};