/**
 * distribution.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 fs = require('fs');
var extfs = require('fs-extra');
var async = require('async');
var path = require('path');
var synchronized = require('synchronized');

var dibs = require('../../core/dibs');
var dfs = require('../dibs.dist-fs/dist-fs.js');
var Snapshot = require('./snapshot.js');
var utils = require('../../lib/utils.js');
var Package = require('../dibs.core.server/package.js');
var DError = require('../../core/exception.js');


module.exports.init = init;
module.exports.load = loadDistributions;
module.exports.create = createDistribution;
module.exports.remove = removeDistribution;
module.exports.registerPackages = registerPackages;
module.exports.removePackages = removePackages;
module.exports.generateSnapshot = generateSnapshot;
module.exports.removeSnapshot = removeSnapshot;
module.exports.prepareToDownloadPackage = prepareToDownloadPackage;


var DIST_INFO_FILE = 'tizen-ta-dists.idx';

function Distribution(name, options) {
    var self = this;

    this.name = name;
    this.options = options;
    this.time = null;
    this.snapshots = [];
    this.latestSnapshot = null;
    this.path = null;

}

function init(repoPath, callback) {
    extfs.outputJsonSync(path.join(repoPath, DIST_INFO_FILE), []);
    callback(null, {});
}


function loadDistributions(repoPath, callback) {
    var dists = loadDistributionInfo(repoPath);
    if (dists.length === 0) {
        callback(null, dists);
        return;
    }

    async.eachSeries(dists, function (dist, cb) {
        loadDistribution(dist.name, repoPath, function (err, dist2) {
            if (!err) {
                dist.snapshots = dist2.snapshots;
                // choose the latest 'auto' snapshot
                for (var i = 0; i < dist.snapshots.length; i++) {
                    if (dist.snapshots[i].type === 'auto') {
                        dist.latestSnapshot = dist.snapshots[i];
                    }
                }
                // if not, choose latest snapshot
                if (dist.latestSnapshot === null && dist.snapshots.length > 0) {
                    dist.latestSnapshot = dist.snapshots[dist.snapshots.length - 1];
                }
            }
            cb(err);
        });
    }, function (err) {
        callback(err, dists);
    });
}


function loadDistributionInfo(repoPath) {
    return extfs.readJsonSync(path.join(repoPath, DIST_INFO_FILE)).map(function (e) {
        e.path = path.join(repoPath, e.name);
        return e;
    });
}


function loadDistribution(distName, repoPath, callback) {
    // check directory
    var distPath = path.join(repoPath, distName);
    if (!fs.existsSync(distPath)) {
        callback(new DError('TIZENTAREPO021', {
            distPath: distPath
        }), null);
        return;
    }

    // create distribution
    var dist = new Distribution(distName, {});

    // load snapshots
    Snapshot.load(distPath, function (err, snapshots) {
        if (!err) {
            dist.snapshots = snapshots;
        }
        callback(err, dist);
    });
}


function createDistribution(dname, opts, repoPath) {
    var newObj = new Distribution(dname, opts);
    newObj.time = utils.generateTimeStamp();
    newObj.path = path.join(repoPath, newObj.name);

    // create distribution directories
    fs.mkdirSync(path.join(repoPath, dname));
    fs.mkdirSync(path.join(repoPath, dname, 'snapshots'));
    fs.mkdirSync(path.join(repoPath, dname, 'packages'));

    // create snapshot.info file
    extfs.outputJsonSync(path.join(repoPath, dname, 'snapshots.idx'), []);

    // modify distribution.info
    appendDistributionInfo(newObj, repoPath);

    return newObj;
}


function appendDistributionInfo(newDist, repoPath) {
    var dists = loadDistributionInfo(repoPath);

    dists.push(newDist);

    extfs.outputJsonSync(path.join(repoPath, DIST_INFO_FILE), dists.map(function (e) {
        return {
            name: e.name
        };
    }));
}


function removeDistribution(dname, opts, repoPath) {
    // modify distribution.info
    removeDistributionInfo(dname, repoPath);

    // remove distribution directories
    extfs.removeSync(path.join(repoPath, dname));
}


function removeDistributionInfo(dname, repoPath) {
    var dists = loadDistributionInfo(repoPath).filter(function (e) {
        return e.name !== dname;
    });

    extfs.outputJsonSync(path.join(repoPath, DIST_INFO_FILE), dists.map(function (e) {
        return {
            name: e.name
        };
    }));
}


function registerPackages(rpaths, dist, opts, progress, callback) {
    async.waterfall([
        function (cb) {
            if (progress) {
                progress('## Extracting package information...');
            }
            extractPackageInfosFromFiles(rpaths, cb);
        },
        function (fileInfos, cb) {
            registerPackagesWithSync(fileInfos, dist, opts, progress, cb);
        }
    ], function (err, sname) {
        callback(err, sname);
    });
}


function registerPackagesWithSync(fileInfos, dist, opts, progress, callback) {
    var copiedFiles = [];
    var newSnapshot = null;

    synchronized(dist.name, function (cb) {
        async.waterfall([
            function (cb2) {
                Snapshot.appendPackages(fileInfos, dist.latestSnapshot,
                    opts.forceUpload,
                    function (err, s) {
                        if (!err) {
                            newSnapshot = s;
                        }
                        cb2(err, fileInfos);
                    });
            },
            function (fileInfos, cb2) {
                if (progress) {
                    progress('## Copying package files to repository...');
                }
                copyFilesToRepository(fileInfos, opts, dist.path,
                    function (err, copied) {
                        copiedFiles = copied;
                        cb2(err);
                    });
            },
            function (cb2) {
                if (progress) {
                    progress('## Updating snapshot...');
                }
                saveSnapshot(newSnapshot, dist, cb2);
            }
        ], function (err, sname) {
            if (err) {
                rollbackPackageRegisteration(copiedFiles);
                cb(err, null);
            } else {
                cb(err, sname);
            }
        });
    }, function (err, sname) {
        callback(err, sname);
    });
}


// callback( err, copied )
function copyFilesToRepository(fileInfos, opts, distPath, callback) {
    var copied = [];

    async.eachSeries(Object.keys(fileInfos),
        function (dfsPath, cb) {
            var pkg = fileInfos[dfsPath];
            var fname = path.basename(dfsPath);
            var tpath = path.join(distPath, 'packages', fname);

            // get file from remote
            dfs.getFile(tpath, dfsPath, function (err) {
                if (!err) {
                    if (pkg !== null) {
                        // update path property
                        pkg.path = '/packages/' + fname;
                    }
                    copied.push(tpath);
                }
                cb(err);
            });
        },
        function (err) {
            callback(err, copied);
        });
}


//callback( err, pkgs )
function extractPackageInfosFromFiles(rpaths, callback) {
    var fileInfos = {};
    async.eachLimit(rpaths, 8,
        function (dfsPath, cb) {
            async.waterfall([
                function (cb1) {
                    downloadSingleFileIfNotExist(dfsPath, cb1);
                }
            ], function (err) {
                if (!err) {
                    var fileInfo = dfs.getFileInfo(dfsPath);
                    var pkg = {
                        name: path.basename(dfsPath),
                        checksum: fileInfo.checksum,
                        size: fileInfo.size
                    };
                    fileInfos[dfsPath] = pkg;
                }
                cb(err);
            });
        }, function (err) {
            callback(err, fileInfos);
        });
}


function downloadSingleFileIfNotExist(dfsPath, callback) {
    var realPath = dfs.getRealFilePath(dfsPath);
    if (!fs.existsSync(realPath)) {
        dfs.getFile(null, dfsPath, callback);
    } else {
        callback(null);
    }
}


function saveSnapshot(newSnapshot, dist, callback) {
    var filtered = dist.snapshots.filter(function (e) {
        return e.name === newSnapshot.name;
    });

    async.series([
        // remove
        function (cb) {
            if (filtered.length > 0) {
                Snapshot.remove(newSnapshot.name, dist.path, cb);
            } else {
                cb(null);
            }
        },
        // save
        function (cb) {
            Snapshot.save(newSnapshot, dist.path, cb);
        }], function (err) {
        if (filtered.length > 0) {
            var idx = dist.snapshots.indexOf(filtered[0]);
            dist.snapshots.splice(idx, 1);
        }

        // add
        dist.snapshots.push(newSnapshot);

        // only "auto" snapshot can be the latest
        if (newSnapshot.type === 'auto') {
            dist.latestSnapshot = newSnapshot;
        }
        callback(err, newSnapshot);
    });
}


function rollbackPackageRegisteration(copiedFiles) {
    for (var i = 0; i < copiedFiles.length; i++) {
        fs.unlinkSync(copiedFiles[i]);
    }
}


function removePackages(names, dist, opts, callback) {
    var copiedFiles = [];
    async.waterfall([
        // remove from pkg_lists
        function (cb) {
            Snapshot.removePackages(names, dist.latestSnapshot, opts, cb);
        },
        // save snapshot
        function (newSnapshot, cb) {
            saveSnapshot(newSnapshot, dist, cb);
        }
    ], function (err, sname) {
        if (err) {
            callback(err, null);
        } else {
            callback(err, sname);
        }
    });

}


function generateSnapshot(name, dist, opts, callback) {
    if (name !== null && (opts.force === undefined || opts.force === false) &&
        dist.snapshots.filter(function (e) {
            return (e.name === name);
        }).length > 0) {

        callback(new DError('TIZENTAREPO022', {
            snapshot: name
        }), null);
        return;
    }

    var sourceSnapshot = null;
    if (opts.refSnapshot !== undefined) {
        var filtered = dist.snapshots.filter(function (e) {
            return (e.name === opts.refSnapshot);
        });

        if (filtered.length === 0) {
            callback(new DError('TIZENTAREPO023', {
                ref: opts.refSnapshot
            }), null);
            return;
        } else {
            sourceSnapshot = filtered[0];
        }
    } else {
        sourceSnapshot = dist.latestSnapshot;
    }

    // create & save
    var newSnapshot = Snapshot.create(name, sourceSnapshot);
    saveSnapshot(newSnapshot, dist, callback);
}


function removeSnapshot(name, dist, opts, callback) {
    var snapshots = dist.snapshots.filter(function (e) {
        return (e.name === name);
    });

    if (snapshots.length === 0) {
        callback(new DError('TIZENTAREPO024', {
            name: name
        }), null);
        return;
    }

    // remove from snapshot
    var idx = dist.snapshots.indexOf(snapshots[0]);
    dist.snapshots.splice(idx, 1);

    // remove directory
    Snapshot.remove(name, dist.path, callback);
}


function prepareToDownloadPackage(name, dist, opts, callback) {
    var filtered = dist.snapshots.filter(function (e) {
        return e.name === opts.snapshotName;
    });
    if (filtered.length === 0) {
        callback(new DError('TIZENTAREPO024', {
            name: opts.snapshotName
        }), null);
        return;
    }
    var snapshot = filtered[0];
    Snapshot.prepareToDownload(name, snapshot, dist.path, opts.os, callback);
}
