/**
 * snapshot-migration.js
 * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * Sungmin Kim <sm.art.kim@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 async = require('async');
var fs = require('fs');
var optimist = require('optimist');
var path = require('path');
var _ = require('underscore');
var mysql = require('mysql');

// var dibs = require('../../core/dibs.js');
var utils = require('../../lib/utils.js');

var Package = require('../org.tizen.common/package.js');
var ProjectModel = require('../dibs.model.common/project.js');
var SnapshotModel = require('../dibs.model.common/snapshot.js');
var ArtifactModel = require('../dibs.model.common/artifact.js');


// print log messages using color code.
function stylize(str, style) {
    var styles = {
        grey: [90, 39],
        red: [31, 39],
        green: [32, 39],
        yellow: [33, 39]
    };
    return '\033[' + styles[style][0] + 'm' + str + '\033[' + styles[style][1] + 'm';
}

['grey', 'yellow', 'red', 'green'].forEach(function (style) {
    String.prototype.__defineGetter__(style, function () {
        return stylize(this, style);
    });
});

// custom setting
var customlog = {
    info: function (str) { console.log(str.toString('utf-8').green); },
    error: function (str) { console.log(str.toString('utf-8').red); },
    warn: function (str) { console.log(str.toString('utf-8').yellow); }
};

var usageMessage =
    'This tool is command line interface for migrating snapshots information into database\n' +
    'Usage: $0 <SUBCOMMAND> [OPTS]\n' +
    'Subcommands:\n' +
    '  migrate      Migrate snapshots and artifacts from a repository to database\n' +
    '  validate     Valdate snapshot migration process\n' +
    'Subcommand usage:\n' +
    '  migrate      snapshot-migration migrate -r <reposiotry path> [-D <distribution name>] -H <db host> -u <db user name> -p <db password> -d <db name>\n' +
    '  valdate      snapshot-migration validate -r <reposiotry path> [-D <distribution name>] -H <db host> -u <db user name> -p <db password> -d <db name>\n';

var argv = optimist.usage(usageMessage)
    .describe('r', 'repository path.            { dibs-repo }')
    .describe('D', 'target distribution.        { develop }')
    .describe('H', 'database host.              { db-host }')
    .describe('u', 'database user name.         { db-user }')
    .describe('p', 'database password.          { db-password }')
    .describe('d', 'target database name.       { db-name } ')
    .describe('h', 'show help')
    .string('s')
    .string('u')
    .string('p')
    .string('d')
    .string('D')
    .string('H')
    .alias({ h: 'help', s: 'server', r: 'repo', D: 'dist', H: 'host', u: 'user', p: 'password', d: 'database'})
    .argv;

if (argv.h) {
    optimist.showHelp();
    process.exit(0);
}

// arguments
var repoPath = argv.r;
var targetDist = argv.D;

var dbHost = argv.H;
var dbUser = argv.u;
var dbPassword = argv.p;
var dbName = argv.d;

var latestSnapshot = null;
var prevSnapshot = null;
var PROJECTS = {};

var subCmds = argv._;

// validate sub-commands
if (!subCmds || subCmds.length === 0) {
    console.log('Usage: snapshot-migration <SUBCOMMAND> [OPTS]');
    console.log('Try \'snapshot-migration --help\' for more information');
    process.exit(-1);
}

// validate sub command
if (argv.r === undefined || argv.r === true) {
    customlog.error('Options should be specified!\n');
    optimist.showHelp();
    process.exit(-1);
}


handleSubcommands(subCmds[0]);


function handleSubcommands(command) {
    switch (command) {
    case 'migrate':
        migrateSnapshotsFileToDB(function (err) {
            if (err) {
                customlog.error(err);
                customlog.error('Snapshot migration from repository to database terminates incompletely!');
                process.exit(-1);
            } else {
                customlog.warn('Snapshot migration from repository to database is successful!');
                process.exit(0);
            }
        });
        break;
    case 'validate':
        validateMigration(function (err) {
            if (err) {
                customlog.error(err);
                customlog.error('The migrated database is incomplete! Please check about it!!');
                process.exit(-1);
            } else {
                customlog.warn('Validate snapshot-migration successfully. The migrated database is sound.');
                process.exit(0);
            }
        });
        break;
    default:
        customlog.error('Invalid sub-command: ' + subCmds);
        process.exit(-1);
    }
}

function validateMigration(callback) {
    console.log('Start validating migrated database.');

    async.waterfall([
        function (cb) {
            loadRepo(function (err1, dists) {
                cb(err1, dists);
            });
        },
        function (dists, cb) {
            async.eachLimit(dists, 1, function (dist, cb1) {
                async.waterfall([
                    function (cb2) {
                        customlog.info('# - Load package information');
                        loadPackages(dist.snapshots, dist.path, function (err1, snapshots) {
                            dist.snapshots = snapshots;
                            cb2(err1, dist);
                        });
                    },
                    function (dist, cb2) {
                        customlog.info('# - Compare snapshot files and database');
                        validateSnapshots(dist, cb2);
                    }
                ], cb1);
            },
            function (err1) {
                cb(err1);
            });
        }
    ],
    function (err) {
        callback(err);
    });
}


function loadPackages(snapshots, distPath, callback) {
    async.mapLimit(snapshots, 1, function (snapshot, cb) {
        loadPackageFromSnapshot(snapshot, distPath, function (err1, result) {
            cb(err1, result);
        });
    },
    function (err, snapshots) {
        callback(err, snapshots);
    });
}


function loadPackageFromSnapshot(snapshot, distPath, callback) {
    var snapshotPath = path.join(distPath, snapshot.path);
    var envs = [];

    customlog.info('# - Read packages from \'' + snapshot.name + '\'');

    async.waterfall([
        function (cb) {
            customlog.info(' ## - Read \'os_info\' file.');
            fs.readFile(path.join(snapshotPath, 'os_info'), { encoding: 'utf8'}, function (err1, data) {
                envs = data.split('\n');
                cb(err1);
            });
        },
        function (cb) {
            customlog.info(' ## - Read \'archive_pkg_list\' file.');
            fs.readFile(path.join(snapshotPath, 'archive_pkg_list'), { encoding: 'utf8'}, function (err1, data) {
                snapshot.archives = data.split('\n');
                cb(err1);
            });
        },
        function (cb) {
            customlog.info(' ## - Read \'pkg_list_{os}\' file.');

            var osPackages = {};
            async.each(envs, function (env, cb1) {
                osPackages[env] = {};

                var pkgListPath = path.join(snapshotPath, 'pkg_list_' + env);
                fs.readFile(pkgListPath, { encoding: 'utf8' }, function (err1, data) {
                    if (err1) {
                        customlog.error(err1);
                        cb1(err1);
                    } else {
                        Package.getPkgListFromString(data, function (err2, pkgs) {
                            if (err2) {
                                customlog.error(err2);
                            } else {
                                osPackages[env] = pkgs;
                            }
                            cb1(err2);
                        });
                    }
                });
            },
            function (err) {
                snapshot.osPackages = osPackages;
                cb(err, snapshot);
            });
        }
    ],
    function (err, snapshot) {
        callback(err, snapshot);
    });
}


function validateSnapshots(dist, callback) {
    async.eachLimit(dist.snapshots, 1, function (snapshotFile, cb) {
        validateSnapshot(snapshotFile, dist.name, function (err1) {
            if (err1) {
                customlog.error('# - \'' + snapshotFile.name + '\' snapshot is bad');
            } else {
                customlog.warn('# - \'' + snapshotFile.name + '\' snapshot is good');
            }
            cb(err1);
        });
    },
    function (err) {
        callback(err);
    });
}


function validateSnapshot(srcSnapshot, distName, callback) {
    customlog.info('# - Valdate ' + srcSnapshot.name);

    async.waterfall([
        function (cb) {
            searchSnapshotFromDB({ name: srcSnapshot.name, distName: distName }, function (err1, result) {
                if (err1) {
                    cb(err1);
                } else {
                    cb(err1, result);
                }
            });
        },
        function (snapshotDB, cb) {
            compareSnapshot(srcSnapshot, snapshotDB, function (err1) {
                cb(err1, snapshotDB);
            });
        },
        function (snapshotDB, cb) {
            checkPackageExistenceInRepo(distName, snapshotDB.artifacts, function (err1) {
                cb(err1, snapshotDB);
            });
        },
        function (snapshotDB, cb) {
            checkPackageExistenceInDB(distName, snapshotDB.artifacts, cb);
        }
    ],
    function (err) {
        callback(err);
    });
}


function checkPackageExistenceInRepo(distName, artifacts, callback) {
    var distPath = path.join(repoPath, distName);
    customlog.info('# - Check existence of artifacts in \'' + distPath + '\'');

    async.eachLimit(artifacts, 1, function (artifact, cb) {
        var binaryPath = path.join(distPath, artifact.path);
        fs.stat(binaryPath, function (err1, stat) {
            if (err1) {
                customlog.error(err1);
                cb(err1);
            } else {
                if (artifact.size === stat.size) {
                    cb(null);
                } else {
                    var msg = 'Artifact size is different!! ' + artifact.size + ':' + stat.size;
                    cb(new Error(msg));
                }
            }
        });
    },
    function (err) {
        callback(err);
    });
}


function checkPackageExistenceInDB(distName, artifacts, callback) {
    var distPath = path.join(repoPath, distName);
    var conn = null;

    customlog.info('# - Check existence of artifacts with DB');

    async.series([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            var archivePath = path.join(distPath, 'source');
            customlog.info(' ## - Check packages of archive type from \'' + archivePath + '\'');
            fs.readdir(archivePath, function (err1, pkgList) {
                if (err1) {
                    customlog.error(err1);
                    cb(err1);
                } else {
                    async.eachLimit(pkgList, 1, function (pkgName, cb1) {
                        ArtifactModel.select(conn, { distName: distName, file: pkgName }, function (err2, results) {
                            if (err2) {
                                customlog.error(err2);
                                cb1(err2);
                            } else {
                                if (results.length === 0) {
                                    var msg = pkgName + ' does not exist in ' + distName;
                                    cb1(new Error(msg));
                                } else {
                                    cb1(null);
                                }
                            }
                        });
                    },
                    function (err1) {
                        if (err1) {
                            customlog.error(err1);
                        }
                        cb(err1);
                    });
                }
            });
        },
        function (cb) {
            var binaryPath = path.join(distPath, 'binary');
            customlog.info(' ## - Check packages of binary type from \'' + binaryPath + '\'');
            fs.readdir(binaryPath, function (err1, pkgList) {
                if (err1) {
                    customlog.error(err1);
                    cb(err1);
                } else {
                    async.eachLimit(pkgList, 1, function (pkgName, cb1) {
                        ArtifactModel.select(conn, { distName: distName, file: pkgName }, function (err2, results) {
                            if (err2) {
                                customlog.error(err2);
                                cb1(err2);
                            } else {
                                if (results.length === 0) {
                                    var msg = pkgName + ' does not exist in ' + distName;
                                    cb1(new Error(msg));
                                } else {
                                    cb1(null);
                                }
                            }
                        });
                    },
                    function (err1) {
                        if (err1) {
                            customlog.error(err1);
                        }
                        cb(err1);
                    });
                }
            });
        }
    ],
    function (err) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(err1);
            }

            if (err) {
                customlog.error(err);
            }
            callback(err);
        });
    });
}

function compareSnapshot(srcSnapshot, destSnapshot, callback) {

    // compare snapshot
    var snapshotKeys = ['name', 'time', 'path'];
    var diffCount = 0;
    _.each(snapshotKeys, function (key) {
        if (!_.isEqual(destSnapshot[key], srcSnapshot[key])) {
            diffCount++;
        }
    });

    if (!_.isEqual(destSnapshot.attribute, srcSnapshot.type)) {
        diffCount++;
    }

    if (diffCount !== 0) {
        customlog.info(destSnapshot.name + ' is different with file!!');
    }

    // compare archive type
    var archiveCount = 0;
    _.each(srcSnapshot.archives, function (archive) {
        if (_.isEmpty(_.where(destSnapshot.artifacts, { name: archive, type: 'archive'}))) {
            archiveCount++;
        }
    });

    if (archiveCount !== 0) {
        customlog.info(destSnapshot.name + ' has different archives with file!!');
    }

    // compare binary type
    var binaryCount = 0;
    _.each(destSnapshot.artifacts, function (artifact) {
        if (artifact.type === 'archive') {
            return;
        }

        _.each(srcSnapshot.osPackages[artifact.environment], function (binaryPkg) {
            if (artifact.name === binaryPkg.name) {
                _.each(artifact, function (value, key) {
                    if (key === 'attribute') {
                        // console.log(_.isEqual(artifact.attribute, binaryPkg.attr));
                        if (artifact.attribute && !_.isEqual(artifact.attribute, binaryPkg.attr)) {
                            customlog.error(key + ' - ' + artifact.attribute + ':' + binaryPkg.attr);
                            binaryCount++;
                        }
                    } else if (key === 'environment') {
                        // console.log(_.isEqual(artifact.description, binaryPkg.desc));
                        if (!_.isEqual(artifact.environment, binaryPkg.os)) {
                            customlog.error(key + ' - ' + artifact.environment + ':' + binaryPkg.os);
                            binaryCount++;
                        }
                    } else if (key === 'description') {
                        // console.log(_.isEqual(artifact.description, binaryPkg.desc));
                        if (artifact.description && !_.isEqual(artifact.description, binaryPkg.desc)) {
                            customlog.error(key + ' - ' + artifact.description + ':' + binaryPkg.desc);
                            binaryCount++;
                        }
                    } else if (key === 'options') {
                        // console.log(artifact.options);
                        _.each(artifact.options, function (value, key) {
                            var regExp = /__[A-Z]/;
                            if (regExp.exec(key) === null) {
                                // console.log(' - options ' + key);
                                // console.log(_.isEqual(artifact.options[key], binaryPkg[key]));
                                if (!_.isEqual(artifact.options[key], binaryPkg[key])) {
                                    customlog.error(key + ' - ' + artifact.options[key] + ':' + binaryPkg[key]);
                                    binaryCount++;
                                }
                            }
                        });
                    } else if (_.contains(['id', 'distName', 'type', 'status', 'file'], key)) {
                        return;
                    } else {
                        // console.log(_.isEqual(artifact[key], binaryPkg[key]));
                        if (!_.isEqual(artifact[key], binaryPkg[key])) {
                            customlog.error(key + ' - ' + artifact[key] + ':' + binaryPkg[key]);
                            binaryCount++;
                        }
                    }
                });
            }
        });
    });

    if (binaryCount !== 0) {
        customlog.info(destSnapshot.name + ' has different binaries with file!!');
    }

    if (diffCount || archiveCount || binaryCount) {
        return callback(new Error(destSnapshot.name + ' database is invalid. Please check about data!!'));
    } else {
        return callback(null);
    }
}


/*
 * migration routine
 */

