/**
 * snapshot.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 _ = require('underscore');

var DError = require('../../core/exception.js');
var utils = require('../../lib/utils.js');
var Artifact = require('./artifact.js');

/**
 * @module models/snapshot
 */

/**
 * @constructor
 * @param {string} name - snapshot name
 * @param {string} distName - distribution name
 * @param {string} type - snapshot type
 * @param {string} time - snapshot creation time
 * @param {string} attribute - snapshot attribute
 * @param {string} path - snapshot path
 * @param {string} description - snapshot description
 * @param {array} options - snapshot info
 */

function Snapshot(name, distName, type, time, status, attribute, path, description, options) {
    var self = this;
    /** @type {number} */
    this.id = -1;
    /** @type {string} */
    this.name = name || '';
    /** @type {string} */
    this.distName = distName || '';
    /** @type {string} */
    this.type = type || '';
    /** @type {string} */
    this.time = time || '';
    /** @type {string} */
    this.status = 'OPEN';
    /** @type {string} */
    this.attribute = attribute || '';
    /** @type {string} */
    this.path = path || '';
    /** @type {string} */
    this.description = description || '';
    /** @type {array} */
    this.options = options || {};
}


/**
 * @function create
 * @param {string} id - snapshot id
 * @param {string} distName - distribution name
 * @param {string} type - snapshot type
 * @param {string} time - snapshot creation time
 * @param {string} status - snapshot status (OPEN : CLOSE)
 * @param {string} attribute - snapshot attribute
 * @param {string} path - snapshot path
 * @param {string} description - server description
 * @param {array} options - snapshot info
 * @memberOf module:models/snapshot
 */

module.exports.create = create;
function create(name, distName, type, time, status, attribute, path, description, options, callback) {
    callback(null, new Snapshot(name, distName, type, time, status, attribute, path, description, options));
}


module.exports.insert = function (conn, snapshot, callback) {
    var snapshotObj = _.clone(snapshot);

    var changedPackages = [];
    var unchangedPackages = [];

    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err1) {
                cb(err1);
            });
        },
        function (cb) {
            // check if given snapshot name is in a distribution.
            select(conn, { name: snapshotObj.name, distName: snapshotObj.distName }, function (err1, results) {
                if (err1) {
                    cb(err1);
                } else {
                    // console.log(err1);
                    // console.log(results);

                    if (results.length > 0) {
                        cb(new DError('SNAPSHOT002', { name: snapshotObj.name, distName: snapshotObj.distName}));
                    } else {
                        cb(null);
                    }
                }
            });
        },
        function (cb) {
            if (snapshot.artifacts) {
                unchangedPackages = _.filter(snapshot.artifacts, function (artifact) {
                    return (artifact.id && artifact.id !== -1);
                });

                changedPackages = _.filter(snapshot.artifacts, function (artifact) {
                    return (artifact.id === -1 || !artifact.id);
                });

                async.map(changedPackages, function (artifact, cb1) {
                    Artifact.insert(conn, artifact, false, function (err2, result) {
                        cb1(err2, result);
                    });
                },
                function (err1, insertedPackages) {
                    snapshotObj.artifacts = _.union(unchangedPackages, insertedPackages);
                    cb(err1);
                });
            } else {
                cb(null);
            }
        },
        function (cb) {
            // insert snapshot
            var sql = 'INSERT INTO snapshots SET ' +
                '  name = ' + utils.DBStr(snapshotObj.name) +
                ', distribution_name = ' + utils.DBStr(snapshotObj.distName) +
                ', type = ' + utils.DBStr(snapshotObj.type) +
                ', time = ' + utils.DBStr(snapshotObj.time) +
                ', status = ' + utils.DBStr(snapshotObj.status) +
                ', attribute = ' + utils.DBStr(snapshotObj.attribute) +
                ', path = ' + utils.DBStr(snapshotObj.path) +
                ', description = ' + utils.DBStr(snapshotObj.description);

            conn.query(sql, function (err, result) {
                if (err) {
                    cb(new DError('MODEL003', sql, err));
                } else {
                    snapshotObj.id = result.insertId;
                    cb(null);
                }
            });
        },
        function (cb) {
            var options = _.clone(snapshotObj.options);

            // insert snapshot_info
            async.eachLimit(_.keys(options), 5, function (idx, cb1) {
                if (_.isUndefined(snapshotObj.options[idx]) || _.isNull(snapshotObj.options[idx])) {
                    cb1(null);
                } else {
                    var strNtype = utils.objectToStringAndType(snapshotObj.options[idx]);
                    var sql = 'INSERT INTO snapshot_info SET ' +
                        '  snapshot_id = ' + utils.DBStr(snapshotObj.id) +
                        ', property = ' + utils.DBStr(idx) +
                        ', value = ' + utils.DBStr(strNtype.string) +
                        ', type = ' + utils.DBStr(strNtype.type);

                    conn.query(sql, function (err) {
                        if (err) {
                            cb1(new DError('MODEL003', sql, err));
                        } else {
                            cb1(null);
                        }
                    });
                }
            }, function (err) {
                cb(err);
            });
        },
        function (cb) {
            // insert snapshot_artifact
            async.eachLimit(snapshotObj.artifacts, 5, function (artifact, cb1) {
                var sql = 'INSERT INTO snapshot_artifact SET' +
                    ' snapshot_id = ' + utils.DBStr(snapshotObj.id) +
                    ', artifact_id = ' + utils.DBStr(artifact.id);

                conn.query(sql, function (err) {
                    if (err) {
                        cb1(new DError('MODEL003', sql, err));
                    } else {
                        cb1(null);
                    }
                });
            }, function (err) {
                cb(err);
            });
        },
        function (cb) {
            conn.query('COMMIT', function (err) {
                cb(err);
            });
        }
    ],
    function (err) {
        if (err) {
            conn.query('ROLLBACK', function () {});
            callback(err, {});
        } else {
            callback(err, snapshotObj);
        }
    });
};

