"use strict";

var fs           = require('fs');
var Path         = require('path');
var mime         = require('mime');
var crypto       = require('crypto');
var zlib         = require('zlib');
var util         = require('util');
var httpHandler  = require('./http-handler');
var EventEmitter = require('events').EventEmitter;
var console      = require('console-plus');
var crc          = require('crc');

var limitSizeFileToCache = 10485760;

function CacheContainer(){
    var fullPathList = {};
    var content  = {};
    var watchers = {};
    var Self = this;
    var reportTimer = null;
    var contentLength = 0;
    var contentSize = 0;

    function createReport(){
        if (!reportTimer){
            reportTimer = setTimeout(function(){
                console.info("Cache size: " + Self.getTotalCacheSize() + " " + "Cache list: " + contentLength);

                reportTimer = null;
            }, 5000);
        }
    }

    function calcSize(clc){
        var size = clc.length || 0;

        if (clc.gzip){
            size += clc.gzip.length || 0;
        } else
        if (clc.deflate){
            size += clc.deflate.length || 0;
        }

        return size;
    }

    this.addToCache = function(data, _watcher){
        var path = crc.crc32(data.k).toString();
        var fullPath = crc.crc32(data.p).toString();

        if (content.hasOwnProperty(path)){
            console.info('The file has been updated: ' + content[path].p);

            contentSize -= calcSize(content[path]);

        } else {
            contentLength ++;
        }

        fullPathList[fullPath] = path;
        content[path] = data;
        watchers[fullPath] = _watcher;

        contentSize += calcSize(content[path]);

        createReport();
    };

    this.findFileInCache = function(_path){
        var path = crc.crc32(_path).toString();

        return content.hasOwnProperty(path) ? content[path] : null;
    };

    this.removeFromCache = function(_path){
        var fullPath = crc.crc32(_path).toString();

        if (fullPathList.hasOwnProperty(fullPath)){
            var path = fullPathList[fullPath];
            contentLength --;

            contentSize -= calcSize(content[path]);

            try{
                watchers[fullPath].close();
            } catch(e){}

            content[path]  = null;
            watchers[fullPath] = null;
            fullPathList[fullPath] = null;

            delete fullPathList[fullPath];
            delete watchers[fullPath];
            delete content[path];

            createReport();

            console.warn('Removing from cache: ' + _path + " count: " + contentLength, console.logLevel.L_Extended);
        }
    };

    this.clearCache = function(){
        content = {};

        for (var i in watchers){
            try {
                watchers[i].close();
            } catch(e){}
        }

        watchers = {};
        fullPathList = {};

        contentLength = 0;
        contentSize = 0;

        createReport();
    };

    this.getTotalCacheSize = function(){
        return contentSize;
    };

    this.contentLength = function(){
        return contentLength;
    };
}

var cache = new CacheContainer();

function doDeflate(data, callback){
    try {
        zlib.deflate(data, function(err, res) {
            if (err) {
                callback(err);
            } else {
                data.deflate = res;
                callback(null, data);
            }
        });
    } catch(e){
        callback(null, data);
    }
}

function doGzip(data, callback){
    try{
        zlib.gzip(data, function(err, res) {
            if (err) {
                callback(err, null);
            } else {
                data.gzip = res;
                callback(null, data);
            }
        });
    } catch(e){
        callback(null, data);
    }
}

function prepare(data, options, callback) {
    if (options.hashAlgo) {
        var hash = crypto.createHash(options.hashAlgo);
            hash.update(data);

        data.hash = hash.digest('hex');
    }

    if (options.gzip) {
        doGzip(data, callback);
    } else
    if (options.deflate) {
        doDeflate(data, callback);
    } else {
        callback(null, data);
    }
}

function watchHandle(path, options, em){
    var watcher = null;

    try{
        watcher = fs.watch(path);

        watcher.on('change', function() { // event, filename
            //try {
                watcher.close();
            //} catch(e){}

            //setTimeout(function(){
            generate(path, options, em);
            //}, 50);
        });

        watcher.on('error', function(err) {
            //try {
                watcher.close();
            //} catch(e){}

            cache.removeFromCache(path);

            em.emit('error', err);
        });
    } catch(e){
        cache.removeFromCache(path);

        watcher = null;
    }

    return watcher;
}