function migrateSnapshotsFileToDB(callback) {
    console.log('Start migrating snapshots from files to database.');

    async.waterfall([
        /*
        function (cb) {
            // TODO: temporary method for test
            customlog.info('# - Delete snapshots and artifacts database');
            removeSnapshotFromDB(cb);
        },
        */
        function (cb) {
            loadRepo(function (err1, dists) {
                cb(err1, dists);
            });
        },
        function (dists, cb) {
            async.eachLimit(dists, 1, function (dist, cb1) {
                async.waterfall([
                    function (cb2) {
                        customlog.info('# - Get database for project build info from \'' + dist.name + '\' distribution.');
                        searchProjectBuildInfoFromDB({ distName: dist.name }, function (err1, projects) {
                            PROJECTS[dist.name] = projects;
                            cb2(err1);
                        });
                    },
                    function (cb2) {
                        customlog.info('# - Migrate each snapshot from \'' + dist.name + '\' dist to database');
                        migrateSnapshots(dist, dist.snapshots, function (err2) {
                            cb2(err2);
                        });
                    }
                ],
                function (err1) {
                    cb1(err1);
                });
            },
            function (err) {
                cb(err);
            });
        }
    ],
    function (err) {
        callback(err);
    });
}


function loadRepo(callback) {
    console.log('Load repository from filesystem');

    async.waterfall([
        function (cb) {
            customlog.info('# - Check if reposiotry directory exists or not. ' + repoPath);
            fs.exists(repoPath, function (exist) {
                if (!exist) {
                    cb(new Error('Given repository does not exist.'));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            var distInfoPath = path.join(repoPath, 'distribution.info');

            customlog.info('# - Read \'distribution.info\' file from ' + repoPath);
            fs.readFile(distInfoPath, { encoding: 'utf8' }, function (err1, data) {
                if (err1) {
                    customlog.error(err1);
                    cb(err1, null);
                } else {
                    var distributions = null;
                    var dists = loadDistributionInfoString(data, repoPath);

                    if (targetDist) {
                        distributions = _.filter(dists, function (dist) {
                            return (dist.name === targetDist);
                        });
                    } else {
                        distributions = dists;
                    }
                    cb(null, distributions);
                }
            });
        },
        function (distributions, cb) {
            customlog.info('# - Check if each distribution directory is valid or not');
            async.map(distributions, function (dist, cb1) {
                fs.exists(dist.path, function (exist) {
                    if (!exist) {
                        cb1(new Error(dist.path + ' does not exist.'));
                    } else {
                        var snapshotInfo = path.join(dist.path, 'snapshot.info');
                        fs.readFile(snapshotInfo, { encoding: 'utf8' }, function (err2, data) {
                            if (err2) {
                                cb1(err2, null);
                            } else {
                                var results = loadSnapshotInfoString(data);
                                // sort by snapshot time.
                                dist.snapshots = _.sortBy(results, function (snapshot) {
                                    return snapshot.time;
                                });

                                cb1(null, dist);
                            }
                        });
                    }
                });
            },
            function (err, dists) {
                cb(err, dists);
            });
        }
    ],
    function (err, dists) {
        callback(err, dists);
    });
}


/*
 * database APIs
 */
function addSnapshotToDB(snapshot, callback) {
    var conn = null;

    async.waterfall([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            customlog.info(' ## - Insert snapshots and artifacts into database');
            SnapshotModel.insert(conn, snapshot, function (err1, result) {
                snapshot.id = result.insertId;
                cb(err1);
            });
        }
    ],
    function (err) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(err1);
            }

            if (err) {
                customlog.error(err);
                callback(err, null);
            } else {
                callback(null, snapshot);
            }
        });
    });
}


