/**
 * parser.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var PEG = require('pegjs');
var fs = require('fs');
var Path = require('path');
var async = require('async');
var _ = require('underscore');

/**
* DIBS tizen pkginfo.manifest file parser
* @module models/tizen-common/parser
*/

var parser = null;
var depParser = null;
var changeParser = null;

function setParser(callback) {
    if (parser !== null) {
        callback(null);
    } else {
        fs.readFile(Path.join(__dirname, 'pkg.pegjs'), {
            encoding: 'utf-8'
        }, function (err, rules) {
            parser = PEG.buildParser(rules);
            callback(err);
        });
    }
}

function setDepParser(callback) {
    if (depParser !== null) {
        callback(null);
    } else {
        fs.readFile(Path.join(__dirname, 'dep.pegjs'), {
            encoding: 'utf-8'
        }, function (err, rules) {
            depParser = PEG.buildParser(rules);
            callback(err);
        });
    }
}
function setChangeParser(callback) {
    if (changeParser !== null) {
        callback(null);
    } else {
        fs.readFile(Path.join(__dirname, 'changelog.pegjs'), {
            encoding: 'utf-8'
        }, function (err, rules) {
            changeParser = PEG.buildParser(rules);
            callback(err);
        });
    }
}
/**
 * Describe dependency
 * @typedef {object} dependency
 * @property {string} package_name - package name
 * @property {string} comp - [option] version compare operand {=,>,<}
 * @property {string} base_version - [option] version for compare
 * @property {string} os - [option] os alias (ubuntu-32/ubuntu-64/windows-32/windows-64/macos-64)
 * @memberOf module:models/tizen-common/parser
 */

/**
 * Describe package infomation
 * @typedef {object} pkginfo
 * @property {string} Package - Package name
 * @property {string} Label - [option] (use install manager)
 * @property {string} Version - Package version
 * @property {string} OS - Install machine os
 * @property {string} Build-host-os - Build machine os
 * @property {string} Maintainer - Maintainer
 * @property {string} Attribute - [option] (use install manager)
 * @property {Array.<module:models/tizen-common/parser.dependency>} Install-dependency - [option] (use install manager)
 * @property {Array.<module:models/tizen-common/parser.dependency>} Build-dependency - [option] (use tizen builder)
 * @property {Array.<module:models/tizen-common/parser.dependency>} Source-dependency - [option] (use tizen builder)
 * @property {string} Conflicts - [option]
 * @property {string} Source - [option]
 * @property {string} Src-path - [option] (use tizen repository)
 * @property {string} Path - [option] (use tizen repository)
 * @property {string} Origin - [option]
 * @property {string} SHA256 - [option] (use tizen repository)
 * @property {string} Size - [option] (use tizen repository)
 * @property {string} Uncompressed-size - [option] (use install manager)
 * @property {string} C-%%% - [option] (use tizen repository)
 * @property {string} Description - [option]
 * @memberOf module:models/tizen-common/parser
 */

/**
 * @callback callback_err_pkginfos
 * @param {error|undefined} error
 * @param {Array.<module:models/tizen-common/parser.pkginfo>} pkginfos
 * @memberOf module:models/tizen-common/parser
 */

function runPostParseProcess(pkg) {
    if (!pkg.OS || pkg.OS.length === 0) {
        throw new Error('Parse Error : package ' + pkg.Package + ' os is not setted');
    }
    //if(!pkg['Build-host-os'] || pkg['Build-host-os'].length === 0){
    //    throw new Error("Parse Error : package "+pkg.Package+" build host os is not setted");
    //}
    pkg.OS = _.map(pkg.OS.split(','), function (os) {
        return os.trim();
    });
    var depStringList = ['Install-dependency', 'Build-dependency', 'Source-dependency'];
    var depList = _.select(depStringList, function (dep) {
        return pkg[dep];
    });
    _.each(depList, function (dep) {
        try {
            pkg[dep] = depParser.parse(pkg[dep].split(' ').join(''));
        } catch (e) {
            throw new Error('Parse Error : package ' + pkg.Package + ' of ' + dep + ' is not parsed :' + pkg[dep]);
        }
        _.each(pkg[dep], function (depIdx) {
            if (depIdx.os === undefined && pkg.OS.length === 1) {
                depIdx.os = pkg.OS[0];
            }
        });
    });
    pkg.Maintainer = _.map(pkg.Maintainer.split(','), function (Maintainer) {
        return Maintainer.trim();
    });
    if (pkg.Changelog) {
        pkg.Changelog = changeParser.parse(pkg.Changelog);
    }

    //validation fields
    var fields = ['Package', 'OS', 'Build-host-os', 'Install-dependency', 'Build-dependency', 'Source-dependency',
        'Attribute', 'Label', 'Description', 'Source', 'Version', 'Maintainer', 'Changelog', 'Uncompressed-size', 'Size',
        'Path', 'Origin', 'SHA256', 'Conflicts'];
    var pkgFields = _.keys(pkg);
    var customLeft = _.difference(pkgFields, fields);
    _.each(customLeft, function (field) {
        if (!field.match(/^C-/)) {
            throw new Error('Parse Error : package ' + pkg.Package + ' has weird field : ' + field);
        }
    });
}