module.exports.select = select;
function select(conn, condition, callback) {
    var where = '';
    var whereList = [];
    var keyList = ['id', 'name', 'distribution_name', 'type', 'time', 'status', 'attribute', 'path', 'description'];
    var limit = '';
    var order = ' ORDER BY id ASC';

    var cond = _.clone(condition);
    cond.distribution_name = condition.distName;
    delete cond['distName'];

    async.waterfall([
        function (cb) {
            _.each(keyList, function (key) {
                if (cond[key] !== undefined) {
                    var query = null;
                    if (key === 'id') {
                        if (_.isArray(cond[key])) {
                            query = key + ' in (' + _.map(cond[key], function (val) { return utils.DBStr(val); }).join(',') + ') ';
                        } else {
                            query = key + ' = ' + utils.DBStr(cond[key]);
                        }
                    } else {
                        query = key + ' = ' + utils.DBStr(cond[key]);
                    }
                    whereList.push('snapshots.' + query);
                }
            });

            if (whereList.length > 0) {
                where = ' WHERE ' + whereList.join(' AND ');
            }

            // limit info
            if (cond.count !== undefined) {
                if (cond.offset !== undefined) {
                    limit += (' LIMIT ' + cond.offset + ' , ' + cond.count);
                } else {
                    limit += (' LIMIT ' + cond.count);
                }
            }

            if (cond.order !== undefined) {
                order = ' ORDER BY id ' + cond.order;
            }

            // select snapshots
            var sql = 'SELECT * FROM snapshots ' + where + order + limit;
            conn.query(sql, function (err, snapshots) {
                if (err) {
                    cb(new DError('MODEL003', { sql: sql }, err));
                } else {
                    cb(null, snapshots);
                }
            });
        },
        function (snapshots, cb) {
            // select snapshot_info
            async.map(snapshots, function (snapshot, cb1) {
                var info = 'SELECT * FROM snapshot_info WHERE snapshot_id = ' + utils.DBStr(snapshot.id);
                conn.query(info, function (err1, snapshotInfo) {
                    if (err1) {
                        cb1(new DError('MODEL003', { sql: info }, err1));
                    } else {
                        var options = {};

                        _.each(snapshotInfo, function (data) {
                            options[data.property] = utils.stringAndTypeToObj(data.value, data.type);
                        });

                        create(snapshot.name, snapshot.distribution_name, snapshot.type, snapshot.time, snapshot.status,
                            snapshot.attribute, snapshot.path, snapshot.description, options,
                            function (err2, result) {
                                result.id = snapshot.id;
                                cb1(err2, result);
                            });
                    }
                });
            },
            function (err3, results) {
                if (err3) {
                    cb(new DError('SNAPSHOT001', { condition: cond }, err3));
                } else {
                    cb(null, results);
                }
            });
        },
        function (snapshots, cb) {
            if (!condition.noArtifacts) {
                // select artifacts
                async.map(snapshots, function (snapshot, cb1) {
                    var artifactIds = [];

                    var sql = 'SELECT artifact_id AS id FROM snapshot_artifact WHERE snapshot_id = ' + utils.DBStr(snapshot.id);
                    conn.query(sql, function (err, results) {
                        if (err) {
                            cb1(new DError('MODEL003', { sql: sql }, err));
                        } else if (results.length === 0) {
                            snapshot.artifacts = [];
                            cb1(null, snapshot);
                        } else {
                            _.each(results, function (res) {
                                artifactIds.push(res.id);
                            });

                            Artifact.select(conn, { id: artifactIds }, function (err1, artifacts) {
                                snapshot.artifacts = artifacts;
                                cb1(err1, snapshot);
                            });
                        }
                    });
                },
                function (err1, snapshots) {
                    cb(err1, snapshots);
                });
            } else {
                cb(null, snapshots);
            }
        }
    ],
    function (err, results) {
        callback(err, results);
    });
}