function removeSnapshotFromDB(callback) {
    var conn = null;

    async.waterfall([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            var sql = 'DELETE FROM artifact_info WHERE id >= 1';
            conn.query(sql, function (err) {
                if (err) {
                    customlog.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            var sql = 'DELETE FROM snapshot_info WHERE id >= 1';
            conn.query(sql, function (err) {
                if (err) {
                    customlog.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            var sql = 'DELETE FROM snapshot_artifact WHERE id >= 1';
            conn.query(sql, function (err) {
                if (err) {
                    customlog.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            var sql = 'DELETE FROM artifacts WHERE id >= 1';
            conn.query(sql, function (err) {
                if (err) {
                    customlog.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            var sql = 'DELETE FROM snapshots WHERE id >= 1';
            conn.query(sql, function (err) {
                if (err) {
                    customlog.error(err);
                }
                cb(err);
            });
        }
    ],
    function (err) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(err1);
            }

            if (err) {
                customlog.error(err);
            }
            callback(err);
        });
    });
}

function searchSnapshotFromDB(conditions, callback) {
    var conn = null;

    async.waterfall([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            customlog.info(' ## - Select snapshot \'' + conditions.name);
            SnapshotModel.select(conn, conditions, function (err1, results) {
                if (err1) {
                    cb(err1, null);
                } else {
                    if (results.length !== 0) {
                        cb(null, results[0]);
                    } else {
                        cb(null, null);
                    }
                }
            });
        }
    ],
    function (err, snapshot) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(err1);
            }

            if (err) {
                customlog.error(err);
                return callback(err, null);
            } else {
                return callback(null, snapshot);
            }
        });
    });
}


function searchProjectBuildInfoFromDB(conditions, callback) {
    var conn = null;

    async.waterfall([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            customlog.info(' ## - Query projects from \'' + conditions.distName + '\' dist.');
            ProjectModel.select(conn, conditions, function (err1, results) {
                if (err1) {
                    customlog.error(err1);
                }
                cb(err1, results);
            });
        }
    ],
    function (err, results) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(' ## - Closing db connection has a problem!!');
                customlog.error(err1);
            }

            if (err) {
                return callback(err, []);
            } else {
                return callback(null, results);
            }
        });
    });

}