/**
 * Parsing pkginfo.manifest string
 * @function manifestStringParser
 * @param {module:models/tizen-common/parser.callback_err_pkginfos} callback  - callback(error,pkginfos)
 * @memberOf module:models/tizen-common/parser
 */

function manifestStringParser(string, callback) {
    // remove commant
    var pkgData = _.map(_.filter(string.split('\n'), function (str) {
        return str[0] !== '#' ||
            str.trim() === '#Change log';
    }), function (str) {
        return str.replace(/[ \t]*$/, '');
    }).join('\n');
    var pkgtemp = pkgData.split(/\n\n\n*/);
    pkgData = pkgtemp.join('\n\n');
    async.waterfall([
        function (wcb) {
            setParser(wcb);
        },
        function (wcb) {
            setDepParser(wcb);
        },
        function (wcb) {
            setChangeParser(wcb);
        },
        function (wcb) {
            try { wcb(null, parser.parse(pkgData));
            } catch (e) {
                if (e.line || (e.location && e.location.start && e.location.start.line)) {
                    var errLine = e.line;
                    if (e.location && e.location.start && e.location.start.line) {
                        errLine = e.location.start.line;
                    }
                    var lines = pkgData.split('\n');
                    lines[errLine - 1] = lines[errLine - 1] + '  <<<<- Parse error';
                    var point = _.last(_.first(lines, errLine + 2), 6).join('\n');
                    var error = new Error('Parse Error : \n' + point);
                    wcb(error, null);
                } else {
                    wcb(e, null);
                }
            }
        },
        function (rawManifest, wcb) {
            try {
                wcb(null, _.map(rawManifest, function (pkg) {
                    runPostParseProcess(pkg);
                    return pkg;
                }));
            } catch (e) {
                wcb(e, null);
            }
        }
    ], function (err, data) {
        callback(err, data);
    });
}

module.exports.manifestStringParser = manifestStringParser;

/**
 * Parsing pkginfo.manifest file
 * @function manifestParser
 * @param {module:models/tizen-common/parser.callback_err_pkginfos} callback  - callback(error,pkginfos)
 * @memberOf module:models/tizen-common/parser
 */

module.exports.manifestParser = function (file, callback) {
    async.waterfall([
        function (wcb) {
            fs.readFile(file, {
                encoding: 'utf-8'
            }, wcb);
        }, manifestStringParser
    ], callback);
};

/**
 * Parsing changelog string
 * @function changelogStringParser
 * @param {module:models/tizen-common/parser.callback_err_pkginfos} callback  - callback(error,pkginfos)
 * @memberOf module:models/tizen-common/parser
 */

function changelogStringParser(string, callback) {

    // remove commant
    var changeData = _.map(
        _.filter(string.split('\n'),
            function (str) {
                return str[0] !== '#' || str.trim() === '#Change log';
            }
        ), function (str) {
            return str.replace(/[ \t]*$/, '');
        }
    ).join('\n');
    async.waterfall([
        function (wcb) {
            setChangeParser(wcb);
        },
        function (wcb) {
            try {
                wcb(null, changeParser.parse(changeData));
            } catch (e) {
                wcb(e, null);
            }
        }
    ], function (err, data) {
        if (err) {
            callback(new Error('Parse ERROR : changelog => [ ' + err.message + '=>' + err.stack + ']'), data);
        } else {
            callback(err, data);
        }
    });
}

module.exports.changelogStringParser = changelogStringParser;

/**
 * Parsing changelog file
 * @function changelogParser
 * @param {module:models/tizen-common/parser.callback_err_changelogs} callback  - callback(error,changelogs)
 * @memberOf module:models/tizen-common/parser
 */
module.exports.changelogParser = function (file, callback) {
    async.waterfall([
        function (wcb) {
            fs.readFile(file, {
                encoding: 'utf-8'
            }, wcb);
        },
        function (data, wcb) {
            changelogStringParser(data, wcb);
        }], callback);
};
