/**
 * group.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 async = require('async');
var utils = require('../../lib/utils.js');
var _ = require('underscore');


module.exports.create = create;
module.exports.insert = insert;
module.exports.select = select;
module.exports.selectByProjectPrivilege = selectByProjectPrivilege;
module.exports.update = update;
module.exports.delete = deleteUserGroup;


/**
 * @module models/group
 */

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

/**
 * @callback callback_err_user
 * @param {error|undefined} error
 * @param {module:models/user.User}
 * @memberOf module:models/user
 */

/**
 * @constructor
 * @memberOf module:models/group
 */

function Group(name, type, description, options, parentName) {
    /** type {string} */
    this.id = -1;
    /** type {string} */
    this.name = name;
    /** type {string} */
    this.type = type;
    /** type {string} */
    this.description = description;
    /** type {string} */
    this.parentName = parentName;
    /** type {string} */
    this.status = 'CLOSE';
    /** type {module:models/user.options} */
    this.options = options;
    /** type {module:models/user.options} */
    this.projects = [];
}

/**
 * @function create
 * @param {string} name - name
 * @param {string} type - type
 * @param {string} description -  description
 * @param {string} parentName - parentName
 * @param {string} options - options
 * @returns {module:models/user.User}
 * @memberOf module:models/user
 */

function create(name, type, description, options, parentName) {
    return new Group(name, type, description, options, parentName);
}

/**
 * @function insert
 * @param {object} conn - db connection object
 * @param {module:models/user.User}  - user
 * @param {module:lib/utils.callback_error} callback - callback
 * @memberOf module:models/user
 */

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

        // excute transaction sql
        async.waterfall(
            [
                // TABLE: groups
                function (cb1) {
                    var sql = 'INSERT INTO groups ( name' +
                        ', type' +
                        ', description' +
                        ', status) ' +
                        'VALUES (' + utils.DBStr(group.name) +
                        ', ' + utils.DBStr(group.type) +
                        ', ' + utils.DBStr(group.description) +
                        ', ' + utils.DBStr(group.status) + ')';

                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb1(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                        } else {
                            cb1(null, result.insertId);
                        }
                    });
                },
                // TABLE: group_info
                function (id, cb1) {
                    var key;
                    var sqls = [];
                    for (key in group.options) {
                        var sql = 'INSERT INTO group_info ( group_id' +
                            ', property' +
                            ', value)' +
                            'VALUES (' + id +
                            ', ' + utils.DBStr(key) +
                            ', ' + utils.DBStr(group.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));
                            } else {
                                cb2(null, result);
                            }
                        });
                    },
                        function (err, results) {
                            if (err) {
                                cb1(new DError('MODEL002', err), null);
                                return;
                            } else {
                                cb1(null, id);
                            }
                        });
                },
                // commit sql
                function (id, cb1) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            cb1(new DError('MODEL002', err), null);
                        } else {
                            cb1(err, id);
                        }
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('GROUP001', {
                        name: group.name
                    }, err));
                    return;
                }
                callback(err, result);
            });
    });
}

/**
 * @typedef {object} jobQuerySql
 * @property {string} id
 * @property {string} name
 * @property {string} email
 * @property {string} password_hash
 * @property {string} password_salt
 * @property {string} status
 * @property {string} count
 * @property {string} offset
 * @memberOf module:models/user
 */

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

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

    var key_list = ['id', 'name', 'type', 'status'];
    for (i = 0; i < key_list.length; i++) {
        var 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 groups' + where + limit;
    conn.query(sql, function (err, groups) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }
        async.map(groups, function (group, cb) {
            var sql = 'SELECT * FROM group_info WHERE group_id =' + group.id;
            conn.query(sql, function (err, group_info) {
                if (err) {
                    callback(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }

                var opt = {};
                for (i = 0; i < group_info.length; i++) {
                    opt[group_info[i].property] = group_info[i].value;
                }

                var groupObj = create(group.name, group.type, group.description, opt, null);
                groupObj.id = group.id;
                groupObj.status = group.status;

                sql = 'SELECT * FROM group_project ' +
                    'WHERE group_id =' + group.id;
                conn.query(sql, function (err, results) {
                    if (err) {
                        callback(new DError('MODEL003', {
                            sql: sql
                        }, err));
                        return;
                    }
                    for (var i = 0; i < results.length; i++) {
                        groupObj.projects.push(results[i].project_id);
                    }
                    cb(null, groupObj);
                });
            });
        },
            function (err, results) {
                if (err) {
                    callback(new DError('USER002', {
                        condition: condition
                    }, err));
                    return;
                }
                callback(null, results);
            });
    });
}


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

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

    var key_list = ['project_id'];
    if (!condition.project_id) {
        callback(new DError('GROUP002'));
        return;
    }
    for (i = 0; i < key_list.length; i++) {
        var 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 group_project.group_id = groups.id AND ' + where_list.join(' AND ');
    } else {
        where = ' WHERE group_project.group_id = groups.id ';
    }


    var sql = 'SELECT groups.id, groups.name FROM group_project, groups ' + where + limit;
    conn.query(sql, function (err, groupProjects) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }
        var groups = [];
        _.each(groupProjects, function (groupProject) {
            groups.push(groupProject.name);
        });
        callback(null, groups);
    });
}


/**
 * @function update
 * @param {object} conn - db connection object
 * @param {module:models/group.Group} group - group
 * @param {module:models/group.callback_err_distributionid} callback - callback
 * @memberOf module:models/group
 */