function loadSnapshot(snapshot, distPath, callback) {
    var snapshotPath = path.join(distPath, snapshot.path);
    var distName = distPath.substring(distPath.lastIndexOf('/') + 1, distPath.length);

    var environments = [];
    var artifacts = [];

    var isTemp = false;
    var tempArtifacts = [];

    customlog.info(' ## - Snapshot: ' + snapshot.name + ', path: ' + snapshotPath);

    async.waterfall([
        function (cb) {
            if (snapshotPath.match(/\/temp/)) {
                customlog.info(' ## - Read temporary snapshot directory');

                fs.readdir(snapshotPath, function (err1, data) {
                    isTemp = true;

                    var tempPath = snapshotPath.substring(snapshotPath.indexOf('/temp/jobs'), snapshotPath.length);
                    var regExp = /(.*)_(.*)_(.*)\.zip/;
                    tempArtifacts = _.map(data, function (fileName) {
                        var res = regExp.exec(fileName);
                        if (res) {
                            return {
                                name: res[1],
                                version: res[2],
                                os: res[3],
                                status: 'TEMP',
                                path: path.join(tempPath, fileName)
                            };
                        }
                    });
                    tempArtifacts = _.compact(tempArtifacts);
                    cb(err1);
                });
            } else {
                cb(null);
            }
        },
        function (cb) {
            customlog.info(' ## - Read \'os_info\' file from \'' + snapshot.name + '\'');
            fs.readFile(path.join(snapshotPath, 'os_info'), { encoding: 'utf8'}, function (err1, data) {
                environments = data.split('\n');
                cb(err1);
            });
        },
        function (cb) {
            customlog.info(' ## - Read \'archive_pkg_list\' file from \'' + snapshot.name + '\'');
            fs.readFile(path.join(snapshotPath, 'archive_pkg_list'), { encoding: 'utf8'}, function (err1, data) {
                var archives = data.split('\n');
                createArchiveArtifacts(archives, distName, distPath, function (err1, results) {
                    if (err1) {
                        cb(err1);
                    } else {
                        artifacts = results;
                        cb(null);
                    }
                });
            });
        },
        function (cb) {
            customlog.info(' ## - Read \'pkg_list_{os}\' file from \'' + snapshot.name + '\'');
            async.each(environments, function (env, cb1) {
                var pkgListPath = path.join(snapshotPath, 'pkg_list_' + env);
                createBinaryArtifacts(pkgListPath, distName, distPath, null, function (err1, results) {
                    artifacts = _.union(artifacts, results);
                    cb1(err1);
                });
            },
            function (err) {
                if (!err) {
                    if (isTemp) {
                        // update temporary artifact path because pkg_list_xxx file has invalid file path info in case of temp snapshot.
                        artifacts = _.map(artifacts, function (artifact) {
                            var res = _.where(tempArtifacts, { name: artifact.name, version: artifact.version, os: artifact.environment });
                            if (!_.isEmpty(res)) {
                                artifact.path = res[0].path;
                                artifact.status = res[0].status;
                            }
                            return artifact;
                        });
                    }
                }
                cb(err);
            });
        },
        function (cb) {
            var options = {
                origin: snapshot.origin,
                uploader: 'sync-manager@user',
                remote: false,
                __ENVIRONMENTS: environments
            };

            // name, distribution_name, type, time, status,
            // attribute, path, description, options
            SnapshotModel.create(snapshot.name, distName, 'tizen', snapshot.time, null,
                snapshot.type, snapshot.path, null, options, function (err, result) {

                    result.artifacts = artifacts;
                    cb(err, result);
                });
        }
    ],
    function (err, snapshotObj) {
        callback(err, snapshotObj);
    });
}


