//  persistence.js
//  Helper functions for VWF node server that handles all persitence related requests

var helpers = require( './helpers' ),
    serve = require( './serve' ),
    storage = require( './storagefs' ),
    fs = require( 'fs' ),
    url = require( 'url' ),
    libpath = require( 'path' ),
    querystring = require( 'querystring' );



function CreateSaveDirectory( application_path, save_name, save_revision ) {
    var application_segments = helpers.GenerateSegments( application_path );
    var current_directory = "./documents";
    while ( application_segments.length > 0 ) {
        current_directory = helpers.JoinPath( current_directory, application_segments.shift() );
        if ( ! helpers.IsDirectory( current_directory ) ) {
            fs.mkdirSync( current_directory );
        }
    }
    current_directory = helpers.JoinPath( current_directory, save_name );
    if ( ! helpers.IsDirectory( current_directory ) ) {
        fs.mkdirSync( current_directory );
    }
}

// LoadSaveObject function is designed to take the load information provided by the
// GetLoadInformation function, load and parse the specified file, and return that object
// (or undefined, if there is no such file ).
function LoadSaveObject( loadInfo ) {
    if ( loadInfo[ 'save_name' ] ) {
        var fileName = helpers.JoinPath( "./documents", loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], "saveState_" + loadInfo[ 'save_revision' ] + ".vwf.json" );
        if ( helpers.IsFile( fileName ) ) {
            var fileContents = fs.readFileSync( fileName, "utf8" );
            return JSON.parse( fileContents );
        }
    }
    return undefined;
}

