/**
 * Key/Value store for files. Files stored using this API are encrypted automatically 
 * using Sencha Web Application Manager's security infrastructure. 
 *
 *      var files = Ext.space.SecureFiles.get('secrets');
 *
 *      files.get('myKey').then(function(contents){
 *          // do something with the content of the file.
 *      });
 *
 * files is an instance of Ext.space.files.Collection. See Ext.space.files.Collection for 
 * a complete list of file operations.
 *
 * This module also allows you to run queries across all of an application's Collections:
 *
 *      Ext.space.SecureFiles.query({ name: "*.txt" }).then(function(files) {
 *          // got 'em
 *      });
 * 
 * @aside guide secure_file_api
 *  
 */
Ext.define("Ext.space.SecureFiles", {
    singleton: true,
 
    /**
     * @private
     * @type {Object}
     */
    collections: null,
 
    /**
     * @private
     * @type {Array}
     */
    dummyCollections: null,
 
    /**
     * @private
     */
    constructor: function() {
        this.collections = {};
        this.dummyCollections = [];
    },
 
    /**
     * Create a function that caches query results in a dummy collection.
     *
     * @private
     * @return {Function} Function that takes a query result and caches it in a dummy
     *                    collection (necessary to run queries without collections
     *                    and still allow various file operations to work correctly)
     */
    _makeDummyCacher: function() {
        var collection = new Ext.space.files.Collection();
        this.dummyCollections.push(collection);
        return collection._cache.bind(collection);
    },
    
    /**
     * Remove a file from any loaded collections.
     *
     * When a file gets overwritten by some operation, the object in memory needs to
     * be removed from any collections we've loaded; this function traverses through
     * every collection this module is tracking and does just that.
     *
     * @private
     * @param {String} key File key
     */
    _removeFileFromLoadedCollections: function(key) {
        var collections = this.collections, k = key.toString();
 
        Object.keys(collections).forEach(function(path) {
            if (collections[path].files[k]) {
                delete collections[path].files[k];
            }
        });
 
        this.dummyCollections.forEach(function(collection) {
            if (collection.files[k]) {
                delete collection.files[k];
            }
        });
    },
 
    /**
     * Get a collection by name. Collections are automatically created if they do not
     * exist, and multiple requests for collections with the same name will all
     * return the same collection object.
     *
     * @param {String} name The name of the collection to get.
     * @return {Ext.space.files.Collection} the secure collection.
     */
    get: function(name) {
        if (!this.collections.hasOwnProperty(name)) {
            this.collections[name] = new Ext.space.files.Collection(name);
        }
        return this.collections[name];
    },
 
    /**
    * Create a file from its file Key.
    *  The collection the file belongs to is updated
    *  If the File's Collection is not in memory then it is automatically created. 
    *
    * @param {String} key The key of the file on the file system.
    *
    *
    *@private
    */
    getFile: function(key){
        var _self = this;
        var result = new Ext.space.Promise();
         Ext.space.Communicator.send({
            command: "Files#getFile",
            key: key,
            callbacks: {
                onSuccess: function(meta) {
                    var collection = _self.get(meta.path);
                    var file = collection._cache(meta);
                    result.fulfill(file);
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        });
        return result;
    },
 
    /**
     * Query the application's file system for files that match the given criteria.
     *
     * The `query` is a dictionary with data against which to match files. The fields
     * supported are:
     *
     * * `name`: "exactName.txt", "*.txt", etc...
     * * `type`: MIME type ("text/plain", etc...)
     * * `createdBefore`: Date object
     * * `createdAfter`: Date object
     * * `modifiedBefore`: Date object
     * * `modifiedAfter`: Date object
     *
     * The query will combine the criteria specified and produce an array of matching
     * files. If you omit the query completely, the query operation will return an
     * array of all files in the collection.
     *
     * The `options` is a dictionary describing how you want the results to be presented:
     *
     * * `fetch`: "count" or "data" (the default), to return a simple count, or the
     *   complete results, respectively
     * * `sortField`: name of the field on which to sort
     * * `sortDirection`: "asc" or "desc"
     *
     * @param {Object} query (optional) Query object
     * @param {String} query.name (optional) exactName.txt", "*.txt", etc...
     * @param {String} query.type MIME type ("text/plain", etc...)
     * @param {Date} query.createdBefore (optional) Date object
     * @param {Date} query.createdAfter (optional) Date object
     * @param {Date} query.modifiedBefore (optional) Date object
     * @param {Date} query.modifiedAfter (optional) Date object
     * @param {String} options (optional) modifies how  the results will be returned
     * @param {String} options.fetch  (optional) "count" or "data" (the default), to return a simple count, or the complete results, respectively
     * @param {String} options.sortField  (optional)  name of the field on which to sort
     * @param {String} options.sortDirection  (optional)  "asc" or "desc"
     * @return {Ext.space.Promise} Promise that resolves with the Ext.space.files.File
     *                       objects that match the criteria.
     */
    query: function(query, options) {
        var result = new Ext.space.Promise();
        var qry = {path: "*"}; // default to querying the app's entire file system 
 
        // fetching just a count of results, or the results themselves? 
        var fetch = (options && options.fetch && options.fetch.toLowerCase() === "count") ? "count" : "data";
 
        if (query) {
            // copy the rest of the query as is 
            if (query.name) { qry.name = query.name; }
            if (query.type) { qry.type = query.type; }
            if (query.hasOwnProperty("path")) { qry.path = query.path; }
 
            // convert Date objects to epoch seconds 
            if (query.createdBefore) { qry.createdBefore = query.createdBefore.getTime() / 1000; }
            if (query.createdAfter) { qry.createdAfter = query.createdAfter.getTime() / 1000; }
            if (query.modifiedBefore) { qry.modifiedBefore = query.modifiedBefore.getTime() / 1000; }
            if (query.modifiedAfter) { qry.modifiedAfter = query.modifiedAfter.getTime() / 1000; }
        }
 
        var args = {
            command: "Files#queryFiles",
            query: qry,
            fetch: fetch,
            callbacks: {
                onSuccess: function(matches) {
                    var cacheResult;
 
                    // if we're fetching a count, return it directly; if we're fetching 
                    // data on behalf of a collection, return it directly as well, to 
                    // allow the collection to decide how to process it; if we're 
                    // querying across all of an application's collections, then put 
                    // things in the dummy collection and return what gets stored. 
                    if (fetch === "count" || (options && options.collection)) {
                        result.fulfill(matches);
                    } else {
                        cacheResult = Ext.space.SecureFiles._makeDummyCacher();
                        result.fulfill(matches.map(function(match) {
                            return cacheResult(match);
                        }));
                    }
                },
                onError: function(error) {
                    result.reject(error);
                }
            }
        };
 
        if (options) {
            if (options.sortField) { args.sortField = options.sortField; }
            if (options.sortDirection) { args.sortDirection = options.sortDirection.toLowerCase(); }
        }
 
        Ext.space.Communicator.send(args);
 
        return result;
    },
 
    /**
     * Compress the provided files into an archive.
     *
     *      Ext.space.SecureFiles.compress({
     *          files: arrayOfFileObjects,
     *          archiveName: "somefiles.zip"
     *      }).then(function(file) {
     *          // do something with the archive file
     *      });
     *
     *      // or specify more options:
     *      Ext.space.SecureFiles.compress({
     *          files: arrayOfFileObjects,
     *          archiveName: "somefiles.blob",
     *          path: "myArchivePath",
     *          type: "zip"
     *      }).then(function(file) {
     *          // do something with the archive file
     *      });
     *
     * @param {Object} args Options object
     * @param {Array}  args.files Array of Ext.space.files.File objects to compress into an archive
     * @param {String} args.archiveName Name of the archive file to create
     * @param {String} args.path (optional) Path into which to save the archive; defaults to ""
     * @param {String} args.type (optional) Compression type ("zip", "zip", "bzip2", "rar");
     *                      if this is omitted, the system will attempt to determine
     *                      the compression type from the archiveName, and if it
     *                      cannot be determined, defaults to "zip".
     * @return {Ext.space.Promise} Promise that resolves with the Ext.space.files.File
     *                       object for the new archive.
     */
    compress: function(args) {
        var result = new Ext.space.Promise();
 
        if (!args) {
            result.reject("Missing compression arguments");
 
        } else if (!args.files || args.files.length === 0) {
            result.reject("Missing files; cannot create a compressed archive");
 
        } else if (!args.archiveName) {
            result.reject("Missing compressed archive name");
 
        } else {
            var getEncoding = this._getCompressionEncodingFromName.bind(this);
            var cmd = {
                command: "Compression#compress",
                keys: args.files.map(function(file) { return file.key; }),
                archiveName: args.archiveName,
                path: args.path || "",
                encoding: args.type || getEncoding(args.archiveName) || "zip",
                callbacks: {
                    onSuccess: function(meta) {
                        result.fulfill(Ext.space.SecureFiles.get(meta.path)._cache(meta));
                    },
                    onError: function(error) {
                        result.reject(error);
                    }
                }
            };
            Ext.space.Communicator.send(cmd);
        }
 
        return result;
    },
 
    /**
     * Parse the most likely compression encoding from the given filename.
     *
     * @private
     * @param {String} name File name to parse
     * @return {String} Compression encoding, or "" if unknown
     */
    _getCompressionEncodingFromName: function(name) {
        var idx = name.lastIndexOf(".");
        var ext = name.substr(idx+1).toLowerCase();
        if (idx >= 0) {
            if (ext == "gz" || ext == "gzip") {
                return "gzip";
            } else if (ext == "zip") {
                return "zip";
            } else if (ext == "rar") {
                return "rar";
            } else if (ext == "bz2" || ext == "bzip2") {
                return "bzip2";
            }
        }
 
        return "";
    }
 
});