function createBinaryArtifacts(pkgListPath, distName, distPath, options, callback) {

    async.waterfall([
        function (cb) {
            fs.readFile(pkgListPath, { encoding: 'utf8' }, function (err1, data) {
                if (err1) {
                    customlog.error(err1);
                }
                cb(err1, data);
            });
        },
        function (data, cb) {
            Package.getPkgListFromString(data, function (err1, pkgs) {
                if (err1) {
                    customlog.error(err1);
                }
                cb(err1, pkgs);
            });
        },
        function (pkgs, cb) {
            generateBinaryArtifacts(pkgs, distName, distPath, options, function (err1, results) {
                if (err1) {
                    customlog.error(err1);
                }
                cb(err1, results);
            });
        }
    ],
    function (err, results) {
        callback(err, results);
    });
}


function generateBinaryArtifacts(fileArtifacts, distName, distPath, options, callback) {

    // name, distribution_name, type, version,
    // environment, attribute, status, file, path,
    // size, checksum, description, options

    async.map(fileArtifacts, function (artifact, cb) {
        var filePath = artifact.path;
        var fileName = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.length);

        var opts = _.clone(artifact);
        delete opts.name;
        delete opts.version;
        delete opts.os;
        delete opts.attr;
        delete opts.status;
        delete opts.path;
        delete opts.size;
        delete opts.checksum;
        delete opts.desc;

        var keys = _.allKeys(opts);

        var options = {};
        _.each(keys, function (key) {
            options[key] = opts[key];
        });

        ArtifactModel.create(artifact.name, distName, 'binary', artifact.version,
            artifact.os, artifact.attr, artifact.status, fileName, artifact.path,
            artifact.size, artifact.checksum, artifact.desc, options,
            function (err, result) {
                cb(err, result);
            });
    },
    function (err, results) {
        callback(err, results);
    });
}