// LookupSaveRevisions takes the public path and the name of a save, and provides
// an array of all revisions for that save. (If the save does not exist, this will be
// an empty array).
function LookupSaveRevisions( public_path, save_name ) {
    var result = [ ];
    var directoryName = helpers.JoinPath( "./documents/", public_path, save_name );
    if ( helpers.IsDirectory( directoryName ) ) {
        var potentialSaves = fs.readdirSync( directoryName.replace( /\//g, libpath.sep ) );
        for ( var index = 0; index < potentialSaves.length; index++ ) {
            if ( potentialSaves[ index ].match( /^saveState_\d+\.vwf\.json$/ ) ) {
                result.push( parseInt( potentialSaves[ index ].slice( 10, potentialSaves[ index ].length - 9 ) ) );
            }
        }
    }
    return result;
}

// GetLoadInformation receives a parsed request {private_path, public_path, instance, application} and returns the
// details of the save that is designated by the initial request. The details are returned in an object
// composed of: save_name (name of the save) save_revision (revision of the save), explicit_revision (boolean, true if the request
// explicitly specified the revision, false if it did not), and application_path (the public_path of the application this is a save for).
function GetLoadInformation( parsedRequest ) {
    var result = {'save_name': undefined, 'save_revision': undefined, 'explicit_revision': undefined, 'application_path': undefined };
    if ( parsedRequest[ 'private_path' ] ) {
        var segments = helpers.GenerateSegments( parsedRequest[ 'private_path' ] );
        if ( ( segments.length > 1 ) && ( segments[ 0 ] == "load" ) ) {
            var potentialRevisions = LookupSaveRevisions( parsedRequest[ 'public_path' ], segments[ 1 ] );
            if ( potentialRevisions.length > 0 ) {
                result[ 'save_name' ] = segments[ 1 ];
                
                if ( segments.length > 2 ) {
                    var requestedRevision = parseInt( segments[ 2 ] );
                    if ( requestedRevision ) {
                       if ( potentialRevisions.indexOf( requestedRevision ) > -1 ) {
                           result[ 'save_revision' ] = requestedRevision;
                           result[ 'explicit_revision' ] = true;
                           result[ 'application_path' ] = parsedRequest[ 'public_path' ];
                       }
                    }
                }
                if ( result[ 'explicit_revision' ] == undefined ) {
                    result[ 'explicit_revision' ] = false;
                    potentialRevisions.sort( );
                    result[ 'save_revision' ] = potentialRevisions.pop();
                    result[ 'application_path' ] = parsedRequest[ 'public_path' ];
                }
            }
        }
    }
    return result;
}

// GetSaveInformation is a helper function that takes a NodeJS request, a NodeJS response,
// and the application_path (/path/to/application). It returns an array of all saves found for that
// application (including separate entries for individual revisions of saves ).
function GetSaveInformation( request, response, application_path ) {
    var result = [ ];
    var potentialSaveNames = fs.readdirSync( helpers.JoinPath("./documents", application_path ) );
    for ( var index = 0; index < potentialSaveNames.length; index++ ) {
        if ( helpers.IsDirectory( helpers.JoinPath( "./documents", application_path, potentialSaveNames[ index ] ) ) ) {
            var revisionList = LookupSaveRevisions( application_path, potentialSaveNames[ index ] );
            var latestsave = true;
            revisionList.sort();
            while ( revisionList.length > 0 ) {
                var newEntry = {};
                newEntry[ 'applicationpath' ] = application_path;
                newEntry[ 'savename' ] = potentialSaveNames[ index ];
                newEntry[ 'revision' ] = revisionList.pop().toString();
                newEntry[ 'latestsave' ] = latestsave;
                if ( latestsave ) {
                    newEntry[ 'url' ] = helpers.JoinPath( "http://" + request.headers.host, application_path, "load", potentialSaveNames[ index ] + "/" );
                }
                else {
                    newEntry[ 'url' ] = helpers.JoinPath(  "http://" + request.headers.host, application_path, "load", potentialSaveNames[ index ] + "/", newEntry[ 'revision' ] + "/" );
                }
                latestsave = false;
                result.push( newEntry );
            }
        }
    }
    return result;
}

// GetSaveInformationRecursive is a helper function that recursively calls the
// GetSaveInformation function on a directory, and all directories within it (and within those, and so on).
function GetSaveInformationRecursive( request, response, application_path ) {
    var result = [];
    var subDirectories = fs.readdirSync( helpers.JoinPath("./documents", application_path ) );
    for ( var index = 0; index < subDirectories.length; index++ ) {
        if ( helpers.IsDirectory( helpers.JoinPath( "./documents", application_path, subDirectories[ index ] ) ) ) {
            result = result.concat( GetSaveInformationRecursive( request, response, helpers.JoinPath( application_path, subDirectories[ index ] ) ) );
        }
    }
    result = result.concat( GetSaveInformation( request, response, application_path ) );
    return result;
}

// HandlePersistenceLoad attempts to deal with a request which contains load arguments in the
// private_path.
// If the URL is properly formed, it will either (if the load portion of the URL is the last
// portion of the URL, and there is no trailing slash) redirect to the same URL with a trailing
// slash added, or if the URL is otherwise properly formed, it will remove the load specific
// portions of the private_path, and pass this updated parsedRequest on to the normal application
// level Serve function (effectively, load URL's are treated as if all load specific portions
// of the URL don't exist, except for the websocket connection, which uses it to load the initial
// state for the first client. ).
function HandlePersistenceLoad( request, response, parsedRequest, segments ) {
    var loadInformation = GetLoadInformation( parsedRequest );
    if ( loadInformation[ 'save_name' ] != undefined ) {
        segments.shift();
        segments.shift();
        if ( loadInformation[ 'explicit_revision' ] ) {
            segments.shift();
        }
        var subParsedRequest = { };
        subParsedRequest[ 'public_path' ] = parsedRequest[ 'public_path' ];
        subParsedRequest[ 'application' ] = parsedRequest[ 'application' ];
        if ( segments.length > 0 ) {
            subParsedRequest[ 'private_path' ] = segments.join("/");
        }
        else {
            subParsedRequest[ 'private_path' ] = undefined;
        }
        subParsedRequest[ 'instance' ] = parsedRequest[ 'instance' ];
        if ( subParsedRequest[ 'instance' ] ) {
            return global.application.Serve( request, response, subParsedRequest );
        }
        else if ( request.method == "GET" ) {
            var redirectPath = parsedRequest[ 'public_path' ];
            if ( parsedRequest[ 'application' ] ) {
                redirectPath = helpers.JoinPath( redirectPath, parsedRequest[ 'application' ] );
            }
            redirectPath = helpers.JoinPath( redirectPath, helpers.GenerateInstanceID( ), parsedRequest[ 'private_path' ] );
            if ( segments.length == 0 ) {
                redirectPath = helpers.JoinPath( redirectPath, "/");
            }
            serve.Redirect( redirectPath, response );
            return true;
        }
    }
    return false;
}

function GenerateSaveObject( request, public_path, application, instance, save_name ) {
    var result = {};
    result[ "name" ] = save_name;
    result[ "url" ] = helpers.JoinPath( "http://" + request.headers.host, public_path, application, instance, "saves", save_name );
    result[ "vwf_info" ] = {};
    result[ "vwf_info" ][ "public_path" ] = public_path;
    result[ "vwf_info" ][ "application" ] = application;
    result[ "vwf_info" ][ "path_to_application" ] = helpers.JoinPath( public_path, application );
    result[ "vwf_info" ][ "instance" ] = instance;
    var metadata = storage.GetSaveMetadata( public_path, application, instance, save_name );
    if ( metadata ) {
        result[ "metadata" ] = metadata;
    }
    else {
        result[ "metadata" ] = {}
    }
    return result;
    
}

function HandleSavesLoad( request, response, parsedRequest, segments ) {
    // Saves are listed/loaded via GET, and saved via POST
    if ( request.method == "GET" ) {
        //Dealing with either loading a save state, or listing save states,
        // or a malformed request.
        if ( segments.length == 1 ) {
            //No arguments beyond saves, must be listing save states.
            // If instance was set, listing saves for that instance,
            // otherwise listing saves for all instances of this application.
            if ( parsedRequest[ 'instance' ] ) {
                //Listing for specific instance.
                var save_name_list = storage.ListInstanceSaveStates( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ], parsedRequest[ 'instance' ] );
                var save_objects = [ ];
                for ( var index = 0; index < save_name_list.length; index++ ) {
                    save_objects.push( GenerateSaveObject( request, parsedRequest[ 'public_path' ], parsedRequest[ 'application' ], parsedRequest[ 'instance' ], save_name_list[ index ] ) );
                }
                serve.JSON( save_objects, response, url.parse( request.url, true ) );
                return true;
            }
            else {
                var save_hash = storage.ListApplicationSaveStates( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] );
                var instance_hash = { };
                for ( instance_id in save_hash ) {
                    var save_objects = [ ];
                    for ( var index = 0; index < save_hash[ instance_id ].length; index++ ) {
                        save_objects.push( GenerateSaveObject( request, parsedRequest[ 'public_path' ], parsedRequest[ 'application' ], instance_id, save_hash[ instance_id ][ index ] ) );
                    }
                    instance_hash[ instance_id ] = save_objects;
                }
                serve.JSON( instance_hash, response, url.parse( request.url, true ) );
                return true;
            }
        } else  if ( segements.length == 2 ) {
        }
    } else if ( request.method == "POST" ) {
    }
    return false;
}

