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

/**
 * @module models/user
 */

/**
 * @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/user
 */

function User(email, name, password_hash, password_salt, image_id, options) {
    /** type {string} */
    this.id = -1;
    /** type {string} */
    this.email = email;
    /** type {string} */
    this.name = name;
    /** type {string} */
    this.status = 'CLOSE';
    /** type {string} */
    this.password_hash = password_hash;
    /** type {string} */
    this.password_salt = password_salt;
    /** type {string} */
    this.image_id = image_id;
    /** type {module:models/user.options} */
    this.options = options;
    /** type {module:models/user.options} */
    this.groups = [];
}

/**
 * @function create
 * @param {string} email - email
 * @param {string} name - name
 * @param {string} password_hash - password_hash
 * @param {string} password_salt - password_salt
 * @param {string} options - options
 * @returns {module:models/user.User}
 * @memberOf module:models/user
 */

function create(email, name, password_hash, password_salt, img_id, options) {
    return new User(email, name, password_hash, password_salt, img_id, options);
}
module.exports.create = create;

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

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

        // excute transaction sql
        async.waterfall(
            [
                // TABLE: users
                function (cb1) {
                    var sql = 'INSERT INTO users ( name' +
                        ', email' +
                        ', password_hash' +
                        ', password_salt' +
                        ', image_id' +
                        ', status) ' +
                        'VALUES (' + utils.DBStr(user.name) +
                        ', ' + utils.DBStr(user.email) +
                        ', ' + utils.DBStr(user.password_hash) +
                        ', ' + utils.DBStr(user.password_salt) +
                        ', ' + utils.DBStr(user.image_id) +
                        ', ' + utils.DBStr(user.status) + ')';

                    conn.query(sql, function (err, result) {
                        if (err) {
                            callback(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        cb1(null, result.insertId);
                    });
                },
                // TABLE: user_info
                function (id, cb1) {
                    var key;
                    var sqls = [];
                    for (key in user.options) {
                        var sql = 'INSERT INTO user_info ( user_id' +
                            ', property' +
                            ', value)' +
                            'VALUES (' + id +
                            ', ' + utils.DBStr(key) +
                            ', ' + utils.DBStr(user.options[key]) + ')';
                        sqls.push(sql);
                    }

                    async.map(sqls, function (sql, cb2) {
                        conn.query(sql, function (err, result) {
                            if (err) {
                                callback(new DError('MODEL003', {
                                    sql: sql
                                }, err));
                                return;
                            }
                            cb2(null, result);
                        });
                    },
                        function (err, results) {
                            if (err) {
                                callback(new DError('MODEL002', err));
                                return;
                            }
                            cb1(null, id);
                        });
                },
                // commit sql
                function (id, cb1) {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            callback(new DError('MODEL002', err));
                            return;
                        }
                        cb1(err, id);
                    });
                }
            ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {});
                    callback(new DError('USER001', {
                        email: user.email
                    }, 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
 */

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

    var key_list = ['id', 'name', 'email', 'password_hash', 'password_salt', 'image_id', '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 users' + where + limit;
    conn.query(sql, function (err, users) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }
        async.map(users, function (user, cb) {
            var sql = 'SELECT * FROM user_info WHERE user_id =' + user.id;
            conn.query(sql, function (err, user_info) {
                if (err) {
                    callback(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }

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

                var userObj = create(user.email,
                    user.name, user.password_hash, user.password_salt, user.image_id, opt);
                userObj.id = user.id;
                userObj.status = user.status;
                userObj.isAdmin = false;

                sql = 'SELECT * FROM user_group, groups ' +
                    'WHERE user_id =' + user.id + ' AND user_group.group_id = groups.id';
                conn.query(sql, function (err, groups) {
                    if (err) {
                        callback(new DError('MODEL003', {
                            sql: sql
                        }, err));
                        return;
                    }
                    for (var i = 0; i < groups.length; i++) {
                        userObj.groups.push(groups[i].name);
                        if (groups[i].name === 'administrator') {
                            userObj.isAdmin = true;
                        }
                    }
                    cb(null, userObj);
                });
            });
        },
            function (err, results) {
                if (err) {
                    callback(new DError('USER002', {
                        condition: condition
                    }, err));
                    return;
                }
                callback(null, results);
            });
    });
}

/**
 * @function selectGroup
 * @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
 */

module.exports.selectGroup = function (conn, email, callback) {
    var sql = 'SELECT * FROM users WHERE email = ' + utils.DBStr(email);
    conn.query(sql, function (err, users) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }

        if (users.length === 0) {
            callback(new DError('USER003', {
                email: email
            }, err));
            return;
        }

        var user = users[0];

        sql = 'SELECT * FROM user_info WHERE user_id =' + user.id;
        conn.query(sql, function (err, user_info) {
            if (err) {
                callback(new DError('MODEL003', {
                    sql: sql
                }, err));
                return;
            }

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

            var userObj = create(user.email, user.name, user.password_hash, user.password_salt, user.image_id, opt);
            userObj.id = user.id;
            userObj.status = user.status;
            userObj.isAdmin = false;

            sql = 'SELECT * FROM user_group, groups ' +
                'WHERE user_id =' + user.id + ' AND user_group.group_id = groups.id AND groups.status =\'OPEN\'';
            conn.query(sql, function (err, groups) {
                if (err) {
                    callback(new DError('MODEL003', {
                        sql: sql
                    }, err));
                    return;
                }
                for (var i = 0; i < groups.length; i++) {
                    userObj.groups.push(groups[i]);
                    if (groups[i].name === 'administrator') {
                        userObj.isAdmin = true;
                    }
                }
                callback(null, userObj);
            });
        });
    });
};