function createArchiveArtifacts(artifactObjs, distName, distPath, callback) {

    // name, distribution_name, type, version,
    // environment, attribute, status, file, path,
    // size, checksum, description, options

    async.map(artifactObjs, function (artifactName, cb) {
        var artifactPath = path.join('/source', artifactName);
        var sourcePath = path.join(distPath, artifactPath);
        var artifactSize = 0;

        async.waterfall([
            function (cb1) {
                fs.stat(sourcePath, function (err1, stat) {
                    artifactSize = stat.size;
                    cb1(err1);
                });
            },
            function (cb1) {
                utils.getCheckSum(sourcePath, function (err1, checksum) {
                    cb1(err1, checksum);
                });
            },
            function (checksum, cb1) {
                ArtifactModel.create(artifactName, distName, 'archive', null,
                    null, null, 'OPEN', artifactName, artifactPath,
                    artifactSize, checksum, null, {},
                function (err1, result) {
                    cb1(err1, result);
                });
            }
        ],
        function (err, result) {
            cb(err, result);
        });
    },
    function (err, artifacts) {
        callback(err, artifacts);
    });
}


function migrateSnapshots(dist, snapshots, callback) {
    var filteredSnapshots = _.filter(snapshots, function (snapshot) {
        return (snapshot.type === 'auto');
    });

    latestSnapshot = filteredSnapshots[filteredSnapshots.length - 1];
    customlog.info('# - Latest Snapshot: ' + latestSnapshot.name);

    // iterate all snapshots
    async.mapLimit(snapshots, 1, function (snapshot, cb1) {
        customlog.info(' ## - Migrate snapshot \'' + snapshot.name + '\'');

        migrateSnapshot(snapshot, dist.path, function (err1, result) {
            if (err1) {
                customlog.error('# - Migrating \'' + snapshot.name + '\' snapshot is bad!');
            } else {
                customlog.warn('# - Migrating \'' + snapshot.name + '\' snapshot is good!');
            }
            cb1(err1, result);
        });
    },
    function (err, results) {
        callback(err, results);
    });
}


