/**
 * environment.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 DError = require('../../core/exception');
var utils = require('../../lib/utils.js');
var async = require('async');

/**
 * @module models/environment
 */
/**
 * @constructor
 * @param {string} name
 * @param {string} description
 * @param {module:models/environment.options} options
 * @memberOf module:models/environment
 */
function Environment(id, name, description, options) {
    this.id = id;
    this.name = name;
    this.description = description;
    this.options = options;
}

/**
 * @typedef {object} options
 * @property {string} key - key : value format Hash object for environment option
 * @memberOf module:models/environment
 */

/**
 * @function create
 * @param {string} name
 * @param {string} description
 * @param {module:models/environment.options} name
 * @returns {module:models/environment.Environment}
 * @memberOf module:models/environment
 */

module.exports.create = function (id, name, description, options) {
    return new Environment(id, name, description, options);
};

/**
 * @callback callback_err_envid
 * @param {error|undefined} error
 * @param {string} environment id
 * @memberOf module:models/environment
 */

/**
 * @function insert
 * @param {object} conn - db connection object
 * @param {module:models/environment.Environment} environment
 * @param {module:models/environment.callback_err_envid} callback
 * @memberOf module:models/environment
 */

module.exports.insert = function (conn, env, callback) {
    conn.query('START TRANSACTION', function (err) {
        if (err) {
            callback(new DError('MODEL001', err), null);
            return;
        }

        // excute transaction sql
        async.waterfall(
            [
                // TABLE: environments
                function (cb1) {
                    var sql = 'INSERT INTO environments ' +
                        'VALUES (' + utils.DBStr(env.id) + ', ' + utils.DBStr(env.name) + ', ' + utils.DBStr(env.description) + ')';
                    conn.query(sql, function (err, result) {
                        if (err) {
                            callback(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb1(null, env.id);
                    });
                },
                // TABLE: environment_info
                function (id, cb1) {
                    var key;
                    var sqls = [];

                    for (key in env.options) {
                        var sql = 'INSERT INTO environment_info ( environment_id ' +
                            ', property ' +
                            ', value) ' +
                            'VALUES (' + utils.DBStr(id) +
                            ', ' + utils.DBStr(key) +
                            ', ' + utils.DBStr(env.options[key]) + ')';
                        sqls.push(sql);
                    }

                    async.map(sqls, function (sql, cb2) {
                        conn.query(sql, function (err, result) {
                            if (err) {
                                cb2(new DError('MODEL003', {
                                    sql: sql
                                }, err), null);
                                return;
                            }
                            cb2(null, result);
                        });
                    },
                        function (err, results) {
                            if (err) {
                                callback(err, null);
                                return;
                            }
                            cb1(null, id);
                        });
                },
                // commit sql
                function (id, cb1) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            callback(new DError('MODEL002', err), null);
                            return;
                        }
                        cb1(err, id);
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('ENV001', {
                        name: env.name
                    }, err), null);
                    return;
                }
                callback(err, result);
            });
    });
};

/**
 * @callback callback_err_optionlength
 * @param {error|undefined} error
 * @param {string} optionlength
 * @memberOf module:models/environment
 */

/**
 * @function update
 * @param {object} conn - db connection object
 * @param {module:models/environment.Environment} environment
 * @param {module:models/environment.callback_err_optionlength} callback
 * @memberOf module:models/environment
 */

