/**
 * snapshot.js
 * Copyright (c) 2000 - 2017 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 fs = require('fs');
var util = require('util');
var extfs = require('fs-extra');
var path = require('path');
var async = require('async');
var _ = require('underscore');
var yaml = require('js-yaml');

var dibs = require('../../core/dibs');
var Artifact = require('../../plugins/dibs.model.common/artifact.js');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var Package = require('../org.tizen.common/package.js');
var Utils = require('../../lib/utils.js');
var DError = require('../../core/exception.js');
var SnapshotModel = require('../dibs.model.common/snapshot.js');
var FeedAtom = require('./feed-atom.js');

module.exports.create = createSnapshot;
module.exports.remove = removeSnapshots;

module.exports.getUpdatedPackages = getUpdatedPackages;

module.exports.searchSnapshots = searchSnapshots;
module.exports.searchSnapshotList = searchSnapshotList;

module.exports.registerArtifacts = registerArtifacts;
module.exports.deregisterPackages = deregisterPackages;
module.exports.generateSnapshot = generateSnapshot;


function createSnapshot(distribution, baseSnapshotName, opts, callback) {
    var timestamp = Utils.generateTimeStamp(true);

    var name;
    var path;
    var attribute;
    var uploader = 'DIBS';

    var distName = distribution.name;

    if (opts && opts.isTemp) {
        name = 'TEMP_JOB-' + opts.tempJobId.toString();
    } else if (opts && opts.name) {
        name = opts.name;
    } else {
        name = timestamp;
    }

    if (opts && opts.attribute) {
        attribute = opts.attribute;
    } else if (opts && opts.isTemp) {
        attribute = 'temp';
    } else {
        attribute = 'auto';
    }

    if (opts && opts.userEmail) {
        uploader = opts.userEmail;
    }

    if (opts && opts.path) {
        path = opts.path;
    } else {
        path = '/snapshots/' + name;
    }

    async.waterfall([
        function (cb) {
            var cond = {
                distName: distName,
                name: name
            };

            dibs.rpc.datamgr.searchSnapshots(cond, function (err, results) {
                if (err) {
                    return cb(err);
                } else if (results && results.length > 0) {
                    if (opts.force === undefined || opts.force === false) {
                        return cb(new DError('VSREPO022', {snapshot: name}));
                    } else {
                        var snapshotList = [];
                        snapshotList.push(name);
                        removeSnapshots(snapshotList, distribution, opts, function (err) {
                            cb(err);
                        });
                    }
                } else {
                    return cb(null);
                }
            });
        },
        function (cb) {
            if (!baseSnapshotName) {
                var options = {};

                SnapshotModel.create(name, distName, distribution.type, timestamp, 'OPEN', attribute, path, opts.desc, options,
                    function (err, s) {
                        s.artifacts = [];
                        s.options.uploader = uploader;
                        cb(err, s, null);
                    });
            } else {
                var cond = {
                    distName: distName,
                    name: baseSnapshotName,
                    artifacts: true
                };
                dibs.rpc.datamgr.searchSnapshots(cond, function (err, results) {
                    if (err) {
                        return cb(err);
                    } else if (results.length !== 1) {
                        return cb(new DError('TREPO023', {ref: baseSnapshotName}));
                    } else {
                        var oldSnapshotData = results[0];
                        var newSnapshotData = _.clone(oldSnapshotData);
                        newSnapshotData.name = name;
                        newSnapshotData.path = path;
                        newSnapshotData.time = timestamp;
                        newSnapshotData.attribute = attribute;
                        newSnapshotData.options.uploader = uploader;

                        return cb(null, newSnapshotData, oldSnapshotData);
                    }
                });
            }
        }], callback);
}

function removeSnapshots(snapshotList, distribution, opts, callback) {
    var removedArtifacts = [];
    async.series([
        function (cb) {
            async.eachSeries(snapshotList, function (snapshotName, cb1) {
                dibs.rpc.datamgr.searchSnapshots({
                    distName: distribution.name,
                    name: snapshotName
                }, function (err1, results) {
                    if (err1) {
                        return cb1(err1);
                    } else if (!results || results.length === 0) {
                        return cb1(null);
                    } else {
                        dibs.rpc.datamgr.deleteSnapshot(results[0].id, function (err1, artifacts) {
                            if (_.isArray(artifacts)) {
                                removedArtifacts = _.union(removedArtifacts, artifacts);
                            }
                            cb1(err1);
                        });
                    }
                });
            }, cb);
        },
        function (cb) {
            removeSnapshotsFile(snapshotList, distribution.path, cb);
        },
        function (cb) {
            async.eachSeries(removedArtifacts, function (artifact, cb1) {
                dibs.rpc.datamgr.searchArtifacts({distName: artifact.distName, path: artifact.path, status: 'OPEN'}, function (err1, results) {
                    if (err1) {
                        return cb1(err1);
                    } else if (results && results.length > 0) {
                        // skip remove file
                        return cb1(null);
                    } else {
                        var file = path.join(distribution.path, artifact.path);
                        var versionPath = path.dirname(file);
                        extfs.remove(versionPath, function (err1) {
                            if (err1) {
                                dibs.log.warn('Fail remove package files... ' + file);
                            }
                            cb1(err1);
                        });
                    }
                });
            }, cb);
        }
    ], callback);
}

function saveSnapshotFile(snapshot, distPath, callback) {
    var snapshots = [];
    var snapshotDirPath = path.join(distPath, snapshot.path);

    async.series([
        function (cb) {
            // create directory
            extfs.mkdirs(snapshotDirPath, cb);
        },
        function (cb) {
            fs.writeFile(path.join(snapshotDirPath, 'snapshot.json'), util.inspect(snapshot, {depth: null}), cb);
        },
        function (cb) {
            // add snapshot
            loadSnapshotInfo(distPath, function (err, snapshotList) {
                snapshots = snapshotList;
                cb(err);
            });
        },
        function (cb) {
            var insertIdx = null;
            if (snapshot.attribute === 'manual') {
                var originSnapshots = snapshots.filter(function (e) {
                    return e.name === snapshot.origin;
                });
                if (originSnapshots.length >= 1) {
                    insertIdx = snapshots.indexOf(originSnapshots[0]) + 1;
                } else {
                    insertIdx = snapshots.length;
                }
            } else {
                insertIdx = snapshots.length;
            }
            var snapshotInfo = _.clone(snapshot);
            delete snapshotInfo.artifacts;
            snapshots.splice(insertIdx, 0, snapshotInfo);

            // save snapshot info
            saveSnapshotInfo(snapshots, distPath, cb);
        }
    ], function (err) {
        if (err) {
            return callback(new DError('VSREPO034', err));
        } else {
            return callback(null, snapshot);
        }
    });
}
function saveAtomFile(snapshot, distPath, opts, callback) {
    var atom = new FeedAtom(snapshot.distName);
    var atomPath = path.join(distPath, 'atom.xml');

    var atomSnapshotPath = path.join(distPath, snapshot.path, 'atom.xml');

    async.series([
        function (cb) {
            fs.exists(atomPath, function (exists) {
                if (exists) {
                    cb(null);
                } else {
                    atom.save(atomPath, cb);
                }
            });
        },
        function (cb) {
            atom.load(atomPath, cb);
        },
        function (cb) {
            async.each(snapshot.newPkgs, function (artifact, cb1) {
                var entry = artifactToEntry(artifact);
                atom.addEntry(entry, cb1);
            }, cb);
        },
        function (cb) {
            async.each(snapshot.updatedPkgs, function (artifact, cb1) {
                var entry = artifactToEntry(artifact);
                atom.updateEntry(entry, cb1);
            }, cb);
        },
        function (cb) {
            async.each(snapshot.removedPkgs, function (artifact, cb1) {
                var id;
                if (artifact.options) {
                    id = artifact.options.identity;
                }
                atom.removeEntry(id, cb1);
            }, cb);
        },
        function (cb) {
            if (opts.isTemp) {
                cb(null);
            } else {
                atom.save(atomPath, cb);
            }
        },
        function (cb) {
            atom.save(atomSnapshotPath, cb);
        }
    ], function (err) {
        if (err) {
            return callback(new DError('VSREPO033', err));
        } else {
            return callback(null, snapshot);
        }
    });
}


function saveSnapshotInfo(snapshots, distPath, callback) {
    var snapshotInfoPath = getSnapshotInfoPath(distPath);
    fs.writeFile(snapshotInfoPath, yaml.dump(snapshots),
        function (err) {
            callback(err);
        });
}


function selectSnapshotInfo(snapshotNameList, snapshotInfoList) {
    var snapshotInfoHash = _.indexBy(snapshotInfoList, 'name');
    return _.map(snapshotNameList, function (name) {
        return snapshotInfoHash[name];
    });
}

function removeSnapshotInfo(snapshotNameList, snapshotInfoList) {
    var snapshotInfoHash = _.indexBy(snapshotInfoList, 'name');
    _.each(snapshotNameList, function (name) {
        _.reject(snapshotInfoList, function (snapshot) {
            return snapshot.name === name;
        });
    });
    return _.compact(_.values(snapshotInfoHash));
}

/*
 *
 */