function migrateSnapshot(snapshot, distPath, callback) {
    var distName = distPath.substring(distPath.lastIndexOf('/') + 1, distPath.length);

    async.waterfall([
        function (cb) {
            if (prevSnapshot) {
                customlog.info(' ## - Search previous snapshot \'' + prevSnapshot + '\' in ' + distName);
                searchSnapshotFromDB({ name: prevSnapshot, distName: distName }, function (err1, result) {
                    cb(err1, result);
                });
            } else {
                cb(null, null);
            }
        },
        function (oldSnapshot, cb) {
            customlog.info(' ## - Load snapshot \'' + snapshot.name + '\' in ' + distName);
            loadSnapshot(snapshot, distPath, function (err1, newSnapshot) {
                if (oldSnapshot) {
                    customlog.info(' ## - Compare artifacts between previous and current snapshot');
                    newSnapshot.artifacts = compareArtifacts(oldSnapshot.artifacts, newSnapshot.artifacts);
                }
                cb(err1, newSnapshot);
            });
        },
        function (snapshotObj, cb) {
            addSnapshotToDB(snapshotObj, function (err1, result) {
                cb(err1, result);
            });
        },
        function (snapshotObj, cb) {
            if (snapshotObj.name === latestSnapshot.name) {
                customlog.info(' ## - Update project build info into latest snapshot: ' + snapshotObj.name);

                searchSnapshotFromDB({ name: snapshotObj.name, distName: distName }, function (err1, result) {
                    if (err1) {
                        cb(err1);
                    } else {
                        result.artifacts = addProjectBuildInfoIntoObj(result.artifacts, PROJECTS[distName]);
                        updateProjectBuildInfo(result.artifacts, function (err1) {
                            cb(err1, result);
                        });
                    }
                });
            } else {
                customlog.info(' ## - Skip updating project build info into snapshot: ' + snapshotObj.name);
                cb(null, snapshotObj);
            }
        }
    ],
    function (err, snapshotObj) {
        if (snapshot.type === 'auto') {
            customlog.info(' ## - Previous Snapshot: ' + snapshot.name);
            prevSnapshot = snapshot.name;
        }
        callback(err, snapshotObj);
    });
}


function compareArtifacts(srcPackages, destPackages) {
    var changed = [];
    var unchanged = [];

    _.each(destPackages, function (destPkg) {
        _.each(srcPackages, function (srcPkg) {
            if (srcPkg.name === destPkg.name && srcPkg.type === destPkg.type &&
                srcPkg.environment === destPkg.environment) {
                if (srcPkg.version !== destPkg.version) {
                    changed.push(destPkg);
                } else {
                    if (srcPkg.checksum !== destPkg.checksum) {
                        customlog.warn('checksum is different!!!');
                        customlog.warn(srcPkg.checksum + ' : ' + destPkg.checksum);
                    } else {
                        unchanged.push(srcPkg);
                    }
                }
            }
        });
    });

    return _.union(unchanged, changed);
}


function addProjectBuildInfoIntoObj(artifacts, projects) {
    var artifactObjs = null;

    artifactObjs = _.map(artifacts, function (artifact) {
        if (artifact.type === 'archive') {
            return artifact;
        }

        var opts = _.clone(artifact.options);

        _.each(projects, function (project) {
            if (project.type === 'Tizen-Source') {
                if (project.options && project.options['LAST_BUILD_INFO']) {
                    var buildInfo = project.options['LAST_BUILD_INFO'][artifact.environment];
                    if (buildInfo) {
                        _.each(buildInfo.packageNames, function (name) {
                            if (name === artifact.name) {
                                if (buildInfo.version !== artifact.version) {
                                    var errorMsg = 'Artifact version is different from last build info of project.\n' +
                                        ' - ARTIFACT:' + artifact.name + ' / ' + artifact.version + ' / ' + artifact.environment + '\n' +
                                        ' - PROJECT:' + project.name + ' / ' + buildInfo.version;
                                    customlog.error(errorMsg);
                                } else {
                                    opts['__BUILD_DISTRIBUTION'] = project.distName;
                                    opts['__GIT_REPO'] = project.options['GIT_REPO'];
                                    opts['__GIT_BRANCH'] = project.options['GIT_BRANCH'];
                                    opts['__GIT_COMMIT_ID'] = buildInfo.gitCommit;
                                    opts['__PROJECT_NAME'] = project.name;
                                    opts['__PROJECT_TYPE'] = project.type;
                                    opts['__PRE_BUILD'] = project.options['PRE_BUILD'];
                                    opts['__POST_BUILD'] = project.options['POST_BUILD'] || [];
                                    opts['__USE_GIT_SUBMODULES'] = project.options['USE_GIT_SUBMODULES'];
                                }
                            }
                        });
                    }
                }
            } else if (project.type === 'Tizen-Binary') {
                if (project.options && project.options['PKG_NAME'] &&
                    project.options['PKG_NAME'] === artifact.name) {
                    opts['__BUILD_DISTRIBUTION'] = project.distName;
                    opts['__PROJECT_NAME'] = project.name;
                    opts['__PROJECT_TYPE'] = project.type;
                }
            }
        });

        artifact.options = opts;
        return artifact;
    });

    return artifactObjs;
}