module.exports.update = function (conn, env, callback) {
    var i;
    conn.query('START TRANSACTION', function (err) {
        if (err) {
            callback(new DError('MODEL001', err), null);
            return;
        }

        // Check condition of key
        if (env.id === undefined) {
            callback(new DError('ENV004', err), null);
            return;
        }

        // excute transaction sql
        async.waterfall(
            [
                // Find
                function (cb) {
                    selectKey(conn, {
                        id: env.id
                    }, function (err, envs) {
                        if (err) {
                            cb(err, null);
                            return;
                        } else if (envs.length === 0) {
                            cb(new DError('ENV005', {
                                id: env.id
                            }, err), null);
                            return;
                        }
                        cb(null, env); // result is only one.
                    });
                },
                // TABLE: environments
                function (env, cb) {
                    var update_list = [];
                    var update_key_list = ['name', 'description'];
                    for (i = 0; i < update_key_list.length; i++) {
                        var key = update_key_list[i];
                        if (env[key] !== undefined) {
                            update_list.push(key + ' = ' + utils.DBStr(env[key]));
                        }
                    }

                    // None update list
                    if (update_list.length === 0) {
                        cb(null, env);
                        return;
                    }

                    var sql = 'UPDATE environments SET ' + update_list.join(', ') + ' WHERE id = ' + utils.DBStr(env.id);
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, env);
                    });
                },
                // TABLE: environment_info
                function (env, cb) {
                    var sql;
                    var opt_list = [];

                    if (env.options !== undefined) {
                        var opts = env.options;
                        for (var key in opts) {
                            if (key !== undefined) {
                                opt_list.push({
                                    property: key,
                                    value: opts[key]
                                });
                            }
                        }
                    }

                    // None update list
                    if (opt_list.length === 0) {
                        cb(null, env);
                        return;
                    }

                    async.map(opt_list, function (opt, cb1) {
                        sql = 'SELECT * FROM environment_info WHERE environment_id = ' + utils.DBStr(env.id) + ' AND property = ' + utils.DBStr(opt.property);
                        conn.query(sql, function (err, result) {
                            if (err) {
                                cb1(new DError('MODEL003', {
                                    sql: sql
                                }, err), null);
                                return;
                            } else if (result.length === 0) {
                                sql = 'INSERT INTO environment_info ( environment_id' +
                                    ', property' +
                                    ', value) ' +
                                    'VALUES (' + utils.DBStr(env.id) +
                                    ', ' + utils.DBStr(opt.property) +
                                    ', ' + utils.DBStr(opt.value) + ')';
                            } else {
                                sql = 'UPDATE environment_info ' +
                                    'SET value = ' + utils.DBStr(opt.value) +
                                    ' WHERE environment_id = ' + utils.DBStr(env.id) +
                                    ' AND property = ' + utils.DBStr(opt.property);
                            }
                            conn.query(sql, function (err, result) {
                                if (err) {
                                    cb1(new DError('MODEL003', {
                                        sql: sql
                                    }, err), null);
                                    return;
                                }
                                cb1(null, opt);
                            });
                        });
                    },
                        function (err, results) {
                            if (err) {
                                cb(new DError('MODEL002', err), null);
                                return;
                            }
                            cb(null, results.length);
                        });
                },
                // commit sql
                function (result, cb) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            cb(new DError('MODEL002', err), null);
                            return;
                        }
                        cb(err, result);
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('ENV006', err), null);
                    return;
                }
                callback(err, result);
            });
    });
};

/**
 * @typedef {object} environmentQuerySql
 * @property {string} id
 * @property {string} name
 * @property {string} description
 * @property {string} count
 * @property {string} offset
 * @memberOf module:models/environment
 */

/**
 * @function delete
 * @param {object} conn - db connection object
 * @param {module:models/environment.environmentQuerySql} condition
 * @param {module:models/environment.callback_err_environment} callback
 * @memberOf module:models/environment
 */