function removeSnapshotsFile(snapshotNameList, distPath, callback) {
    var removeSnapshotInfoList = [];
    var removedSnapshotInfoList = [];

    var loadDistSnapshotInfo = function (cb) {
        loadSnapshotInfo(distPath, cb);
    };
    var saveRemovedSnapshotInfo = function (snapshotInfoList, cb) {
        removeSnapshotInfoList = selectSnapshotInfo(snapshotNameList, snapshotInfoList);
        removedSnapshotInfoList = removeSnapshotInfo(snapshotNameList, snapshotInfoList);
        saveSnapshotInfo(removedSnapshotInfoList, distPath, cb);
    };
    var removeDirs = function (cb) {
        var removePathList = _.map(removeSnapshotInfoList, function (sInfo) {
            return path.join(distPath, sInfo.path);
        });
        async.eachLimit(removePathList, 10, extfs.remove, cb);
    };
    async.waterfall([
        loadDistSnapshotInfo,
        saveRemovedSnapshotInfo,
        removeDirs
    ],
        function (err) {
            callback(err);
        });
}


function registerArtifacts(distribution, rpaths, opts, callback) {
    var latestSnapshotName;
    var artifacts = [];

    if (distribution.latestSnapshot) {
        latestSnapshotName = distribution.latestSnapshot.name;
    }

    async.waterfall([
        function (cb) {
            if (opts.buildInfo) {
                async.each(opts.buildInfo, function (solution, cb1) {
                    var name = solution.name;
                    var version = solution.version;
                    var file = _.findWhere(solution.files, {type: 'vsix'});
                    var identity = solution.identity;

                    if (!name || !version || !file || !identity) {
                        return cb1(DError('VSREPO032'));
                    }

                    var filePath = path.join('/artifacts', identity, version, file.name);
                    var options = {
                        identity: identity,
                        files: solution.files,
                        projectName: solution.projectName,
                        projectType: solution.projectType,
                        gitRepo: solution.gitRepo,
                        gitBranch: solution.gitBranch,
                        gitCommitID: solution.gitCommitID,
                        publisher: solution.publisher,
                        published: solution.published,
                        manifest: solution.manifest
                    };

                    Artifact.create(solution.name, distribution.name, solution.type, solution.version, solution.environment, null,
                        'OPEN', file.name, filePath, file.size, file.checksum, solution.description, options, function (err, artifact) {
                            artifacts.push(artifact);
                            cb1(err);
                        });
                }, cb);
            } else {
                return cb('Not supported local register artifacts!');
            }
        },
        function (cb) {
            createSnapshot(distribution, latestSnapshotName, opts, cb);
        },
        function (newSnapshotData, oldSnapshotData, cb) {
            newSnapshotData.updatedPkgs = [];
            newSnapshotData.newPkgs = [];
            newSnapshotData.removedPkgs = [];
            newSnapshotData.overwritePkgs = [];

            // append package to new snapshot
            async.each(artifacts, function (artifact, cb1) {
                appendArtifactToSnapshot(oldSnapshotData, newSnapshotData, artifact, opts, cb1);
            }, function (err) {
                cb(err, newSnapshotData);
            });
        },
        function (newSnapshotData, cb) {
            save(distribution, artifacts, newSnapshotData, opts, cb);
        }
    ], callback);
}