module.exports.update = function (conn, snapshot, callback) {
    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err) {
                if (err) {
                    cb(new DError('MODEL001', err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            var sql = 'UPDATE snapshots SET' +
                ' name = ' + utils.DBStr(snapshot.name) +
                ', distribution_name = ' + utils.DBStr(snapshot.distName) +
                ', type = ' + utils.DBStr(snapshot.type) +
                ', time = ' + utils.DBStr(snapshot.time) +
                ', status = ' + utils.DBStr(snapshot.status) +
                ', attribute = ' + utils.DBStr(snapshot.attribute) +
                ', path = ' + utils.DBStr(snapshot.path) +
                ', description = ' + utils.DBStr(snapshot.description) +
                ' WHERE id = ' + utils.DBStr(snapshot.id);

            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err), null);
                } else {
                    cb(err);
                }
            });
        },
        function (cb) {
            conn.query('COMMIT', function (err) {
                if (err) {
                    cb(new DError('MODEL002', err));
                } else {
                    cb(err);
                }
            });
        }
    ],
    function (err) {
        if (err) {
            conn.query('ROLLBACK', function () {});
        }
        callback(err);
    });
};


module.exports.updateStatus = function (conn, snapshot, callback) {
    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err) {
                if (err) {
                    cb(new DError('MODEL001', err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            var sql = 'UPDATE snapshots SET status = ' + utils.DBStr(snapshot.status) +
                ' WHERE id = ' + utils.DBStr(snapshot.id);

            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err), null);
                } else {
                    cb(err);
                }
            });
        },
        function (cb) {
            conn.query('COMMIT', function (err) {
                if (err) {
                    cb(new DError('MODEL002', err));
                } else {
                    cb(err);
                }
            });
        }
    ],
    function (err) {
        if (err) {
            conn.query('ROLLBACK', function () {});
        }
        callback(err);
    });
};


function deleteUnattachedArtifacts(conn, artifactIds, callback) {
    var unattachedArtifacts = [];
    var deletedArtifacts = [];

    async.series([
        function (cb) {
            async.eachLimit(artifactIds, 5, function (id, cb1) {
                var sql = 'SELECT count(*) AS count FROM snapshot_artifact WHERE artifact_id = ' + utils.DBStr(id);
                conn.query(sql, function (err2, results) {
                    if (err2) {
                        cb1(new DError('MODEL003', { sql: sql }, err2), null);
                    } else {
                        if (results[0].count === 0) {
                            unattachedArtifacts.push(id);
                        }
                        cb1(null);
                    }
                });
            },
            function (err1) {
                cb(err1);
            });
        },
        function (cb) {
            async.eachLimit(unattachedArtifacts, 5, function (id, cb1) {
                Artifact.select(conn, {id: id}, function (err1, results) {
                    if (err1) {
                        return cb1(err1);
                    }
                    deletedArtifacts.push(results[0]);
                    Artifact.delete(conn, id, cb1);
                });
            }, cb);
        }
    ],
    function (err) {
        callback(err, deletedArtifacts);
    });
}

module.exports.delete = function (conn, id, callback) {
    var artifactIds = [];

    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err) {
                if (err) {
                    cb(new DError('MODEL001', err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            var sql = 'SELECT * FROM snapshot_artifact WHERE snapshot_id = ' + utils.DBStr(id);
            conn.query(sql, function (err, results) {
                if (err) {
                    cb(new DError('MODEL003', { sql: sql }, err), null);
                } else {
                    _.each(results, function (res) {
                        artifactIds.push(res.artifact_id);
                    });
                    cb(null);
                }
            });
        },
        function (cb) {
            var sql = 'DELETE FROM snapshot_artifact WHERE snapshot_id = ' + utils.DBStr(id);
            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', { sql: sql }, err), null);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // delete snapshot_info
            var sql = 'DELETE FROM snapshot_info WHERE snapshot_id = ' + utils.DBStr(id);
            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', { sql: sql }, err), null);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // delete snapshot
            var sql = 'DELETE FROM snapshots WHERE id = ' + utils.DBStr(id);
            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', { sql: sql }, err), null);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // delete artifacts that any snapshots does not refer to.
            deleteUnattachedArtifacts(conn, artifactIds, cb);
        },
        function (deletedArtifacts, cb) {
            conn.query('COMMIT', function (err) {
                if (err) {
                    cb(new DError('MODEL002', err));
                } else {
                    cb(err, deletedArtifacts);
                }
            });
        }
    ],
    function (err, deletedArtifacts) {
        if (err) {
            conn.query('ROLLBACK', function () {});
        }
        callback(err, deletedArtifacts);
    });
};
