/**
 * internal-sync.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 extfs = require('fs-extra');
var async = require('async');
var path = require('path');
var _ = require('underscore');

var dibs = require('../../core/dibs');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var Distribution = require('../org.tizen.repository/distribution.js');
var Snapshot = require('../org.tizen.repository/snapshot.js');
var TizenUtils = require('../org.tizen.common/tizen_utils.js');
var Utils = require('../../lib/utils.js');


module.exports.synchronize = synchronize;
module.exports.updateSyncOptions = updateSyncOptions;


function synchronize(sourceDistribution, opts, parent, callback) {
    async.waterfall([
        // search distribution
        function (cb) {
            parent.searchDistributions({
                distName: sourceDistribution.syncDistName
            }, cb);
        },
        function (targetDists, cb) {
            if (targetDists.length <= 0) {
                callback(new DError('TREPO004', {
                    dist: sourceDistribution.syncDistName
                })); return;
            }
            if (sourceDistribution.type !== targetDists[0].type) {
                callback(new DError('TASYNC001')); return;
            }
            cb(null, targetDists[0]);
        },
        // synchronize
        function (targetDistribution, cb) {
            synchronizeDistribution(sourceDistribution, targetDistribution, opts, parent, cb);
        }
    ], function (err) {
        callback(err, sourceDistribution);
    });
}


function progressLog(contents, opts) {
    if (opts.progress) {
        opts.progress(contents);
    }
}


function synchronizeDistribution(sourceDistribution, targetDistribution, opts, parent, callback) {
    var sourceSnapshot = sourceDistribution.latestSnapshot;
    var targetSnapshot;
    var pkgs;

    async.waterfall([
        function (cb) {
            if (opts.SYNC_SNAPSHOT) {
                parent.searchSnapshots({
                    name: opts.SYNC_SNAPSHOT,
                    repoType: 'tizen',
                    distName: targetDistribution.name
                }, function (err, snaps) {
                    if (err) {
                        cb(err);
                    } else if (snaps.length === 0) {
                        cb(new Error('can not find snapshot'));
                    } else {
                        targetSnapshot = snaps[0];
                        cb(null);
                    }
                });
            } else {
                targetSnapshot = targetDistribution.latestSnapshot;
                cb(null);
            }
        },
        function (cb) {
            progressLog('# Checking updated packages...', opts);
            pkgs = Snapshot.getUpdatedPackages(targetSnapshot, sourceSnapshot);

            // skip synchronizing when no updated pkgs
            if (pkgs.length === 0) {
                progressLog(' - nothing', opts);
                cb('SKIP');
            } else {
                progressLog(' - ' + pkgs.map(function (e) {
                    return e.name + '(' + e.os + ')';
                }).join(' '), opts);
                cb(null);
            }
        },
        function (cb) {
            progressLog('# Preparing updated packages...', opts);
            Distribution.preparePackagesFromRemote(pkgs, targetSnapshot, targetDistribution.path, opts.progress, cb);
        },
        function (rpaths, cb) {
            async.mapSeries(rpaths,
                function (dfsPath, cb1) {
                    var idx = rpaths.indexOf(dfsPath);
                    var pInfo = TizenUtils.getInfoFromPackageFileName(path.basename(dfsPath));
                    if (pInfo && pInfo.os && pkgs[idx].os && pkgs[idx].os !== pInfo.os) {
                        copyFile(pkgs[idx], targetDistribution, parent, cb1);
                    } else {
                        cb1(null, dfsPath);
                    }
                },
                cb);
        },
        function (rpaths, cb) {
            var buildInfo = [];
            _.each(rpaths, function (dfsPath) {
                var pInfo = TizenUtils.getInfoFromPackageFileName(path.basename(dfsPath));
                if (pInfo && pInfo.os && pInfo.name) {
                    var build = {};
                    var regex = /__[A-Z]/;
                    _.each(targetSnapshot.osPackages[pInfo.os][pInfo.name].options, function (value, key) {
                        var match = regex.exec(key);
                        if (match) {
                            build[match.input] = value;
                        }
                    });
                    buildInfo.push({
                        rpath: dfsPath,
                        build: build
                    });
                }
            });
            cb(null, rpaths, buildInfo);
        },
        function (rpaths, buildInfo, cb) {
            progressLog('# Registering updated packages...', opts);

            // NOTE. sync must consider compatibility
            Distribution.registerPackages(rpaths, sourceDistribution, {
                noChangeLogCheck: true,
                buildInfo: buildInfo
            }, opts.progress, cb);
        }
    ], function (err) {
        if (err && err === 'SKIP') {
            callback(null);
        } else {
            callback(err);
        }
    });
}


function copyFile(pkg, syncDist, parent, callback) {
    var syncSnapshot = syncDist.latestSnapshot;
    var tempPath = null;
    async.waterfall([
        //  create temp dir
        function (cb) {
            Utils.genTemp(cb);
        },
        // download
        function (tempDir, cb) {
            tempPath = tempDir;
            Distribution.downloadPackagesFromRemote([pkg], syncSnapshot, tempPath, syncDist.path, null, cb);
        },
        function (lpaths, cb) {
            addFiles(lpaths, cb);
        }],
        function (err, rpaths) {
            if (tempPath !== null) {
                extfs.removeSync(tempPath);
            }
            if (!err) {
                callback(err, rpaths[0]);
            } else {
                callback(err, null);
            }
        });
}


function addFiles(lpaths, callback) {
    async.mapLimit(lpaths, 8,
        function (lpath, cb) {
            // NOTE. SYNC must be finished in 1 hour after adding file to DFS
            //      Otherwise, added files will be removed on DFS
            dfs.addFile(null, lpath, {
                lifetime: 60 * 60 * 1000
            }, function (err, dfsPath) {
                cb(err, dfsPath);
            });
        },
        function (err, rpaths) {
            callback(err, rpaths);
        });
}


function updateSyncOptions(dist, opts, parent, callback) {

    // if auto sync, add
    if (opts.SYNC_DIST_NAME && opts.SYNC_PERIOD > 0) {
        addSyncAction(dist.name, opts, parent);
    } else {
        removeSyncAction(dist.name, parent);
    }

    // write to config
    parent.setSyncToConfig(dist.name, 'internal',
        null, opts.SYNC_DIST_NAME, opts.SYNC_PERIOD);

    callback(null);
}


function addSyncAction(name, opts, parent) {
    parent.getServer().addSyncAction(name, function (cb) {
        parent.synchronizeDistribution(name, opts, function (err, dist) {
            cb(err);
        });
    }, null, opts.SYNC_PERIOD * 1000);
}


function removeSyncAction(name, parent) {
    parent.getServer().removeSyncAction(name);
}

