//   file-cache.js
//   This file contains the implementation of the file cache system used by the VWF nodeJS server.


var fs = require( 'fs' ),
    mime = require( 'mime' ),
    zlib = require( 'zlib' );


// Helper function to generate a hash for a string.
function hash( str ) {
    return require( 'crypto' ).createHash( 'md5' ).update( str ).digest( "hex" );
}

function _FileCache( ) {
    this.files = [ ];
    this.enabled = true;
    this.clear = function ( ) {
        this.files.length = 0;
    };
    
    // Function for determining whether to treat file as binary or text
    // based on file extentsion.
    this.getDataType = function ( file ) {
        var type = file.substr( file.lastIndexOf( '.' ) + 1 ).toLowerCase( );
        if ( type === 'js' || type === 'html' || type === 'xml' || type === 'txt' || type === 'xhtml' || type === 'css' ) {
            return "utf8";
        }
        else return "binary";
    };

    // Function for getting a file from the file cache.
    // Asynchronous, returns file to the callback function.
    // Passes null to the callback function if there is no such file.
    this.getFile = function ( path, callback ) {
        //First, check if we have already served this file
        // and have it already cached, if so, return the
        // already cached file.
        for ( var i = 0; i < this.files.length; i++ ) {
            if( this.files[ i ].path == path ) {    
                global.log( 'serving from cache: ' + path, 2 );
                callback( this.files[ i ] );
                return;
            }
        }

        // if got here, have no record of this file yet.
        var datatype = this.getDataType( path );
        var file = fs.readFileSync( path );
        var stats = fs.statSync( path );

        if ( file ) {
            // The file was not already in the cache, but does exist.
            // Gzip the file, which is an async process, and callback
            // with the gzipped file entry when finished.
            var self = this;
            zlib.gzip( file, function ( _, zippeddata ) {
                
                var newentry = { };
                newentry.path = path;
                newentry.data = file;
                newentry.stats = stats;
                newentry.zippeddata = zippeddata;
                newentry.contentlength = file.length;
                newentry.datatype = datatype;
                newentry.hash = hash( file );
                
                global.log( newentry.hash, 2 );
                global.log( 'loading into cache: ' + path, 2 );
                if ( self.enabled == true ) {
                    self.files.push( newentry );
                    var watcher = fs.watchFile( path, { }, function ( event, filename ) {
                        global.log( this.entry.path + ' has changed on disk', 2 );
                        self.files.splice( self.files.indexOf( this.entry ), 1 );
                        fs.unwatchFile(path); //make sure not to create new watchers at every reload
                    } );
                    watcher.entry = newentry;
                }
                callback( newentry );
                return;
            } );

            // Just returning since we're waiting on the async gzip process.
            return;
        }
        // File was not in cache, and did not exists, return null.
        callback( null );
    };
    
    // Function to serve a file out of the filecache.
    this.ServeFile = function ( request, filename, response, URL ) {
        FileCache.getFile( filename, function ( file ) {
            if ( !file ) {
                response.writeHead( 500, {
                    "Content-Type": "text/plain"
                } );
                response.write( 'file load error' + '\n');
                response.end( );
                return;
            }

            var type = mime.getType( filename );

            if ( request.headers[ 'if-none-match' ] === file.hash ) {
                response.writeHead( 304, {
                    "Content-Type": type,
                    "Last-Modified": encodeURIComponent(file.stats.mtime),
                    "ETag": file.hash,
                    "Cache-Control":"public; max-age=31536000" ,
                } );
                response.end( );
                return;
            }

            if ( request.headers[ 'accept-encoding' ] && request.headers[ 'accept-encoding' ].indexOf( 'gzip' ) >= 0 ) {
                response.writeHead( 200, {
                    "Content-Type": type,
                    "Last-Modified": encodeURIComponent(file.stats.mtime),
                    "ETag": file.hash,
                    "Cache-Control":"public; max-age=31536000" ,
                    'Content-Encoding': 'gzip'
                } );
                response.write(file.zippeddata, file.datatype);
            }
            else if ( request.headers['range'] ) {
                // To support the HTML 5 audio tag, the server needs to respond with the Content-Range in the header
                    var range = request.headers['range'];
                    range = range.split('=')[1];
                    var ranges = range.split('-');
                    var start = parseInt(ranges[0]);
                    var end = parseInt(ranges[1]) || (file.contentlength - 1);


                    var bytesheader = 'bytes ' + start + '-' + end + '/' + file.contentlength;

                    response.writeHead(206, {
                        "Content-Type": type,
                        "Content-Length": (end - start) + 1,
                        "Last-Modified": encodeURIComponent(file.stats.mtime),
                        "ETag": file.hash,
                        "Cache-Control": "public; max-age=31536000",
                        "Accept-Ranges": "bytes",
                        "Content-Range": bytesheader
                    });
                    var newdata = file.data.slice(start, end + 1);
                    response.write(newdata, file.datatype);
            }
            else {
                response.writeHead( 200, {
                    "Content-Type": type,
                    "Last-Modified": encodeURIComponent(file.stats.mtime),
                    "ETag": file.hash,
                    "Cache-Control":"public; max-age=31536000"
                } );
                response.write( file.data, file.datatype );
            }
            response.end( );
        } );    
    };
}


exports._FileCache = _FileCache;