function generateSnapshot(distribution, name, opts, callback) {
    var baseSnapshotName;

    opts.name = name;

    if (opts.refSnapshot) {
        baseSnapshotName = opts.refSnapshot;
    } else {
        baseSnapshotName = distribution.latestSnapshot.name;
    }

    async.waterfall([
        function (cb) {
            opts.attribute = 'manual';
            createSnapshot(distribution, baseSnapshotName, opts, cb);
        },
        function (newSnapshotData, oldSnapshotData, cb) {
            save(distribution, [], newSnapshotData, opts, cb);
        }
    ], function (err, newSnapshot) {
        callback(err, newSnapshot);
    });
}

function save(distribution, artifacts, newSnapshotData, opts, callback) {
    var newSnapshot;
    var conditions = {
        distName: distribution.name,
        name: newSnapshotData.name
    };

    async.waterfall([
        function (cb) {
            dibs.rpc.datamgr.searchSnapshots(conditions, function (err, results) {
                if (err) {
                    return cb(err);
                } else if (results && results.length > 0) {
                    return cb(new DError('VSREPO022', { snapshot: newSnapshotData.name}));
                } else {
                    return cb(null);
                }
            });
        },
        function (cb) {
            if (_.isEmpty(artifacts)) {
                return cb(null, null);
            } else {
                copyFilesToRepository(artifacts, opts, distribution.path, cb);
            }
        },
        function (artifactPath, cb) {
            newSnapshotData.options['ARTIFACTS'] = newSnapshotData.artifacts;
            dibs.rpc.datamgr.addSnapshot(newSnapshotData.name,
                distribution.name,
                newSnapshotData.type,
                newSnapshotData.time,
                'OPEN',
                newSnapshotData.attribute,
                newSnapshotData.path,
                newSnapshotData.desc,
                newSnapshotData.options,
                function (err, snapshot) {
                    if (err) {
                        return cb(new DError('VSREPO038', err));
                    } else {
                        newSnapshot = snapshot;
                        return cb(null);
                    }
                });
        },
        function (cb) {
            saveSnapshotFile(newSnapshot, distribution.path, cb);
        },
        function (snapshot, cb) {
            saveAtomFile(newSnapshotData, distribution.path, opts, cb);
        }
    ], function (err) {
        callback(err, newSnapshot);
    });
}