module.exports.delete = function (conn, condition, callback) {
    conn.query('START TRANSACTION', function (err) {
        if (err) {
            callback(new DError('MODEL001', err), null);
            return;
        }

        // Check condition of key
        if (condition.id === undefined && condition.name === undefined) {
            callback(new DError('ENV003', {
                condition: condition
            }, err), null);
            return;
        }

        // excute transaction sql
        async.waterfall(
            [
                // Find
                function (cb) {
                    selectKey(conn, condition, function (err, envs) {
                        if (err) {
                            cb(err, null);
                            return;
                        }
                        if (envs[0] === undefined) {
                            cb(new DError('ENV007', {
                                condition: condition
                            }, err), null);
                            return;
                        }

                        cb(null, envs[0]); // result is only one.
                    });
                },
                // TABLE: environment_info
                function (env, cb) {
                    var sql = 'DELETE FROM environment_info WHERE environment_id = ' + utils.DBStr(env.id);
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, env);
                    });
                },
                // TABLE: environments
                function (env, cb) {
                    var sql = 'DELETE FROM environments WHERE id = ' + utils.DBStr(env.id);
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, env);
                    });
                },
                // commit sql
                function (env, cb) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            cb(new DError('MODEL002', err), null);
                            return;
                        }
                        cb(err, env);
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('ENV003', {
                        condition: condition
                    }, err), null);
                    return;
                }
                callback(err, result);
            });
    });
};

/**
 * @callback callback_err_environment
 * @param {error|undefined} error
 * @param {module:models/environment.Environment} environment
 * @memberOf module:models/environment
 */

/**
 * @function select
 * @param {object} conn - db connection object
 * @param {module:models/environment.environmentQuerySql} condition
 * @param {module:models/environment.callback_err_environment} callback
 * @memberOf module:models/environment
 */

module.exports.select = function (conn, condition, callback) {
    if (condition.options === undefined) {
        selectKey(conn, condition, callback);
    } else {
        selectOptions(conn, condition, callback);
    }
};


function selectKey(conn, condition, callback) {
    var i;
    var key;
    var where = '';
    var where_list = [];

    var key_list = ['id', 'name', 'description'];
    for (i = 0; i < key_list.length; i++) {
        key = key_list[i];
        if (condition[key] !== undefined) {
            where_list.push(key + ' = ' + utils.DBStr(condition[key]));
        }
    }

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

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

    var sql = 'SELECT * FROM environments' + where + limit;
    conn.query(sql, function (err, envs) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }

        //Query info table
        async.map(envs, function (env, cb) {
            var sql = 'SELECT * FROM environment_info WHERE environment_id = ' + utils.DBStr(env.id);
            conn.query(sql, function (err, env_info) {
                if (err) {
                    callback(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }

                var opt = {};
                for (i = 0; i < env_info.length; i++) {
                    opt[env_info[i].property] = env_info[i].value;
                }
                env.options = opt;
                cb(null, env);
            });
        },
            function (err, results) {
                if (err) {
                    callback(new DError('ENV002', {
                        condition: condition
                    }, err));
                    return;
                }
                callback(null, results);
            });
    });
}

function selectOptions(conn, condition, callback) {
    var i;
    var key;
    var where = '';
    var where_list = [];
    var join_on_list = [];

    // main table condition
    var key_list = ['id', 'name', 'description'];
    for (i = 0; i < key_list.length; i++) {
        key = key_list[i];
        if (condition[key] !== undefined) {
            where_list.push('environments.' + key + ' = ' + utils.DBStr(condition[key]));
        }
    }

    // info table condition
    if (condition.options !== undefined) {
        var opts = condition.options;
        for (key in opts) {
            where_list.push('environment_info.property' + ' = ' + utils.DBStr(key));
            where_list.push('environment_info.value' + ' = ' + utils.DBStr(opts[key]));
        }
    }

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

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

    var sql = 'SELECT environments.* FROM environments' +
        ' LEFT JOIN environment_info' +
        ' ON environments.id = environment_info.environment_id ' +
        where + ' GROUP BY environments.id' + limit;
    conn.query(sql, function (err, envs) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err), null);
            return;
        }

        async.map(envs, function (env, cb) {
            var condition = {
                id: env.id
            };
            selectKey(conn, condition, function (err, envs) {
                cb(null, envs[0]); //result is only one (select by id)
            });
        },
            function (err, results) {
                if (err) {
                    callback(new DError('ENV002', {
                        condition: condition
                    }, err));
                    return;
                }
                callback(err, results);
            });
    });
}
