/**
 * Connect to sqlite data base.
 * @function lib.db._connectSqlite
 * @param {string} url - Data base url.
 * @param {object} models - Models object.
 * @protected
 * @ignore
 * @author Taka Okunishi
 *
 */
var path = require('path'),
    string = require('../../lib/string'),
    keyValueFormat = string.keyValueFormat,
    object = require('../../lib/object'),
    random = require('../../lib/random'),
    choice = random.choice,
    copy = object.copy,
    underscorize = string.underscorize,
    define = object.define,
    isString = string.isString;

exports = module.exports = function (url, models) {
    if (!models) models = {};
    var sqlite3 = require('sqlite3');

    if (!url.match(/\.db$/))url = [url, 'db'].join('.');
    url = path.resolve(url);
    var connection = {
        db: null,
        open: function () {
            connection.db = new (sqlite3.verbose()).Database(url);
            Object.keys(models).forEach(function (key) {
                var model = models[key];
                model.prototype._connection = new Connection(connection.db, key);
            });
            return connection;
        },
        close: function () {
            connection.db && connection.db.close();
            return connection;
        },
        dropDatabase: function (callback) {
            connection.close();
            var fs = require('fs');
            fs.exists(url, function (exists) {
                if (exists) {
                    connection.open();
                    callback(null);
                } else {
                    fs.unlink(url, function (err) {
                        if (!err) {
                            connection.open();
                        }
                        callback(err);
                    });
                }
                return connection;
            });
        }
    };
    return connection.open();
};

exports._queriesFormat = function (table_name) {
    var tmpl = {
        createTable: "CREATE TABLE IF NOT EXISTS {{table_name}} (_id TEXT, data TEXT);",
        insert: "INSERT INTO {{table_name}} VALUES ( '{{_id}}', '{{data}}' );commit;",
        update: "UPDATE {{table_name}} SET data='{{data}}' WHERE _id='{{_id}}';commit;",
        select: "SELECT * FROM {{table_name}};",
        remove: "DELETE FROM {{table_name}} WHERE _id='{{_id}}';commit;",
        truncate: "TRUNCATE TABLE {{table_name}}"
    };
    var result = {};
    Object.keys(tmpl).forEach(function (key) {
        result[key] = tmpl[key].replace(/\{\{table_name\}\}/g);
    });
    return result;
};


/**
 * Hit or not
 * @param {object} conditions
 * @param {object} data
 * @returns {boolean} - Hit or not.
 * @protected
 * @ignore
 */
exports._hit = function (conditions, data) {
    if (!data) return false;
    var keys = Object.keys(conditions),
        propertyName, condition, result = false;
    if (!keys.length) return true;
    for (var i = 0; i < keys.length; i++) {
        propertyName = keys[i];
        condition = conditions[propertyName];
        var value = data[propertyName];
        if (isString(condition)) {
            result = (value === condition);
        } else {
            var $regex = condition && condition['$regex'];
            if ($regex) {
                if (isString($regex)) $regex = new RegExp($regex);
                result = value && value.match($regex);
            } else {
                result = condition.toString() === value;
            }
        }
        if (!result) return false;
    }
    return result;
};


/**
 * Database connection
 * @constructor
 * @param {object} db - Database object.
 * @param {string} name
 * @protected
 * @ignore
 */
var Connection = exports._Connection = define({
    init: function (db, name) {
        var s = this;
        s.db = db;
        var tableName = underscorize(name).toUpperCase();
        s.sql = exports._queriesFormat(tableName);
        db.serialize(function () {
            db.run(keyValueFormat(s.sql.createTable, {}));
        });
    },
    accessor: 'limit,skip'.split(','),
    properties: {
        _limit: Infinity,
        _skip: 0,
        reset: function () {
            var s = this;
            s.skip(0);
            s.limit(Infinity);
        },
        newId: function () {
            var result = '';
            while (result.length < 24) {
                result += choice.apply(choice, ('1234567890abcdef'.split('')));
            }
            return result;
        },
        save: function (data, callback) {
            var s = this,
                _id = data._id = data._id || s.newId();
            var sql = keyValueFormat(s.sql.insert, {
                _id: _id,
                data: JSON.stringify(data)
            });
            s.db.run(sql, function (err) {
                callback(err, data);
            });
            return s;
        },
        update: function (condition, data, opt, callback) {
            var id = data._id = condition._id;
            var s = this;
            s.findOne({_id: id}, function (err, origin) {
                var $set = data['$set'] || {};
                if (err) {
                    callback && callback(err, null);
                    return;
                }
                $set = copy($set, origin);
                var sql = keyValueFormat(s.sql.update, {
                    _id: id,
                    data: JSON.stringify($set)
                });
                s.db.run(sql, function (err) {
                    callback && callback(err, data);
                });
            });
            return s;
        },
        remove: function (data, caller, callback) {
            var s = this,
                sql = keyValueFormat(s.sql.remove, data);
            s.db.run(sql, function (err) {
                callback(err, data);
            });
            return s;
        },
        find: function (condition, callback) {
            var s = this;
            var sql = keyValueFormat(s.sql.select, {});
            var db = s.db;
            var result = [];

            db.serialize(function () {
                s.db.each(sql, function (err, selected) {
                    try {
                        var data = selected.data && JSON.parse(selected.data);
                        data._id = selected._id;
                        var hit = exports._hit(condition, data);
                        if (hit) {
                            result.push(data);
                        }
                    } catch (e) {
                        console.error(e);
                    }
                }, function () {
                    for (var i = 0; i < s.skip(); i++) {
                        result.shift();
                    }
                    while (s.limit() < result.length) {
                        result.pop();
                    }
                    s.reset();
                    callback(null, result);
                });
            });
            return s;
        },
        findOne: function (condition, callback) {
            var s = this;
            s.find(condition, function (err, result) {
                callback(err, result && result.length && result[0] || null);
            });
            return s;
        },
        count: function (condition, callback) {
            var s = this;
            s.find(condition, function (err, result) {
                callback(err, result && result.length || 0);
            });
            return s;
        },
        ensureIndex: function (config, callback) {
            //Do nothing.
            for (var i = 0; i < arguments.length; i++) {
                var arg = arguments[i];
                if (typeof(arg) === 'function') arg();
            }
        },
        getIndexes: function (callback) {
            //Do nothing.
            for (var i = 0; i < arguments.length; i++) {
                var arg = arguments[i];
                if (typeof(arg) === 'function') arg();
            }
        }
    }
});