function copyFilesToRepository(artifacts, opts, distPath, callback) {
    var saved = [];
    var basePath;


    if (opts.isTemp) {
        var jobPath = 'job-' + opts.tempJobId.toString();
        basePath = path.join(distPath, 'temp', jobPath);
    } else {
        basePath = distPath;
    }

    async.eachSeries(artifacts, function (artifact, cb) {
        var idPath = path.join('artifacts', artifact.options.identity);
        var versionPath = path.join(idPath, artifact.version);

        async.series([
            function (cb1) {
                makeDirIfNotExist(path.join(basePath, idPath), function (err1, path) {
                    if (err1) {
                        cb1(err1);
                    } else {
                        saved.push(path);
                        cb1(null);
                    }
                });
            },
            function (cb1) {
                makeDirIfNotExist(path.join(basePath, versionPath), function (err1, path) {
                    if (err1) {
                        cb1(err1);
                    } else {
                        saved.push(path);
                        cb1(null);
                    }
                });
            },
            function (cb1) {
                async.eachSeries(artifact.options.files, function (fileInfo, cb2) {
                    var dfsPath = fileInfo.dfs;
                    var filePath = path.join(versionPath, path.basename(dfsPath));
                    var tpath = path.join(basePath, filePath);

                    delete fileInfo.dfs;
                    fileInfo.path = filePath;

                    dfs.getFile(tpath, dfsPath, function (err2) {
                        if (err2) {
                            cb2(err2);
                        } else {
                            saved.push(tpath);
                            cb2(null);
                        }
                    });
                }, cb1);
            }
        ], cb);
    }, function (err) {
        callback(err, saved);
    });
}

