/**
 * Connect - SQLite3
 * Copyright(c) 2012 David Feinberg
 * MIT Licensed
 * forked from https://github.com/tnantoka/connect-sqlite
 */

/**
 * Module dependencies.
 */
var sqlite3 = require('sqlite3'),
    events = require('events'),
    fs = require('fs');

/**
 * @type {Integer}  One day in milliseconds.
 */
var oneDay = 86400000;

/**
 * Return the SQLiteStore extending connect's session Store.
 *
 * @param   {object}    connect
 * @return  {Function}
 * @api     public
 */
module.exports = function(connect) {
    /**
     * Connect's Store.
     */
    var Store = (connect.session) ? connect.session.Store : connect.Store;

    /**
     * Remove expired sessions from database.
     * @param   {Object}    store
     * @api     private
     */
    function dbCleanup(store) {
        var now = new Date().getTime();
        store.db.run('DELETE FROM ' + store.table + ' WHERE ? > expired', [now]);
    }

    /**
     * Initialize SQLiteStore with the given options.
     *
     * @param   {Object}    options
     * @api     public
     */
    function SQLiteStore(options) {
        options = options || {};
        Store.call(this, options);

        this.table = options.table || 'sessions';
        this.db = options.db || this.table;
        var dbPath;

        if (this.db.indexOf(':memory:') > -1 || this.db.indexOf('?mode=memory') > -1) {
            dbPath = this.db;
        } else {
            dbPath = (options.dir || '.') + '/' + this.db;
        }

        if (options.dir && options.createDirIfNotExists) {
            try {
                fs.mkdirSync(options.dir,
                    {
                        recursive: true
                    });
            }
            catch {
            }
        }
        
        this.db = new sqlite3.Database(dbPath, options.mode);
        this.client = new events.EventEmitter();
        var self = this;

        this.db.exec((options.concurrentDb ? 'PRAGMA journal_mode = wal; ' : '') + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + 'sid PRIMARY KEY, ' + 'expired, sess)',
            function(err) {
                if (err) throw err;
                self.client.emit('connect');

                dbCleanup(self);
                setInterval(dbCleanup, oneDay, self).unref();
            }
        );
    }

    /**
     * Inherit from Store.
     */
    SQLiteStore.prototype = Object.create(Store.prototype);
    SQLiteStore.prototype.constructor = SQLiteStore;

    /**
     * Attempt to fetch session by the given sid.
     *
     * @param   {String}    sid
     * @param   {Function}  fn
     * @api     public
     */
    SQLiteStore.prototype.get = function(sid, fn) {
        var now = new Date().getTime();
        this.db.get('SELECT sess FROM ' + this.table + ' WHERE sid = ? AND ? <= expired', [sid, now],
            function(err, row) {
                if (err) fn(err);
                if (!row) return fn();
                fn(null, JSON.parse(row.sess));
            }
        );
    };

    /**
     * Commit the given `sess` object associated with the given `sid`.
     *
     * @param   {String}    sid
     * @param   {Session}   sess
     * @param   {Function}  fn
     * @api     public
     */
    SQLiteStore.prototype.set = function(sid, sess, fn) {
        try {
            var maxAge = sess.cookie.maxAge;
            var now = new Date().getTime();
            var expired = maxAge ? now + maxAge : now + oneDay;
            sess = JSON.stringify(sess);

            this.db.all('INSERT OR REPLACE INTO ' + this.table + ' VALUES (?, ?, ?)',
                [sid, expired, sess],
                function(err, rows) {
                    if (fn) fn.apply(this, arguments);
                }
            );
        } catch (e) {
            if (fn) fn(e);
        }
    };

    /**
     * Destroy the session associated with the given `sid`.
     *
     * @param   {String}    sid
     * @api     public
     */
    SQLiteStore.prototype.destroy = function(sid, fn) {
        this.db.run('DELETE FROM ' + this.table + ' WHERE sid = ?', [sid], fn);
    };

    /**
     * Fetch all sessions.
     *
     * @param   {Function}  fn
     * @api     public
     */
    SQLiteStore.prototype.all = function(fn) {
        this.db.all('SELECT * FROM ' + this.table + '', function(err, rows) {
            if (err) fn(err);
            fn(null, rows.map((row) => JSON.parse(row.sess)));
        });
    };

    /**
     * Fetch number of sessions.
     *
     * @param   {Function}  fn
     * @api     public
     */
    SQLiteStore.prototype.length = function(fn) {
        this.db.all('SELECT COUNT(*) AS count FROM ' + this.table + '', function(err, rows) {
            if (err) fn(err);
            fn(null, rows[0].count);
        });
    };

    /**
     * Clear all sessions.
     *
     * @param   {Function}  fn
     * @api     public
     */
    SQLiteStore.prototype.clear = function(fn) {
        this.db.exec('DELETE FROM ' + this.table + '', function(err) {
            if (err) fn(err);
            fn(null, true);
        });
    };

    /**
     * Touch the given session object associated with the given session ID.
     *
     * @param   {string}    sid
     * @param   {object}    session
     * @param   {function}  fn
     * @public
     */
    SQLiteStore.prototype.touch = function(sid, session, fn) {
        if (session && session.cookie && session.cookie.expires) {
            var now = new Date().getTime();
            var cookieExpires = new Date(session.cookie.expires).getTime();
            this.db.run('UPDATE ' + this.table + ' SET expired=? WHERE sid = ? AND ? <= expired',
                [cookieExpires, sid, now],
                function(err) {
                    if (fn) {
                        if (err) fn(err);
                        fn(null, true);
                    }
                }
            );
        } else {
           fn(null, true);
        }
    }

    return SQLiteStore;
};
