/**
 * server.js
 * Copyright (c) 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 async = require('async');
var _ = require('underscore');

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


module.exports.create = create;
module.exports.insert = insert;
module.exports.select = select;
module.exports.selectAll = selectAll;
module.exports.update = update;
module.exports.delete = deleteServer;


/**
 * @module models/server
 */

/**
 * @constructor
 * @param {number} id - server id
 * @param {string} type - server type
 * @param {string} osType - os type
 * @param {string} parentId - parent server id
 * @param {string} host - access ip
 * @param {string} port - access port
 * @param {string} domain - server domain
 * @param {string} description - server description
 * @param {array} information - server informations
 */

function Server(id, type, parentId, osType, host, port, domainName, description, information) {
    var self = this;
    /** @type {number} */
    this.id = id;
    /** @type {string} */
    this.type = type;
    /** @type {string} */
    this.os_type = osType || '';
    /** @type {string} */
    this.parent_id = parentId || '';
    /** @type {string} */
    this.host = host;
    /** @type {string} */
    this.port = port;
    /** @type {string} */
    this.domain_name = domainName || '';
    /** @type {string} */
    this.description = description || '';
    /** @type {array} */
    this.information = information || {};
}

/**
 * @function create
 * @param {number} id - server id
 * @param {string} type - server type
 * @param {string} parentId - parent server id
 * @param {string} host - access ip
 * @param {string} port - access port
 * @param {string} domain - server domain
 * @param {string} description - server description
 * @param {array} information - server informations
 * @memberOf module:models/serveer
 */

function create(id, type, parentId, osType, host, port, domainName, description, information, callback) {
    var err;
    if (!id) {
        err = new DError('SVR001', {
            field: 'id'
        });
    }
    if (!type) {
        err = new DError('SVR001', {
            field: 'server_type'
        });
    }
    if (!host) {
        err = new DError('SVR001', {
            field: 'host'
        });
    }
    callback(err, new Server(id, type, parentId, osType, host, port, domainName, description, information));
}

/**
 * @function insert
 * @param {object} conn - db connection object
 * @param {module:models/server} server - server object
 * @param {module:models/server.callback_err_serverid} callback - callback
 * @memberOf module:models/server
 */