function generate(path, options, em) {
    var start = path;
    var queue = [start];

    function readDir(path){
        fs.readdir(path, function(err, files) {
            if (err) {
                em.emit('error', err);
            } else {
                options.root = options.root || path;

                if (options.watchDirectoryChange) {
                    //watchHandle(path, options, em);
                }

                //pending ++;

                queue = queue.concat(files.map(function(file) {
                    return Path.resolve(path, file)
                }));

                read();
            }
        })
    }

    function readFile(path, fileInfo){
        fs.readFile(path, function(err, fileData) {
            if (err) {
                cache.removeFromCache(path);

                em.emit('error', err);
            } else {
                options.root = options.root || Path.dirname(path);

                fileData.p         = path;
                fileData.k         = (options.prefix ? options.prefix : '') + (options.root ? path.slice(options.root.length) : path).replace(/\\/g, '/');
                fileData.mtime     = fileInfo.mtime;
                fileData.mime_type = mime.lookup(path);

                prepare(fileData, options, function(err, data) {
                    if (err) {
                        em.emit('error', err);
                    } else {
                        if (options.watchFileChange) {
                            var watcher = watchHandle(path, options, em);
                        }

                        if (watcher) {
                            //em.emit('contents', data, watcher);
                            cache.addToCache(data, watcher);
                        }

                        read();
                    }
                })
            }
        });
    }

    function getFileInfo(path){
        fs.stat(path, function(err, fileInfo) {
            if (err) {
                cache.removeFromCache(path);

                em.emit('error', err);
            } else {
                if (fileInfo.isDirectory()) {
                    readDir(path);
                } else
                if (fileInfo.size > limitSizeFileToCache) {
                    em.emit('errorLimit', {path: path, size: fileInfo.size, limit: limitSizeFileToCache});

                    cache.removeFromCache(path);
                } else {
                    readFile(path, fileInfo);
                }
            }
        })
    }

    function read() {
        if (queue.length === 0) {
            em.emit('done', cache /*path*/);
        } else {
            var path = queue.shift();

            getFileInfo(path);
        }
    }

    read();
}

function genOptions(options, defaults) {
    Object.keys(defaults).forEach(function(k) {
        if(!options.hasOwnProperty(k)) {
            options[k] = defaults[k]
        }
    });

    return options;
}

function FileCache(dir, defaultOptions, cb) {
    var em      = new EventEmitter();
    var pending = 0;

    if (typeof defaultOptions === 'function') {
        cb = defaultOptions;

        defaultOptions = {
            prefix              : false,
            watchDirectoryChange: false,
            watchFileChange     : false,
            gzip                : false,
            deflate             : false,
            hashAlgo            : false
        }
    }

    if (typeof dir === 'function') {
        cb  = dir;
        dir = null;
    }

    if(dir !== null && typeof dir === 'object' && !(dir instanceof String)) {
        defaultOptions = dir;
        dir = null;
    }

    if (typeof defaultOptions === 'boolean') { // legacy code
        defaultOptions = {
            watchFileChange: defaultOptions,
            hashAlgo       : 'sha1'
        };
    }

    cb = cb || function() {};

    /*em.on('contents', function(data, watcher) {
        cache.addToCache(data, watcher);

        //em.emit('change', data);
    });*/

    em.options = function(options) {
        Object.keys(options).forEach(function(k) {
            defaultOptions[k] = options[k];
        })
    };

    em.load = function(path, options, cb) {
        pending ++;

        if (typeof options === 'function') {
            cb      = options;
            options = {};
        }

        options = genOptions(options||{}, defaultOptions);

        path = Path.resolve(path);

        function onDone() {
            pending --;

            if (pending === 0) {
                em.removeListener('done', onDone);

                cb && cb(null, cache);

                em.emit('ready', cache); // {path: start, totalSize:  cache.getTotalCacheSize()}
            }
        }

        generate(path, options, em);

        em.on('done', onDone);

        return em;
    };

    em.httpHandler = function(options) {
        return httpHandler(options, cache);
    };

    em.clearCache = function() {
        cache.clearCache();
    };

    if (dir) {
        em.load(dir, cb)
    }

    em.setCacheFileSizeLimit = function(limit){
        limitSizeFileToCache = limit;
    };

    em.removeFromCache = function(path){
        cache.removeFromCache(path);
    };

    return em;
}

module.exports = FileCache;