/**
 * @function selectUsersByGroupName
 * @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
 */

module.exports.selectUsersByGroupName = function (conn, groupName, callback) {
    var sql = 'SELECT * FROM groups WHERE name = ' + utils.DBStr(groupName);
    conn.query(sql, function (err, groups) {
        if (err) {
            callback(new DError('MODEL003', {
                sql: sql
            }, err));
            return;
        }

        if (groups.length !== 1) {
            callback(new DError('GROUP001', {
                group: groupName
            }, err));
            return;
        }

        var group = groups[0];
        sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM user_group WHERE group_id=' + group.id + ')';
        conn.query(sql, function (err, users) {
            if (err) {
                callback(new DError('MODEL003', {
                    sql: sql
                }, err));
                return;
            }
            async.map(users, function (user, cb) {
                var sql = 'SELECT * FROM user_info WHERE user_id =' + user.id;
                conn.query(sql, function (err, user_info) {
                    if (err) {
                        callback(new DError('MODEL003', {
                            sql: sql
                        }, err));
                        return;
                    }

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

                    var userObj = create(user.email, user.name, user.password_hash, user.password_salt, user.image_id, opt);
                    userObj.id = user.id;
                    userObj.status = user.status;
                    userObj.isAdmin = false;

                    sql = 'SELECT * FROM user_group, groups ' +
                        'WHERE user_id =' + user.id + ' AND user_group.group_id = groups.id';
                    conn.query(sql, function (err, groups) {
                        if (err) {
                            callback(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        for (var i = 0; i < groups.length; i++) {
                            userObj.groups.push(groups[i].name);
                            if (groups[i].name === 'administrator') {
                                userObj.isAdmin = true;
                            }
                        }
                        cb(null, userObj);
                    });
                });
            }, function (err, results) {
                if (err) {
                    callback(new DError('GROUP001', {
                        group: groupName
                    }, err));
                    return;
                }
                callback(null, results);
            });
        });
    });
};

/**
 * @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
 */

module.exports.update = function (conn, user, callback) {
    async.waterfall([
        function (wcb) {
            conn.query('START TRANSACTION', function (err) {
                wcb(err);
            });
        },
        function (wcb) {
            var stmt = 'UPDATE users SET ';
            stmt += 'name = ' + utils.DBStr(user.name) + ', ';
            stmt += 'email = ' + utils.DBStr(user.email) + ', ';
            stmt += 'image_id = ' + utils.DBStr(user.image_id) + ', ';
            if (user.password_hash) {
                stmt += 'password_hash = ' + utils.DBStr(user.password_hash) + ', ';
            }
            if (user.password_salt) {
                stmt += 'password_salt = ' + utils.DBStr(user.password_salt) + ', ';
            }
            if (user.status.toUpperCase() === 'OPEN') {
                stmt += 'status = ' + utils.DBStr(user.status);
            } else {
                stmt += 'status = \'CLOSE\' ';
            }
            stmt += 'WHERE id = ' + user.id;
            conn.query(stmt, function (err, result) {
                wcb(err);
            });
        },
        function (wcb) {
            if (_.size(user.options) !== 0) {
                async.map(_.keys(user.options),
                    function (index, mcb) {
                        var stmt = 'SELECT * FROM user_info ' +
                            'WHERE user_id = ' + user.id + ' AND property = ' + utils.DBStr(index);
                        conn.query(stmt, function (err, result) {
                            var stmt = null;
                            if (err) {
                                mcb(new DError('MODEL003', {
                                    sql: stmt
                                }, err));
                                return;
                            }

                            if (result.length === 0) {
                                stmt = 'INSERT INTO user_info SET ' +
                                    'user_id = ' + user.id + ' ' +
                                    ', property = ' + utils.DBStr(index) +
                                    ', value = ' + utils.DBStr(JSON.stringify(user.options[index]));
                            } else {
                                stmt = 'UPDATE user_info SET ' +
                                    'value = ' + utils.DBStr(JSON.stringify(user.options[index])) +
                                    'WHERE user_id = ' + user.id + ' AND property = ' + utils.DBStr(index);
                            }
                            conn.query(stmt, function (err, result) {
                                if (err) {
                                    mcb(new DError('MODEL003', {
                                        sql: stmt
                                    }, err));
                                } else {
                                    mcb(err);
                                }
                            });
                        });
                    }, function (err, result) {
                        wcb(err);
                    });
            } else {
                wcb(null);
            }
        },
        function (wcb) {
            async.waterfall([
                function (cb) {
                    var existGroups = [];
                    var stmt = 'SELECT * FROM user_group, groups ' +
                        'WHERE user_group.user_id = ' + user.id +
                        ' AND user_group.group_id = groups.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(user.groups, result.name)) {
                                var stmt2 = 'DELETE FROM user_group ' +
                                    'WHERE user_id = ' + user.id + ' AND group_id = ' + result.group_id;
                                conn.query(stmt2, function (err, result) {
                                    if (err) {
                                        cb1(new DError('MODEL003', {
                                            sql: stmt2
                                        }, err));
                                    } else {
                                        cb1(null);
                                    }
                                });
                            } else {
                                existGroups.push(result.name);
                                cb1(null);
                            }
                        }, function (err) {
                            cb(err, existGroups);
                        });
                    });
                },
                function (existGroups, cb) {
                    async.eachSeries(user.groups, function (group, cb1) {
                        if (!_.contains(existGroups, group)) {
                            var stmt = 'INSERT INTO user_group ( user_id, group_id ) ' +
                                'SELECT (SELECT id FROM users WHERE id =' + user.id + '),' +
                                ' (SELECT id FROM groups WHERE name = ' + utils.DBStr(group) + ') ' +
                                '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);
    });
};


module.exports.delete = function (conn, condition, callback) {
    conn.query('START TRANSACTION', function (err) {
        if (err) {
            callback(new DError('MODEL001', err), null);
            return;
        }
        async.series([
            function (cb) {
                deleteInternal(conn, condition, cb);
            }
        ],
            function (err, result) {
                if (err) {
                    conn.query('ROLLBACK', function () {
                        callback(err);
                    });
                } else {
                    conn.query('COMMIT', function (err) {
                        if (err) {
                            callback(new DError('MODEL002', err));
                        } else {
                            callback(null);
                        }
                    });
                }
            });
    });
};


function deleteInternal(conn, condition, callback) {

    // Check condition of key
    if (!condition.id) {
        callback(new DError('USER006', {
            condition: condition
        }, null));
        return;
    }

    select(conn, condition, function (err, items) {
        if (err) {
            callback(err, null);
            return;
        }
        if (items.length !== 1) {
            callback(new DError('USER004'), null);
            return;
        }
        var item = items[0];

        async.series(
            [
                function (cb) {
                    conn.query('START TRANSACTION', function (err) {
                        cb(err);
                    });
                },
                // TABLE: user_info
                function (cb) {
                    var sql = 'DELETE FROM user_info WHERE user_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        cb(null);
                    });
                },
                // TABLE: user_board
                function (cb) {
                    var sql = 'DELETE FROM user_board WHERE user_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        cb(null);
                    });
                },
                // TABLE: user_group
                function (cb) {
                    var sql = 'DELETE FROM user_group WHERE user_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: notifications
                function (cb) {
                    var sql = 'DELETE FROM notifications WHERE target_user_id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }, err));
                            return;
                        }
                        cb(null, item);
                    });
                },
                // TABLE: users
                function (cb) {
                    var sql = 'DELETE FROM users WHERE id = ' + item.id;
                    conn.query(sql, function (err, result) {
                        if (err) {
                            cb(new DError('MODEL003', {
                                sql: sql
                            }));
                            return;
                        }
                        cb(null);
                    });
                },
                function (cb) {
                    conn.query('COMMIT', cb);
                }
            ],
            function (err) {
                if (err) {
                    conn.query('ROLLBACK', function () {
                        callback(err);
                    });
                } else {
                    callback(err);
                }
            });

    });
}