function insert(conn, server, callback) {
    var serverObj = _.clone(server);

    async.waterfall([
        function (cb) {
            conn.query('START TRANSACTION', function (err) {
                if (err) {
                    cb(new DError('MODEL001', err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // insert server
            var sql = 'INSERT INTO servers SET ' +
                '  id = ' + utils.DBStr(serverObj.id) +
                ', type = ' + utils.DBStr(serverObj.type) +
                ', parent_id = ' + utils.DBStr(serverObj.parent_id) +
                ', os_type = ' + utils.DBStr(serverObj.os_type) +
                ', host = ' + utils.DBStr(serverObj.host) +
                ', port = ' + utils.DBStr(serverObj.port) +
                ', domain_name = ' + utils.DBStr(serverObj.domain_name) +
                ', description = ' + utils.DBStr(serverObj.description);
            conn.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', sql, err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // insert server_information
            async.each(_.keys(serverObj.information), function (idx, cb1) {
                var strNtype = utils.objectToStringAndType(serverObj.information[idx]);
                serverObj.information.server_id = serverObj.id;
                var sql = 'INSERT INTO server_info SET ' +
                    ' server_id = ' + utils.DBStr(serverObj.information.server_id) +
                    ', property = ' + utils.DBStr(idx) +
                    ', value = ' + utils.DBStr(strNtype.string) +
                    ', type = ' + utils.DBStr(strNtype.type);
                conn.query(sql, function (err, result) {
                    if (err) {
                        cb1(new DError('MODEL003', sql, err));
                    } else {
                        serverObj.information.id = result.insertId;
                        cb1(null);
                    }
                });
            }, cb);
        },
        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, null);
        } else {
            callback(err, serverObj);
        }
    });
}

/**
 * @function update
 * @param {object} conn - db connection object
 * @param {module:models/server~server_info} server - server object
 * @param {module:models/server.callback_err_serverid} callback - callback
 * @memberOf module:models/server
 */
function update(connection, server, callback) {
    async.waterfall([
        function (cb) {
            connection.query('START TRANSACTION', function (err) {
                if (err) {
                    cb(new DError('MODEL001', err));
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            // update servers table
            var sql = 'UPDATE servers SET' +
                ' type = ' + utils.DBStr(server.type) +
                ', os_type = ' + utils.DBStr(server.os_type) +
                ', parent_id = ' + utils.DBStr(server.parent_id) +
                ', host = ' + utils.DBStr(server.host) +
                ', port = ' + utils.DBStr(server.port) +
                ', domain_name = ' + utils.DBStr(server.domain_name) +
                ', description = ' + utils.DBStr(server.description) +
                ' WHERE id = ' + utils.DBStr(server.id);
            connection.query(sql, function (err) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err), null);
                } else {
                    cb(err);
                }
            });
        },
        function (cb) {
            // update approval_route table
            updateServerInformation(connection, server.id, server.information, cb);
        },
        function (cb) {
            connection.query('COMMIT', function (err) {
                if (err) {
                    cb(new DError('MODEL002', err));
                } else {
                    cb(err);
                }
            });
        }
    ], function (err) {
        if (err) {
            connection.query('ROLLBACK', function () {});
        }
        callback(err);
    });
}

function updateServerInformation(connection, serverId, serverInformation, callback) {
    var oldPropertyList;
    var deleteList;

    // update server_info table
    async.waterfall([
        function (cb) {
            // get exists info
            var sql = 'SELECT * FROM server_info WHERE server_id = ' + utils.DBStr(serverId);
            connection.query(sql, function (err, results) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err), null);
                } else {
                    oldPropertyList = results;
                    cb(err);
                }
            });
        },
        function (cb) {
            async.each(_.keys(serverInformation), function (idx, cb1) {
                var sql = 'SELECT * FROM server_info' +
                    ' WHERE server_id = ' + utils.DBStr(serverId) +
                    ' AND property = ' + utils.DBStr(idx);
                connection.query(sql, function (err, results) {
                    if (err) {
                        cb1(new DError('MODEL003', {
                            sql: sql
                        }, err));
                    } else {
                        var sql1 = null;
                        var strNtype = utils.objectToStringAndType(serverInformation[idx]);
                        if (results.length === 1) {
                            // update server information
                            sql1 = 'UPDATE server_info SET ' +
                                ' value = ' + utils.DBStr(strNtype.string) +
                                ', type = ' + utils.DBStr(strNtype.type) +
                                ' WHERE server_id = ' + utils.DBStr(serverId) +
                                ' AND property = ' + utils.DBStr(idx);
                        } else {
                            // insert server information
                            sql1 = 'INSERT INTO server_info SET ' +
                                ' property = ' + utils.DBStr(idx) +
                                ', value = ' + utils.DBStr(strNtype.string) +
                                ', type = ' + utils.DBStr(strNtype.type) +
                                ', server_id = ' + utils.DBStr(serverId);
                        }
                        connection.query(sql1, function (err1) {
                            if (err1) {
                                cb1(new DError('MODEL003', {
                                    sql: sql1
                                }, err1));
                            } else {
                                cb1(null);
                            }
                        });
                    }
                });
            }, cb);
        },
        function (cb) {
            // get delete list
            deleteList = oldPropertyList.filter(function (old) {
                return (!serverInformation ||
                    Object.keys(serverInformation).indexOf(old.property) === -1);
            });
            async.eachSeries(deleteList, function (entry, cb1) {
                var sql = 'DELETE FROM server_info WHERE id = ' + utils.DBStr(entry.id);
                connection.query(sql, function (err1) {
                    if (err1) {
                        cb1(new DError('MODEL003', {
                            sql: sql
                        }, err1));
                    } else {
                        cb1(null);
                    }
                });
            },
                function (err) {
                    cb(err);
                });
        }
    ], callback);
}


/**
 * @function select
 * @param {object} connection - db connection object
 * @param {module:models/server.jobQuerySql} condition - condition
 * @param {module:lib/utils.callback_error} callback - callback
 * @memberOf module:models/server
 */

