Source: Game.js

/**globals Promise, cordova **/
/**
 * Game module needs cordova-plugin-file cordova-plugin-file-transfer
 * @module src/modules/Game
 * @type {Object}
 * @requires ./Utils.js,./File.js
 */
(function(fileModule, Utils, _modules){
    "use strict";

    var Logger = Utils.Logger,
        composeApiString = Utils.composeApiString,
        //Iterator = Utils.Iterator,
        //getJSON = Utils.getJSON,
        jsonpRequest = Utils.jsonpRequest,
        extend = Utils.extend;

    var baseDir,
        cacheDir,
        tempDirectory,
        constants = {},
        wwwDir,
        dataDir,
        stargatejsDir,
        CONF = {
            sdk_url:"",
            dixie_url:"",
            api:"",
            ga_for_game_url:"",
            gamifive_info_api:"",
            bundle_games:[]
        },
        downloading = false;

    var emptyOfflineData = {
        GaForGame: {},
        GamifiveInfo: {},
        queues: {}
    };

    var ga_for_games_qs = {
        print_json_response:1
    };

    var obj = {
        "content_id":"", // to fill
        "formats":"html5applications",
        "sort":"-born_date",
        "category":"b940b384ff0565b06dde433e05dc3c93",
        "publisher":"",
        "size":6,
        "offset":0,
        "label":"",
        "label_slug":"",
        "access_type":"",
        "real_customer_id":"xx_gameasy",
        "lang":"en",
        "use_cs_id":"",
        "white_label":"xx_gameasy",
        "main_domain":"http://www2.gameasy.com/ww",
        "fw":"gameasy",
        "vh":"ww.gameasy.com",
        "check_compatibility_header":0
    };

    var LOG = new Logger("ALL", "[Game - module]", {background:"black",color:"#5aa73a"});

    /**
     * @constructor
     * @alias module:src/modules/Game
     * @example
     *
     * var sgConf = modules: ["game"],
     *     modules_conf: {
     *           "game": {
     *               "bundle_games": [
     *                   "<content_id>",
     *                   "<content_id>"
     *               ]
     *           }
     *       };
     *
     * var afterSgInit = Stargate.initialize(sgConf);
     * afterSgInit
     * .then(function(){
     *      return Stargate.game.download(gameObject, {onStart:function(ev){}, onEnd:function(ev){}, onProgress:function(ev){}})
     * })
     * .then(function(gameID){
     *      Stargate.game.play(gameID);
     * });
     * */
     function Game(){}

    /**
     * Init must be called after the 'deviceready' event
     *
     * @param {Object} customConf - the configuration
     * @param {String} customConf.sdk_url
     * @param {String} customConf.dixie_url
     * @param {String} customConf.api
     * @param {String} customConf.ga_for_game_url
     * @param {String} customConf.gamifive_info_api
     * @param {Array} customConf.bundle_games
     * @returns {Promise<Array<boolean>>}
     * */
     function initialize(customConf){

        if(customConf){
            CONF = extend(CONF, customConf);
        }
        LOG.d("Initialized called with:", CONF);

        if(!fileModule){return Promise.reject("Missing file module!");}

        try{
            baseDir = window.cordova.file.applicationStorageDirectory;
            cacheDir = window.cordova.file.cacheDirectory;
            tempDirectory = window.cordova.file.tempDirectory;
            wwwDir = window.cordova.file.applicationDirectory + "www/";
            stargatejsDir = window.cordova.file.applicationDirectory + "www/js/stargate.js";
            dataDir = window.cordova.file.dataDirectory;
        }catch(reason){
            LOG.e(reason);
            return Promise.reject(reason);
        }


        /**
         * Putting games under Documents r/w. ApplicationStorage is read only
         * on android ApplicationStorage is r/w
         */
        if(window.device.platform.toLowerCase() == "ios"){baseDir += "Documents/";}
        if(window.device.platform.toLowerCase() == "android"){tempDirectory = cacheDir;}

        constants.SDK_DIR = baseDir + "scripts/";
        constants.SDK_RELATIVE_DIR = "../../scripts/";
        constants.GAMEOVER_RELATIVE_DIR = "../../gameover_template/";        
        constants.GAMES_DIR = baseDir + "games/";
        constants.BASE_DIR = baseDir;
        constants.CACHE_DIR = cacheDir;
        constants.TEMP_DIR = tempDirectory;
        constants.CORDOVAJS = wwwDir + "cordova.js";
        constants.CORDOVA_PLUGINS_JS = wwwDir + "cordova_plugins.js";
        constants.STARGATEJS = wwwDir + "js/stargate.js";
        constants.DATA_DIR = dataDir;
        constants.GAMEOVER_DIR = constants.BASE_DIR + "gameover_template/";
        constants.WWW_DIR = wwwDir;

        LOG.i("cordova JS dir to include", constants.CORDOVAJS);

        /** expose */
        _modules.game._public.BASE_DIR = constants.BASE_DIR;
        _modules.game._public.OFFLINE_INDEX = constants.WWW_DIR + "index.html";


        /**
         * Create directories
         * */
        var gamesDirTask = fileModule.createDir(constants.BASE_DIR, "games");
        var scriptsDirTask = fileModule.createDir(constants.BASE_DIR, "scripts");
        var createOfflineDataTask = fileModule.fileExists(constants.BASE_DIR + "offlineData.json")
            .then(function(exists){
                if(!exists){
                    LOG.i("creating offlineData.json");
                    return fileModule.createFile(constants.BASE_DIR, "offlineData.json")
                        .then(function(entry){
                            LOG.d("offlineData", entry);
                            return fileModule.write(entry.path, JSON.stringify(emptyOfflineData));
                        });
                }else{
                    LOG.i("offlineData.json already exists");
                    return exists;
                }
            });

        return Promise.all([
                gamesDirTask,
                scriptsDirTask,
                createOfflineDataTask
            ]).then(function(results){
                LOG.d("GamesDir, ScriptsDir, offlineData.json created", results);
                return copyAssets();
            }).then(getSDK);
    }

    function copyAssets(){
        return Promise.all([
            fileModule.dirExists(constants.BASE_DIR + "gameover_template"),
            fileModule.dirExists(constants.SDK_DIR + "plugins"),
            fileModule.fileExists(constants.SDK_DIR + "cordova.js"),
            fileModule.fileExists(constants.SDK_DIR + "cordova_plugins.js"),
            fileModule.fileExists(constants.SDK_DIR + "stargate.js"),
            fileModule.fileExists(constants.SDK_DIR + "gamesFixes.js")
        ]).then(function(results){
            var all = [];
            if(!results[0]){
                all.push(fileModule.copyDir(constants.WWW_DIR + "gameover_template", constants.BASE_DIR + "gameover_template"));
            }

            if(!results[1]){
                all.push(fileModule.copyDir(constants.WWW_DIR + "plugins", constants.SDK_DIR + "plugins"));
            }

            if(!results[2]){
                all.push(fileModule.copyFile(constants.CORDOVAJS, constants.SDK_DIR + "cordova.js"));
            }

            if(!results[3]){
                all.push(fileModule.copyFile(constants.CORDOVA_PLUGINS_JS, constants.SDK_DIR + "cordova_plugins.js"));
            }

            if(!results[4]){
                all.push(fileModule.copyFile(constants.STARGATEJS, constants.SDK_DIR + "stargate.js"));
            }

            if(!results[5]){
                all.push(fileModule.copyFile(constants.WWW_DIR + "js/gamesFixes.js", constants.SDK_DIR + "gamesFixes.js"));
            }
            return Promise.all(all);
        });
    }

    function getSDK(){

        return Promise.all([
            fileModule.fileExists(constants.SDK_DIR + "dixie.js"),
            fileModule.fileExists(constants.SDK_DIR + "gfsdk.min.js")
        ]).then(function(results){
            var isDixieDownloaded = results[0],
                isSdkDownloaded = results[1],
                tasks = [];

            if(!isSdkDownloaded && CONF.sdk_url !== ""){
                LOG.d("get SDK");
                tasks.push(new fileModule.download(CONF.sdk_url, constants.SDK_DIR, "gfsdk.min.js").promise);
            }

            if(!isDixieDownloaded && CONF.dixie_url !== ""){
                LOG.d("get dixie");
                tasks.push(new fileModule.download(CONF.dixie_url, constants.SDK_DIR, "dixie.js").promise);
            }
            return Promise.all(tasks);
        });
    }

    /**
     * download the game and unzip it
     *
     * @param {object} gameObject - The gameObject with the url of the html5game's zip
     * @param {object} [callbacks={}] - an object with start-end-progress callbacks
     * @param [callbacks.onProgress=function(){}] - a progress function filled with the percentage
     * @param [callbacks.onStart=function(){}] - called on on start
     * @param [callbacks.onEnd=function(){}] - called when unzipped is done
     * @returns {Promise<boolean|FileError|Number>} - true if all has gone good, 403 if unathorized, FileError in case can write in the folder
     * */
    Game.prototype.download = function(gameObject, callbacks){
        var err;
        if(this.isDownloading()){
            err = {type:"error",description:"AlreadyDownloading"};
            callbacks.onEnd(err);
            return Promise.reject(err); 
        }
        
        if((!gameObject.hasOwnProperty("response_api_dld")) || gameObject.response_api_dld.status !== 200){
            err = {type:"error",description:"response_api_dld.status not equal 200 or undefined"};
            callbacks.onEnd(err);
            return Promise.reject(err);
        }

        var alreadyExists = this.isGameDownloaded(gameObject.id);
        var self = this;
        // Defaults
        callbacks = callbacks ? callbacks : {};
        var _onProgress = callbacks.onProgress ? callbacks.onProgress : function(){};
        var _onStart = callbacks.onStart ? callbacks.onStart : function(){};
        var _onEnd = callbacks.onEnd ? callbacks.onEnd : function(){};

        /**
         * Decorate progress function with percentage and type operation
         */
        function wrapProgress(type){
            return function(progressEvent){
                //LOG.d(progressEvent);
                var percentage = Math.round((progressEvent.loaded / progressEvent.total) * 100);
                _onProgress({percentage:percentage,type:type});
            };
        }

        var saveAsName = gameObject.id;
        function start(){
            _onStart({type:"download"});

            LOG.d("Get ga_for_game and gamifive info, fly my minipony!");
            return storeOfflineData(saveAsName)
                .then(function(results){
                    LOG.d("Ga for game and gamifive info stored!", results);
                    LOG.d("Start Download:", gameObject.id, gameObject.response_api_dld.binary_url);
                    return new fileModule.download(gameObject.response_api_dld.binary_url, constants.TEMP_DIR, saveAsName + ".zip", wrapProgress("download")).promise;
                })
                .then(function(entry){
                    //Unpack
                    _onStart({type:"unzip"});
                    LOG.d("unzip:", gameObject.id, constants.TEMP_DIR + saveAsName);
                    return fileModule._promiseZip(entry.path, constants.TEMP_DIR + saveAsName, wrapProgress("unzip"));
                })
                .then(function(result){
                    //Notify on end unzip
                    LOG.d("Unzip ended", result);
                    _onEnd({type:"unzip"});

                    /** check levels of folders before index **/
                    var str = gameObject.response_api_dld.url_download;
                    var folders = str.substring(str.lastIndexOf("game"), str.length).split("/");

                    var src = "";
                    LOG.d("Get the right index folder of the game",folders);

                    // In this case i have another folder before index.html
                    if(folders.length > 2 && isIndexHtml(folders[folders.length - 1])){
                        src = constants.TEMP_DIR + [saveAsName, folders[folders.length - 2]].join("/");
                        LOG.d("More than one level folders before index.html",folders, src);
                    }else{
                        src = constants.TEMP_DIR + saveAsName;
                        LOG.d("One level folder before index.html",folders, src);
                    }

                    LOG.d("Copy game folder in games/", src, constants.GAMES_DIR + saveAsName);                    
                    return fileModule.moveDir(src, constants.GAMES_DIR + saveAsName);                   
                })
                .then(function(result){
                    // Remove the zip in the temp directory
                    LOG.d("Remove zip from:", constants.TEMP_DIR + saveAsName + ".zip", "last operation result", result);
                    return fileModule.removeFile(constants.TEMP_DIR + saveAsName + ".zip");
                })
                .then(function(){
                    //GET COVER IMAGE FOR THE GAME!
                    LOG.d("Save meta.json for:", gameObject.id);
                    var info = {
                        gameId:gameObject.id,
                        size:{width:"240",height:"170",ratio:"1_4"},
                        url:gameObject.images.cover.ratio_1_4,
                        type:"cover",
                        method:"xhr" //!important!
                    };

                    return downloadImage(info);

                })
                .then(function(coverResult){
                    LOG.d("Save meta.json for:", gameObject.id);
                    LOG.d("Download image result", coverResult);

                    /**
                     * Modify gameObject.images.cover.ratio_1_4
                     * it point to the cover image with cdvfile:// protocol
                     * TODO: Build a system for file caching also for webapp
                     * **/
                    gameObject.images.cover.ratio_1_4 = coverResult.internalURL;
                    return fileModule.createFile(constants.GAMES_DIR + saveAsName, "meta.json")
                        .then(function(entry){                            
                            return fileModule.write(entry.path, JSON.stringify(gameObject));
                        });
                })
                .then(function(result){
                    
                    LOG.d("result last operation:save meta.json", result);
                    LOG.d("InjectScripts in game:", gameObject.id, wwwDir);                    
                    return injectScripts(gameObject.id, [
                                constants.SDK_RELATIVE_DIR + "gamesFixes.js",
                                constants.GAMEOVER_RELATIVE_DIR + "gameover.css",
                                constants.SDK_RELATIVE_DIR + "cordova.js",
                                constants.SDK_RELATIVE_DIR + "cordova_plugins.js",
                                constants.SDK_RELATIVE_DIR + "dixie.js",
                                constants.SDK_RELATIVE_DIR + "stargate.js",
                                constants.SDK_RELATIVE_DIR + "gfsdk.min.js"
                            ]);
                }).then(function(results){
                    LOG.d("injectScripts result", results);
                    _onEnd({type:"download"});
                    downloading = false;
                    return gameObject.id;
                }).catch(function(reason){
                    LOG.e(reason, "Cleaning...game not downloaded", gameObject.id);
                    downloading = false;
                    self.remove(gameObject.id);
                    _onEnd({type:"error",description:reason});
                    throw reason;
                });
        }

        return alreadyExists.then(function(exists){
            LOG.d("Exists", exists);
            if(exists){
                downloading = false;
                return Promise.reject({12:"AlreadyExists",gameID:gameObject.id});
            }else{
                downloading = true;
                return start();
            }
        });

    };

    /**
     * play
     *
     * @param {String} gameID - the game path in gamesDir where to look for. Note:the game is launched in the same webview
     * @returns {Promise}
     * */
    Game.prototype.play = function(gameID){
        LOG.d("Play", gameID);
        /*
         * TODO: check if games built with Construct2 has orientation issue
         * attach this to orientationchange in the game index.html
         * if(cr._sizeCanvas) window.cr_sizeCanvas(window.innerWidth, window.innerHeight)
         */
        var gamedir = constants.GAMES_DIR + gameID;
        return fileModule.readDir(gamedir)
            .then(function(entries){

                //Search for an /index.html$/
                return entries.filter(function(entry){
                    var isIndex = new RegExp(/index\.html$/i);
                    return isIndex.test(entry.path);
                });
            })
            .then(function(entry){
                LOG.d(entry);
                var address = entry[0].internalURL + "?hybrid=1";
                if(window.device.platform.toLowerCase() == "ios"){
                    LOG.d("Play ios", address);
                    window.location.href = address;
                }else{
                    LOG.d("Play android", address);
                    //window.location.href = entry[0].path;
                    window.navigator.app.loadUrl(encodeURI(address));
                }
            });
    };

    /**
     * Returns an Array of entries that match /index\.html$/i should be only one in the game directory
     * @private
     * @param {String} gameID
     * @returns {Promise<Array|FileError>}
     * */
    function _getIndexHtmlById(gameID){
        LOG.d("_getIndexHtmlById", constants.GAMES_DIR + gameID);
        return fileModule.readDir(constants.GAMES_DIR + gameID)
            .then(function(entries){
                LOG.d("_getIndexHtmlById readDir", entries);
                return entries.filter(function(entry){
                    var isIndex = new RegExp(/index\.html$/i);
                    return isIndex.test(entry.path);
                });
            });
    }

    /**
     * removeRemoteSDK from game's dom
     *
     * @private
     * @param {Document} dom - the document object
     * @returns {Document} the cleaned document element
     * */
    function _removeRemoteSDK(dom){
        LOG.d("_removeRemoteSDK");
        var scripts = dom.querySelectorAll("script");
        var scriptTagSdk;
        for(var i = 0;i < scripts.length;i++){
            if(scripts[i].src.indexOf("gfsdk") !== -1){
                scriptTagSdk = scripts[i];
                LOG.d("_removeRemoteSDK", scriptTagSdk);
                scriptTagSdk.parentNode.removeChild(scriptTagSdk);
                break;
            }
        }
        return dom;
    }

    /**
     * _injectScriptsInDom
     *
     * @private
     * @param {Document} dom - the document where to inject scripts
     * @param {Array|String} sources - the src tag string or array of strings
     * */
    function _injectScriptsInDom(dom, sources){
        dom = _removeRemoteSDK(dom);
        var _sources = Array.isArray(sources) === false ? [sources] : sources;
        var temp, css;
        LOG.d("injectScripts", _sources);
        // Allow scripts to load from local cdvfile protocol
        // default-src * data: cdvfile://* content://* file:///*;
        var metaTag = document.createElement("meta");
        metaTag.httpEquiv = "Content-Security-Policy";
        metaTag.content = "default-src * " +
            "data: " +
            "content: " +
            "cdvfile: " +
            "file: " +
            "http: " +
            "https: " +
            "gap: " +
            "https://ssl.gstatic.com " +
            "'unsafe-inline' " +
            "'unsafe-eval';" +
            "style-src * cdvfile: http: https: 'unsafe-inline';";
        dom.head.insertBefore(metaTag, dom.getElementsByTagName("meta")[0]);

        /**
         *  Create a script element __root__
         *  in case none script in head is present
         * */
        var root = dom.createElement("script");
        root.id = "__root__";
        dom.head.insertBefore(root, dom.head.firstElementChild);

        var scriptFragment = dom.createDocumentFragment();

        for(var i = 0;i < _sources.length;i++){
            if(_sources[i].endsWith(".css")){
                LOG.d("css inject:",_sources[i]);
                css = dom.createElement("link");
                css.rel = "stylesheet";
                css.href = _sources[i];
                dom.head.insertBefore(css, dom.getElementsByTagName("link")[0]);
            }else{
                temp = dom.createElement("script");
                temp.src = _sources[i];
                scriptFragment.appendChild(temp);
                // insertAfter(temp, root);
            }
        }

        dom.head.insertBefore(scriptFragment, dom.head.getElementsByTagName("script")[0]);
        LOG.d("Cleaned dom:",dom);
        return dom;
    }

    function removeOldGmenu(dom){
        var toRemove = [];
        toRemove.push(dom.querySelector("link[href='/gmenu/frame.css']"));
        toRemove.push(dom.querySelector("iframe#menu"));
        toRemove.push(dom.querySelector("script[src='/gmenu/toggle.js']"));
        var scripts = dom.querySelectorAll("script");

        for(var i = scripts.length - 1;i >= 0; i--){
            if(scripts[i].innerHTML.indexOf("function open") !== -1){
                toRemove.push(scripts[i]);
                //scripts[i].parentNode.removeChild(scripts[i]);
                break;
            }
        }

        for(var j = 0; j < toRemove.length;j++){
            if(toRemove[j]){
                toRemove[j].parentNode.removeChild(toRemove[j]);
            }
        }

        return dom;
    }

    /**
     * injectScripts in game index
     *
     * @private
     * @param {String} gameID
     * @param {Array} sources - array of src'string
     * @returns {Promise<Object|FileError>}
     * */
    function injectScripts(gameID, sources){
        var indexPath;
        return _getIndexHtmlById(gameID)
            .then(function(entry){
                indexPath = entry[0].path;
                LOG.d("injectScripts", indexPath);
                return fileModule.readFileAsHTML(entry[0].path);
            })
            .then(function(dom){
                function appendToHead(element){ dom.head.appendChild(element);}

                var metaTags = dom.body.querySelectorAll("meta");
                var linkTags = dom.body.querySelectorAll("link");
                var styleTags = dom.body.querySelectorAll("style");
                var titleTag = dom.body.querySelectorAll("title");

                metaTags = [].slice.call(metaTags);
                linkTags = [].slice.call(linkTags);
                styleTags = [].slice.call(styleTags);
                titleTag = [].slice.call(titleTag);

                var all = metaTags
                    .concat(linkTags)
                    .concat(styleTags)
                    .concat(titleTag);

                all.map(appendToHead);
                dom.body.innerHTML = dom.body.innerHTML.trim();

                LOG.d("_injectScripts");
                LOG.d(dom);
                return _injectScriptsInDom(dom, sources);
            })
            .then(removeOldGmenu)
            .then(function(dom){
                var attrs = [].slice.call(dom.querySelector("html").attributes);
                var htmlAttributesAsString = attrs.map(function(item){
                    return item.name + '=' + '"' + item.value+'"';
                }).join(" ");

                var finalDocAsString = "<!DOCTYPE html><html " + htmlAttributesAsString + ">" + dom.documentElement.innerHTML + "</html>";
                LOG.d("Serialized dom", finalDocAsString);
                return finalDocAsString;
            })
            .then(function(htmlAsString){
                LOG.d("Write dom:", indexPath, htmlAsString);
                return fileModule.write(indexPath, htmlAsString);
            });
    }

    function isIndexHtml(theString){
        var isIndex = new RegExp(/index\.html$/i);
        return isIndex.test(theString);
    }

    /**
     * remove the game directory
     *
     * @public
     * @param {String} gameID - the game id to delete on filesystem
     * @returns {Promise<Array>}
     * */
    Game.prototype.remove = function(gameID){
        LOG.d("Removing game", gameID);
        var isCached = fileModule.dirExists(constants.CACHE_DIR + gameID + ".zip");
        var isInGameDir = fileModule.dirExists(constants.GAMES_DIR + gameID);
        return Promise.all([isCached, isInGameDir])
            .then(function(results){
                var finalResults = [];
                if(results[0]){
                    LOG.d("Removed in cache", results[0]);
                    finalResults.push(fileModule.removeFile(constants.CACHE_DIR + gameID + ".zip"));
                }

                if(results[1]){
                    LOG.d("Removed", results[1]);
                    finalResults.push(fileModule.removeDir(constants.GAMES_DIR + gameID));
                }

                if(finalResults.length === 0){
                    LOG.i("Nothing to remove", finalResults);
                }
                return finalResults;
            });
    };

    /**
     * isDownloading
     *
     * @public
     * @returns {boolean}
     * */
    Game.prototype.isDownloading = function(){
        return downloading;
    };

    /**
     * abortDownload
     *
     * @public
     * @returns {boolean}
     * */
    Game.prototype.abortDownload = function(){
        if(this.isDownloading()){
            LOG.d("Abort last download");
            if(fileModule.currentFileTransfer){
                fileModule.currentFileTransfer.abort();
                fileModule.currentFileTransfer = null;
            }

            return true;
        }
        LOG.w("There's not a download operation to abort");
        return false;
    };

    /**
     * list
     *
     * @public
     * @returns {Promise<Array>} - Returns an array of metainfo gameObject
     * */
    Game.prototype.list = function(){
        LOG.d("Get games list");
        return fileModule.readDir(constants.GAMES_DIR)
            .then(function(entries){
                var _entries = Array.isArray(entries) ? entries : [entries];
                return _entries.filter(function(entry){
                    //get the <id> folder. Careful: there's / at the end
                    if(entry.isDirectory){
                        return entry;
                    }
                });
            }).then(function(gameEntries){
                var metajsons = gameEntries.map(function(gameEntry){
                    return fileModule.readFileAsJSON(gameEntry.path + "meta.json");
                });

                return Promise.all(metajsons).then(function(results){
                    return results;
                });
            });
    };
    
    /**
     * buildGameOver
     * 
     * @param {Object} datas - the data score, start, duration
     * @param datas.score
     * @param datas.start
     * @param datas.duration
     * @param datas.content_id
     * @returns {Promise} - The promise will be filled with the gameover html {String}     
     */
    Game.prototype.buildGameOver = function(datas){                 
        var metaJsonPath = constants.GAMES_DIR + datas.content_id + "/meta.json";
        /** Check if content_id is here */
        if(!datas.hasOwnProperty("content_id")){ return Promise.reject("Missing content_id key!");}
        
        LOG.d("Read meta.json:", metaJsonPath);
        LOG.d("GAMEOVER_TEMPLATE path", constants.GAMEOVER_DIR + "gameover.html");
        /***
         * if needed
         * return new window.DOMParser().parseFromString(documentAsString, "text/xml").firstChild
         * **/
        return Promise.all([
            fileModule.readFileAsJSON(metaJsonPath),
            fileModule.readFile(constants.GAMEOVER_DIR + "gameover.html")
        ]).then(function(results){
                var htmlString = results[1];
                var metaJson = results[0];
                LOG.i("Meta JSON:", metaJson);
                return htmlString
                    .replace("{{score}}", datas.score)
                    .replace("{{url_share}}", metaJson.url_share)
                    .replace("{{url_cover}}", metaJson.images.cover.ratio_1_4)
                    .replace("{{startpage_url}}", constants.WWW_DIR + "index.html");
        });
    };

    /**
     * isGameDownloaded
     *
     * @param {String} gameID - the id of the game
     * @returns {Promise}
     * */
    Game.prototype.isGameDownloaded = function(gameID){
        return fileModule.dirExists(constants.GAMES_DIR + gameID);
    };

    /**
     * removeAll delete all games and recreate the games folder
     *
     * @returns {Promise}
     * */
    Game.prototype.removeAll = function(){
        return fileModule.removeDir(constants.GAMES_DIR)
            .then(function(result){
                LOG.d("All games deleted!", result);
                return fileModule.createDir(constants.BASE_DIR, "games");
            });
    };

    /**
     * downloadImage
     * Save the image in games/<gameId>/images/<type>/<size.width>x<size.height>.png
     *
     * @param {String} info -
     * @param {String} info.gameId -
     * @param {Object} info.size -
     * @param {String|Number} info.size.width -
     * @param {String|Number} info.size.height -
     * @param {String|Number} info.size.ratio - 1|2|1_5|1_4
     * @param {String} info.url - the url with the [HSIZE] and [WSIZE] in it
     * @param {String} info.type - possible values cover|screenshot|icon
     * @param {String} info.method - possible values "xhr"
     * @returns {Promise<String|FileTransferError>} where string is the cdvfile:// path
     * */
    function downloadImage(info){
        /* info = {
            gameId:"",
            size:{width:"",height:"",ratio:""},
            url:"",
            type:"cover",
            method:"xhr"
        };*/

        //GET COVER IMAGE FOR THE GAME!
        var toDld = info.url
            .replace("[WSIZE]", info.size.width)
            .replace("[HSIZE]", info.size.height)
            .split("?")[0];

        //toDld = "http://lorempixel.com/g/"+info.size.width+"/"+info.size.height+"/";
        //toDld = encodeURI(toDld);

        var gameFolder = constants.GAMES_DIR + info.gameId;
        // var imagesFolder = gameFolder + "/images/" + info.type + "/";
        var imageName = info.type + "_" + info.size.width + "x" + info.size.height + ("_"+info.size.ratio || "") + ".jpeg";
        LOG.d("request Image to", toDld, "coverImageUrl", imageName, "imagesFolder", gameFolder);
        if(info.method === "xhr"){
            return Promise.all([
                    fileModule.createFile(gameFolder, imageName),
                    Utils.getImageRaw({url:toDld})
                ]).then(function(results){
                    var entry = results[0];
                    var blob = results[1];

                    return fileModule.appendToFile(entry.path, blob, true, "image/jpeg");
                });
        }else{
            return new fileModule.download(toDld, gameFolder, imageName, function(){}).promise;
        }
    }

    /**
     * getBundleObjects
     *
     * make the jsonpRequests to get the gameObjects.
     * This method is called only if configuration key "bundle_games" is set with an array of gameIDs
     *
     * @returns {Promise<Array>} the gameObject with response_api_dld key
     * */
    Game.prototype.getBundleGameObjects = function(){
        var self = this;
        if(CONF.bundle_games.length > 0){
            LOG.d("Games bundle in configuration", CONF.bundle_games);
            var whichGameAlreadyHere = CONF.bundle_games.map(function(gameId){
                return self.isGameDownloaded(gameId);
            });

            var filteredToDownload = Promise.all(whichGameAlreadyHere)
                .then(function(results){
                    LOG.d("alreadyDownloaded",results);
                    for(var i = 0;i < results.length;i++){
                        if(results[i]) CONF.bundle_games.splice(i, 1);
                    }
                    return CONF.bundle_games;
                })
                .then(function(bundlesGamesIds){
                    return bundlesGamesIds.join(",");
                });

            var tmpBundleGameObjects;
            return filteredToDownload
                .then(function(bundleGamesIds){

                    obj.content_id = bundleGamesIds;
                    var api_string = composeApiString(CONF.api, obj);
                    LOG.d("Request bundle games meta info:", api_string);

                    return new jsonpRequest(api_string).prom;
                }).then(function(bundleGameObjects){
                    LOG.d("Games bundle response:", bundleGameObjects);
                    tmpBundleGameObjects = bundleGameObjects;
                    var jsonpRequests = bundleGameObjects.map(function(item){
                        return new jsonpRequest(item.url_api_dld).prom;
                    });
                    LOG.d("jsonpRequests",jsonpRequests);
                    return Promise.all(jsonpRequests);
                })
                .then(function(results){
                    LOG.d("RESULTS", results);

                    //extend with the response object
                    for(var i = 0;i < results.length;i++){
                        tmpBundleGameObjects[i].response_api_dld =  results[i];
                    }

                    LOG.d("GameObjects", tmpBundleGameObjects);
                    return tmpBundleGameObjects;
                })
                .catch(function(reason){
                    LOG.e("Games bundle meta fail:", reason);
                });
        }else{
            LOG.w("Bundle_games array is empty!");
            return Promise.reject("bundle_games array is empty!");
        }
    };

    /**
     * needsUpdate
     * checks if there's or not a new version for the game(it makes a call to the api)
     *
     * @param {String} gameId - the gameId
     * @param {Promise<Boolean>}
     * */
    Game.prototype.needsUpdate = function(gameId){
        var oldMd5 = "";
        return fileModule.readFileAsJSON(constants.GAMES_DIR + gameId + "/meta.json")
            .then(function(gameObject){
                oldMd5 = gameObject.response_api_dld.binary_md5;
                return Utils.getJSON(gameObject.url_api_dld);
            })
            .then(function(response){
                if(response.status === 200){
                    return response.binary_md5 !== oldMd5;
                }else{
                    throw new Error("ResponseStatus " + response.status);
                }
            });
    };

    function storeOfflineData(content_id){
        /**
         * Calls for offlineData.json
         * putting GamifiveInfo and GaForGame in this file for each game
         * {
         *  GaForGame:<content_id>:{<ga_for_game>},
         *  GamifiveInfo:<content_id>:{<gamifive_info>},
         *  queues:{}
         * }
         * */
        var apiGaForGames = composeApiString(CONF.ga_for_game_url, ga_for_games_qs);
        var getGaForGamesTask = new jsonpRequest(apiGaForGames).prom;

        return getGaForGamesTask.then(function(ga_for_game){
            LOG.d("apiGaForGames:", apiGaForGames, "ga_for_game:", ga_for_game);

            var gamifive_api = composeApiString(CONF.gamifive_info_api, {
                content_id:content_id,
                _PONY:ga_for_game._PONYVALUE,
                format:"jsonp"
            });

            LOG.d("gamifive_info_api",gamifive_api);
            return [new jsonpRequest(gamifive_api).prom, ga_for_game];

        }).then(function(results){
            return results[0].then(function(gamifive_info){
                LOG.d("gamifiveInfo:", gamifive_info, "ga_for_game", results[1]);
                return updateOfflineData({content_id:content_id, ga_for_game:results[1], gamifive_info:gamifive_info.game_info});
            });
        });
    }

    function updateOfflineData(object){
        return fileModule.readFileAsJSON(constants.BASE_DIR + "offlineData.json")
            .then(function(offlineData){
                offlineData.GaForGame[object.content_id] = object.ga_for_game;
                offlineData.GamifiveInfo[object.content_id] = object.gamifive_info;
                return offlineData;
            })
            .then(function(offlineDataUpdated){
                LOG.d("writing offlineData.json", offlineDataUpdated);
                return fileModule.write(constants.BASE_DIR + "offlineData.json", JSON.stringify(offlineDataUpdated));
            });
    }
    
    var _protected = {};
    _modules.game = {};

    _protected.initialize = initialize;
    _modules.game._protected = _protected;
    _modules.game._public = new Game();

})(stargateModules.file, stargateModules.Utils, stargateModules);