function deregisterPackages(names, distribution, opts, callback) {
    var latestSnapshotName;

    if (!distribution.latestSnapshot) {
        return callback(new DError('VSREPO033'), null);
    } else {
        latestSnapshotName = distribution.latestSnapshot.name;
    }

    async.waterfall([
        function (cb) {
            createSnapshot(distribution, latestSnapshotName, opts, cb);
        },
        function (newSnapshotData, oldSnapshotData, cb) {
            var removeList = [];
            newSnapshotData.removedPkgs = [];
            newSnapshotData.artifacts = _.reject(newSnapshotData.artifacts, function (artifact) {
                var contain = _.contains(names, artifact.name);
                if (contain) {
                    removeList.push(artifact.name);
                    newSnapshotData.removedPkgs.push(artifact);
                    return true;
                } else {
                    return false;
                }
            });

            if (!_.isEmpty(removeList)) {
                save(distribution, [], newSnapshotData, opts, cb);
            } else {
                return cb(new DError('VSREPO014', {artifacts: removeList.toString()}), null);
            }
        }
    ], callback);
}

function getUpdatedPackages(newSnapshot, oldSnapshot, options) {
    var newArtifacts;
    var oldArtifacts;

    // 'options' is optional argument
    if (!options) {
        options = {};
    }

    // if no new snapshot, return emtpy
    if (newSnapshot) {
        newArtifacts = newSnapshot.artifacts;
    } else {
        return [];
    }

    if (oldSnapshot) {
        oldArtifacts = oldSnapshot.artifacts;
    } else {
        return newArtifacts;
    }

    var updatedPkgs = [];
    if (newArtifacts) {
        _.each(newArtifacts, function (newArtifact) {
            var oldArtifact = _.findWhere(oldArtifacts, {
                name: newArtifact.name
            });
            if (!oldArtifact) {
                updatedPkgs.push(newArtifact);
                return;
            }

            var versionCompare = Package.compareVersion(newArtifact.version, oldArtifact.version);

            if (versionCompare > 0) {
                updatedPkgs.push(newArtifact);
            } else if (versionCompare === 0 && newArtifact.checksum !== oldArtifact.checksum && newArtifact.size !== oldArtifact.size) {
                updatedPkgs.push(newArtifact);
            } else {
                ;
            }
        });
    }
    return updatedPkgs;
}

function searchSnapshotList(conditions, callback) {
    if (!conditions.distName) {
        callback(new DError('TREPO003'), []);
        return;
    }

    conditions.noArtifacts = true;

    if (conditions.type) {
        conditions.attribute = conditions.type;
        delete conditions.type;
    }

    if (conditions.repoType) {
        conditions.type = conditions.repoType;
        delete conditions.repoType;
    }

    dibs.rpc.datamgr.searchSnapshots(conditions, function (err1, snapshots) {
        if (err1) {
            return callback(err1, []);
        } else {
            return callback(null, snapshots);
        }
    });
}