function update(conn, group, callback) {
    async.waterfall([
        function (wcb) {
            conn.query('START TRANSACTION', function (err) {
                wcb(err);
            });
        },
        function (wcb) {
            var stmt = 'UPDATE groups SET ';
            stmt += 'name = ' + utils.DBStr(group.name) + ', ';
            stmt += 'type = ' + utils.DBStr(group.type) + ', ';
            stmt += 'description = ' + utils.DBStr(group.description) + ', ';
            if (group.status.toUpperCase() === 'OPEN') {
                stmt += 'status = ' + utils.DBStr(group.status);
            } else {
                stmt += 'status = \'CLOSE\' ';
            }
            stmt += 'WHERE id = ' + group.id;
            conn.query(stmt, function (err, result) {
                wcb(err);
            });
        },
        function (wcb) {
            if (_.size(group.options) !== 0) {
                async.map(_.keys(group.options),
                    function (index, mcb) {
                        var stmt = 'SELECT * FROM group_info ' +
                            'WHERE group_id = ' + group.id + ' AND property = ' + utils.DBStr(index);
                        conn.query(stmt, function (err, result) {
                            if (err) {
                                mcb(new DError('MODEL003', {
                                    sql: stmt
                                }, err));
                                return;
                            }

                            var stmt1 = null;
                            if (result.length === 0) {
                                stmt1 = 'INSERT INTO group_info SET ' +
                                    'group_id = ' + group.id + ' ' +
                                    ', property = ' + utils.DBStr(index) +
                                    ', value = ' + utils.DBStr(JSON.stringify(group.options[index]));
                            } else {
                                stmt1 = 'UPDATE group_info SET ' +
                                    'value = ' + utils.DBStr(JSON.stringify(group.options[index])) +
                                    ' WHERE group_id = ' + group.id + ' AND property = ' + utils.DBStr(index);
                            }
                            conn.query(stmt1, function (err, result) {
                                if (err) {
                                    mcb(new DError('MODEL003', {
                                        sql: stmt1
                                    }, err));
                                } else {
                                    mcb(err);
                                }
                            });
                        });
                    }, function (err, result) {
                        wcb(err);
                    });
            } else {
                wcb(null);
            }
        },
        function (wcb) {
            async.waterfall([
                function (cb) {
                    var existProjectIds = [];
                    var stmt = 'SELECT projects.id FROM group_project, projects ' +
                        'WHERE group_project.group_id = ' + group.id +
                        ' AND group_project.project_id = projects.id';
                    conn.query(stmt, function (err, results) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: stmt
                            }, err), null);
                            return;
                        }

                        async.eachSeries(results, function (result, cb1) {
                            if (!_.contains(group.projects, result.id)) {
                                var stmt2 = 'DELETE FROM group_project ' +
                                    'WHERE group_id = ' + group.id + ' AND project_id = ' + result.id;
                                conn.query(stmt2, function (err, result) {
                                    if (err) {
                                        cb1(new DError('MODEL003', {
                                            sql: stmt2
                                        }, err));
                                    } else {
                                        cb1(null);
                                    }
                                });
                            } else {
                                existProjectIds.push(result.id);
                                cb1(null);
                            }
                        }, function (err) {
                            cb(err, existProjectIds);
                        });
                    });
                },
                function (existProjectIds, cb) {
                    async.eachSeries(group.projects, function (projectId, cb1) {
                        if (!_.contains(existProjectIds, projectId)) {
                            stmt = 'INSERT INTO group_project (group_id, project_id ) ' +
                                'SELECT (SELECT id FROM groups WHERE id =' + group.id + '),' +
                                ' (SELECT id FROM projects WHERE id = ' + projectId + ') ' +
                                'FROM DUAL';
                            conn.query(stmt, function (err, result) {
                                if (err) {
                                    cb1(new DError('MODEL003', {
                                        sql: stmt
                                    }, err));
                                } else {
                                    cb1(err);
                                }
                            });
                        } else {
                            cb1(null);
                        }
                    }, function (err) {
                        cb(err);
                    });
                }], function (err, result) {
                wcb(err);
            });
        },
        function (wcb) {
            conn.query('COMMIT', wcb);
        }
    ], function (err) {
        if (err) {
            conn.query('ROLLBACK', function () {});
        }
        callback(err);
    });
}


function deleteUserGroup(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) {
            callback(new DError('GROUP003', {
                condition: condition
            }, err), null);
            return;
        }

        // excute transaction sql
        async.waterfall(
            [
                // Find
                function (cb) {
                    select(conn, condition, function (err, items) {
                        if (err) {
                            cb(err, null);
                            return;
                        }
                        if (items.length === 0) {
                            cb(new DError('GROUP004', {
                                condition: condition
                            }), null);
                            return;
                        }

                        cb(null, items[0]); // result is only one.
                    });
                },
                // TABLE: group_info
                function (item, cb) {
                    var sql = 'DELETE FROM group_info WHERE group_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: user_group
                function (item, cb) {
                    var sql = 'DELETE FROM user_group WHERE group_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: group_project
                function (item, cb) {
                    var sql = 'DELETE FROM group_project WHERE group_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: notifications
                // NOTE. change its target_group_id to NULL
                function (item, cb) {
                    var sql = 'UPDATE notifications SET target_group_id = NULL WHERE target_group_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: distributions
                // NOTE. change its owner to admin
                function (item, cb) {
                    var sql = 'UPDATE distributions SET group_id = 1 WHERE group_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: groups
                function (item, cb) {
                    var sql = 'DELETE FROM groups WHERE id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err), null);
                        } else {
                            cb(null, item);
                        }
                    });
                },
                // commit sql
                function (item, cb) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            cb(new DError('MODEL002', err), null);
                            return;
                        }
                        cb(err, item);
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('GROUP003', {
                        condition: condition
                    }, err), null);
                    return;
                }
                callback(err, result);
            });
    });
}