function select(connection, condition, callback) {
    var where = '';
    var whereList = [];
    var keyList = ['id', 'type', 'parent_id', 'os_type', 'host', 'port', 'domain_name'];
    var limit = '';
    async.waterfall([
        function (cb) {
            async.each(keyList, function (key, cb1) {
                if (condition[key] !== undefined) {
                    whereList.push('servers.' + key + ' = ' + utils.DBStr(condition[key]));
                }
                cb1(null);
            }, cb);
        },
        function (cb) {
            // limit info
            if (condition.count !== undefined) {
                if (condition.offset !== undefined) {
                    limit += (' LIMIT ' + condition.offset + ' , ' + condition.count);
                } else {
                    limit += (' LIMIT ' + condition.count);
                }
            }

            if (whereList.length > 0) {
                where = ' WHERE ' + whereList.join(' AND ');
            }
            cb(null);
        },
        function (cb) {
            var sql = 'SELECT * FROM servers ' + where + limit;
            connection.query(sql, function (err, servers) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }
                async.map(servers, function (server, cb1) {
                    var sql1 = 'SELECT * FROM server_info WHERE server_id = ' + utils.DBStr(server.id);
                    connection.query(sql1, function (err1, serverInfo) {
                        if (err1) {
                            cb1(new DError('MODEL003', {
                                sql: sql1
                            }, err1));
                            return;
                        }

                        var information = {};
                        _.each(serverInfo, function (value) {
                            information[value.property] = utils.stringAndTypeToObj(value.value, value.type);
                        });

                        create(server.id, server.type, server.parent_id, server.os_type,
                            server.host, server.port, server.domain_name,
                            server.description, information, cb1);
                    });
                }, function (err3, results) {
                    if (err3) {
                        cb(new DError('SVR002', {
                            condition: condition
                        }, err3));
                        return;
                    }
                    cb(null, results);
                });
            });
        }
    ], callback);
}

/**
 * @function selectAll
 * @param {object} connection - db connection object
 * @param {module:lib/utils.callback_error} callback - callback
 * @memberOf module:models/server
 */

function selectAll(connection, callback) {
    async.waterfall([
        function (cb) {
            var sql = 'SELECT * FROM servers ';
            connection.query(sql, function (err, servers) {
                if (err) {
                    cb(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }
                async.map(servers, function (server, cb1) {
                    var sql1 = 'SELECT * FROM server_info WHERE server_id = ' + utils.DBStr(server.id);
                    connection.query(sql1, function (err1, serverInfo) {
                        if (err1) {
                            cb1(new DError('MODEL003', {
                                sql: sql1
                            }, err1));
                            return;
                        }

                        var information = {};
                        _.each(serverInfo, function (value) {
                            information[value.property] = utils.stringAndTypeToObj(value.value, value.type);
                        });

                        create(server.id, server.type, server.parent_id, server.os_type,
                            server.host, server.port, server.domain_name,
                            server.description, information, cb1);
                    });
                }, function (err3, results) {
                    if (err3) {
                        cb(new DError('SVR002', {
                            condition: {
                                id: 'all'
                            }
                        }, err3));
                        return;
                    }
                    cb(null, results);
                });
            });
        }
    ], callback);
}

function deleteServer(connection, serverId, callback) {
    connection.query('START TRANSACTION', function (err) {
        if (err) {
            callback(new DError('MODEL001', err));
            return;
        }
        async.waterfall([
            function (cb) {
                // delete server_info
                var sql = 'DELETE FROM server_info WHERE server_id = ' + utils.DBStr(serverId);
                connection.query(sql, function (err) {
                    if (err) {
                        cb(new DError('MODEL003', {
                            sql: sql
                        }, err), null);
                    } else {
                        cb(null);
                    }
                });
            },
            function (cb) {
                // delete approval
                var sql1 = 'DELETE FROM servers WHERE id = ' + utils.DBStr(serverId);
                connection.query(sql1, function (err) {
                    if (err) {
                        cb(new DError('MODEL003', {
                            sql: sql1
                        }, err), null);
                    } else {
                        cb(null);
                    }
                });
            },
            function (cb) {
                connection.query('COMMIT', function (err) {
                    if (err) {
                        cb(new DError('MODEL002', err));
                    } else {
                        cb(err);
                    }
                });
            }
        ], function (err) {
            if (err) {
                connection.query('ROLLBACK', function () {
                    callback(err);
                });
            } else {
                callback(err);
            }
        });
    });
}