function searchSnapshots(conditions, callback) {
    if (!conditions.distName) {
        callback(new DError('TREPO003'), []);
        return;
    }

    if (conditions.type) {
        conditions.attribute = conditions.type;
        delete conditions.type;
    }

    if (conditions.repoType) {
        conditions.type = conditions.repoType;
        delete conditions.repoType;
    }

    dibs.rpc.datamgr.searchSnapshots(conditions, function (err1, results) {
        if (err1) {
            return callback(err1, []);
        } else {
            return callback(null, results);
        }
    });
}

function makeDirIfNotExist(tpath, callback) {
    fs.exists(tpath, function (exists) {
        if (exists) {
            callback(null, null);
        } else {
            extfs.mkdirs(tpath, function (err) {
                if (err) {
                    callback(err);
                } else {
                    callback(null, tpath);
                }
            });
        }
    });
}

function appendArtifactToSnapshot(oldSnapshotData, newSnapshotData, artifact, opts, callback) {
    var oldArtifact = null;

    if (oldSnapshotData) {
        _.each(oldSnapshotData.artifacts, function (a) {
            if (a.name === artifact.name) {
                if (a.options.identity !== artifact.options.identity) {
                    return callback(new DError('VSREPO030', {identity: a.options.identity}));
                } else {
                    oldArtifact = a;
                }
            }
        });
    }

    if (!oldArtifact) {
        // add new
        newSnapshotData.newPkgs.push(artifact);
    } else {
        // version check
        var versionCompare = Utils.compareVersion(artifact.version, oldArtifact.version);

        if (!opts.forceUpload && !opts.isTemp && versionCompare <= 0) {
            return callback(new DError('VSREPO013', {
                new: artifact.version,
                old: oldArtifact.version
            }));
        } else {
            newSnapshotData.artifacts = _.reject(newSnapshotData.artifacts, function (a) {
                if (a.name === artifact.name) {
                    return true;
                } else {
                    return false;
                }
            });
            newSnapshotData.updatedPkgs.push(artifact);
        }
    }

    // set
    artifact.options['__SNAPSHOT_NAME'] = newSnapshotData.name;
    newSnapshotData.artifacts.push(artifact);
    callback(null);
}

function loadSnapshotInfo(distPath, callback) {
    var snapshotInfoPath = getSnapshotInfoPath(distPath);
    fs.exists(snapshotInfoPath, function (exist) {
        if (exist) {
            try {
                fs.readFile(snapshotInfoPath, 'utf-8', function (err, data) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, yaml.safeLoad(data));
                    }
                });
            } catch (e) {
                console.log('load file ' + snapshotInfoPath + ' failed');
                console.log(e);
                throw e;
            }
        } else {
            return callback(null, []);
        }
    });

}

function getSnapshotInfoPath(distPath) {
    return path.join(distPath, 'snapshot.info');
}

function artifactToEntry(artifact) {
    var published = new Date().format('iso');
    var updated = published;
    var author = [];
    var id;
    var title = artifact.name;
    var summary = artifact.description;
    var version = artifact.version;
    var content = artifact.path;
    var link = [];
    var icon;
    var preview;

    if (artifact.options) {
        id = artifact.options.identity;

        if (artifact.options.published) {
            published = artifact.options.published;
            updated = artifact.options.published;
        }

        if (!artifact.options.publisher) {
            author.push('DIBS');
        } else if (Array.isArray(artifact.options.publisher)) {
            author = artifact.options.publisher;
        } else {
            author.push(artifact.options.publisher);
        }

        if (artifact.options.files) {
            _.each(artifact.options.files, function (file) {
                if (file.type === 'vsix') {
                    return;
                } else if (file.element === 'Icon') {
                    icon = file.path;
                } else if (file.element === 'PreviewImage') {
                    preview = file.path;
                } else {
                    link.push({
                        rel: file.type,
                        href: file.path
                    });
                }
            });
        }
    }

    return {
        id: id,
        title: title,
        summary: summary,
        published: published,
        updated: updated,
        author: author,
        content: content,
        version: version,
        link: link,
        icon: icon,
        previewImage: preview
    };
}