function updateProjectBuildInfo(artifacts, callback) {
    var conn = null;

    async.waterfall([
        function (cb) {
            createDBConnection(function (err, client) {
                conn = client;
                cb(err);
            });
        },
        function (cb) {
            async.eachLimit(artifacts, 1, function (artifact, cb1) {
                if (artifact.type === 'binary') {
                    addArtifactInfo(conn, artifact, cb1);
                } else {
                    cb1(null);
                }
            },
            function (err) {
                cb(err);
            });
        }
    ],
    function (err) {
        conn.end(function (err1) {
            if (err1) {
                customlog.error(err1);
            }

            if (err) {
                customlog.error(err);
            }
            callback(err);
        });
    });
}


function addArtifactInfo(conn, artifact, callback) {
    var options = _.clone(artifact.options);
    var buildOpts = {};

    var regex = /__[A-Z]/;
    _.each(options, function (value, key) {
        var match = regex.exec(key);

        if (match) {
            buildOpts[match.input] = value;
        }
    });

    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err) {
                cb(err);
            });
        },
        function (cb) {
            async.each(_.keys(buildOpts), function (idx, cb1) {
                var strNtype = utils.objectToStringAndType(buildOpts[idx]);
                var sql = 'INSERT INTO artifact_info SET ' +
                    '  artifact_id = ' + utils.DBStr(artifact.id) +
                    ', property = ' + utils.DBStr(idx) +
                    ', value = ' + utils.DBStr(strNtype.string) +
                    ', type = ' + utils.DBStr(strNtype.type);

                conn.query(sql, function (err) {
                    if (err) {
                        console.error(err);
                    }
                    cb1(err);
                });
            },
            function (err) {
                cb(err);
            });
        },
        function (cb) {
            conn.query('COMMIT', function (err) {
                cb(err);
            });
        }
    ],
    function (err) {
        if (err) {
            conn.query('ROLLBACK', function () {});
        }
        callback(err);
    });
}

/*
 * utility APIs
 */

function createDBConnection(callback) {
    var config = {
        host: dbHost || '127.0.0.1',
        port: 3306,
        user: dbUser || 'root',
        password: dbPassword || 'password',
        database: dbName || 'test'
    };

    var client = mysql.createConnection(config);
    client.connect(function (err) {
        if (err) {
            customlog.error(err);
        }
        callback(err, client);
    });
}

// parse distribution.info and convert it into distribution object.
function loadDistributionInfoString(contents, repoPath) {
    var lines = contents.split('\n');

    var dists = [];
    var newDist = null;
    for (var i = 0; i < lines.length; i++) {
        var line = lines[i];
        var toks = line.split(/[: ]+/);

        if (toks[0] === 'name') {
            if (newDist !== null) {
                dists.push(newDist);
            }
            newDist = { name: toks[1] };
            newDist.path = path.join(repoPath, newDist.name);
        } else if (toks[0] === 'time') {
            newDist.time = toks[1];
        } else if (toks[0] === 'id') {
            newDist.uid = toks[1];
        } else {
            // ignore
        }
    }

    if (newDist !== null) {
        dists.push(newDist);
    }

    return dists;
}

// parse snapshot.info and convert it into snapshot object.
function loadSnapshotInfoString(contents) {
    var lines = contents.split('\n');

    var snapshots = [];
    var snapshot = null;
    for (var i = 0; i < lines.length; i++) {
        var line = lines[i];
        var toks = line.split(/[: ]+/);

        if (toks[0] === 'name') {
            if (snapshot !== null) {
                snapshots.push(snapshot);
            }
            snapshot = { name: toks[1] };
        } else if (toks[0] === 'time') {
            snapshot.time = toks[1];
        } else if (toks[0] === 'type') {
            snapshot.type = toks[1];
        } else if (toks[0] === 'path') {
            snapshot.path = toks[1];
        }
    }

    if (snapshot !== null) {
        snapshots.push(snapshot);
    }

    return snapshots;
}