function HandlePersistenceSave( request, response, parsedRequest, segments ) {
    if ( segments.length == 2 ) {
        var saveName = segments[ 1 ];
        var saveRevision = new Date().valueOf();
        CreateSaveDirectory( parsedRequest[ 'public_path' ], saveName, saveRevision );
        var persistenceData = "";
        var persistenceLength = 0;
        request.on( 'data', function ( data ) {
            persistenceData += data;
            if ( persistenceData.length > 50000000 ) {
                persistenceData = "";
                response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( );
                request.connection.destroy();
            }
        } );
        request.on( 'end', function( ) {
            if ( persistenceData.length > 0 ) {
                var processedPost = querystring.parse( persistenceData );
                fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState' + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
                fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState_' + saveRevision + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
            }
        } );
        return true;
    }
    return false;
}


// The Serve function takes the nodeJS request, nodeJS response and the parsedRequest, and
// attempts to see if it is a properly formed persistence related request.
// If so, it serves the request, and returns true.
// If it is an incorrectly formed persistence request, or not a persistence request
// at all, then false will be returned. 
function Serve( request, response, parsedRequest ) {
    if ( parsedRequest[ 'private_path' ] ) {
        var segments = helpers.GenerateSegments( parsedRequest[ 'private_path' ] );
        if ( segments.length > 0 ) {
            switch ( segments[ 0 ] ) {
                case "listallsaves":
                    if ( request.method == "GET" ) {
                        var saveInfo = GetSaveInformationRecursive( request, response, "/" );
                        serve.JSON( saveInfo, response, url.parse( request.url, true ) );
                        return true;
                     }
                     return false;
                case "listsaves":
                    if ( request.method == "GET" ) {
                        var saveInfo = GetSaveInformation( request, response, parsedRequest[ 'public_path' ] );
                        serve.JSON( saveInfo, response, url.parse( request.url, true ) );
                        return true;
                    }
                    return false;
                case "listdescendentsaves":
                    if ( request.method == "GET" ) {
                        var saveInfo = GetSaveInformationRecursive( request, response, parsedRequest[ 'public_path' ] );
                        serve.JSON( saveInfo, response, url.parse( request.url, true ) );
                        return true;
                    }
                    return false;
                case "load":
                    return HandlePersistenceLoad( request, response, parsedRequest, segments );
                case "saves":
                    return HandleSavesLoad( request, response, parsedRequest, segments );
                case "save":
                    if ( ( request.method == "POST" ) && ( segments.length > 1 ) ) {
                        return HandlePersistenceSave( request, response, parsedRequest, segments );
                    }
            }
        }
    }
    return false;
}

exports.GetLoadInformation = GetLoadInformation;
exports.Serve = Serve;
exports.LoadSaveObject = LoadSaveObject;