import { Tools } from "../Misc/tools";
import { Logger } from "../Misc/logger";
import { TGATools } from '../Misc/tga';
import { Engine } from "../Engines/engine";
import { WebRequest } from '../Misc/webRequest';
// Sets the default offline provider to Babylon.js
Engine.OfflineProviderFactory = function (urlToScene, callbackManifestChecked, disableManifestCheck) {
    if (disableManifestCheck === void 0) { disableManifestCheck = false; }
    return new Database(urlToScene, callbackManifestChecked, disableManifestCheck);
};
/**
 * Class used to enable access to IndexedDB
 * @see https://doc.babylonjs.com/how_to/caching_resources_in_indexeddb
 */
var Database = /** @class */ (function () {
    /**
     * Creates a new Database
     * @param urlToScene defines the url to load the scene
     * @param callbackManifestChecked defines the callback to use when manifest is checked
     * @param disableManifestCheck defines a boolean indicating that we want to skip the manifest validation (it will be considered validated and up to date)
     */
    function Database(urlToScene, callbackManifestChecked, disableManifestCheck) {
        var _this = this;
        if (disableManifestCheck === void 0) { disableManifestCheck = false; }
        // Handling various flavors of prefixed version of IndexedDB
        this._idbFactory = (typeof window !== "undefined" ? window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB : indexedDB);
        this._callbackManifestChecked = callbackManifestChecked;
        this._currentSceneUrl = Database._ReturnFullUrlLocation(urlToScene);
        this._db = null;
        this._enableSceneOffline = false;
        this._enableTexturesOffline = false;
        this._manifestVersionFound = 0;
        this._mustUpdateRessources = false;
        this._hasReachedQuota = false;
        if (!Database.IDBStorageEnabled) {
            this._callbackManifestChecked(true);
        }
        else {
            if (disableManifestCheck) {
                this._enableSceneOffline = true;
                this._enableTexturesOffline = true;
                this._manifestVersionFound = 1;
                Tools.SetImmediate(function () {
                    _this._callbackManifestChecked(true);
                });
            }
            else {
                this._checkManifestFile();
            }
        }
    }
    Object.defineProperty(Database.prototype, "enableSceneOffline", {
        /**
         * Gets a boolean indicating if scene must be saved in the database
         */
        get: function () {
            return this._enableSceneOffline;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Database.prototype, "enableTexturesOffline", {
        /**
         * Gets a boolean indicating if textures must be saved in the database
         */
        get: function () {
            return this._enableTexturesOffline;
        },
        enumerable: false,
        configurable: true
    });
    Database.prototype._checkManifestFile = function () {
        var _this = this;
        var noManifestFile = function () {
            _this._enableSceneOffline = false;
            _this._enableTexturesOffline = false;
            _this._callbackManifestChecked(false);
        };
        var timeStampUsed = false;
        var manifestURL = this._currentSceneUrl + ".manifest";
        var xhr = new WebRequest();
        if (navigator.onLine) {
            // Adding a timestamp to by-pass browsers' cache
            timeStampUsed = true;
            manifestURL = manifestURL + (manifestURL.match(/\?/) == null ? "?" : "&") + Date.now();
        }
        xhr.open("GET", manifestURL);
        xhr.addEventListener("load", function () {
            if (xhr.status === 200 || Database._ValidateXHRData(xhr, 1)) {
                try {
                    var manifestFile = JSON.parse(xhr.response);
                    _this._enableSceneOffline = manifestFile.enableSceneOffline;
                    _this._enableTexturesOffline = manifestFile.enableTexturesOffline && Database.IsUASupportingBlobStorage;
                    if (manifestFile.version && !isNaN(parseInt(manifestFile.version))) {
                        _this._manifestVersionFound = manifestFile.version;
                    }
                    if (_this._callbackManifestChecked) {
                        _this._callbackManifestChecked(true);
                    }
                }
                catch (ex) {
                    noManifestFile();
                }
            }
            else {
                noManifestFile();
            }
        }, false);
        xhr.addEventListener("error", function () {
            if (timeStampUsed) {
                timeStampUsed = false;
                // Let's retry without the timeStamp
                // It could fail when coupled with HTML5 Offline API
                var retryManifestURL = _this._currentSceneUrl + ".manifest";
                xhr.open("GET", retryManifestURL);
                xhr.send();
            }
            else {
                noManifestFile();
            }
        }, false);
        try {
            xhr.send();
        }
        catch (ex) {
            Logger.Error("Error on XHR send request.");
            this._callbackManifestChecked(false);
        }
    };
    /**
     * Open the database and make it available
     * @param successCallback defines the callback to call on success
     * @param errorCallback defines the callback to call on error
     */
    Database.prototype.open = function (successCallback, errorCallback) {
        var _this = this;
        var handleError = function () {
            _this._isSupported = false;
            if (errorCallback) {
                errorCallback();
            }
        };
        if (!this._idbFactory || !(this._enableSceneOffline || this._enableTexturesOffline)) {
            // Your browser doesn't support IndexedDB
            this._isSupported = false;
            if (errorCallback) {
                errorCallback();
            }
        }
        else {
            // If the DB hasn't been opened or created yet
            if (!this._db) {
                this._hasReachedQuota = false;
                this._isSupported = true;
                var request = this._idbFactory.open("babylonjs", 1);
                // Could occur if user is blocking the quota for the DB and/or doesn't grant access to IndexedDB
                request.onerror = function () {
                    handleError();
                };
                // executes when a version change transaction cannot complete due to other active transactions
                request.onblocked = function () {
                    Logger.Error("IDB request blocked. Please reload the page.");
                    handleError();
                };
                // DB has been opened successfully
                request.onsuccess = function () {
                    _this._db = request.result;
                    successCallback();
                };
                // Initialization of the DB. Creating Scenes & Textures stores
                request.onupgradeneeded = function (event) {
                    _this._db = (event.target).result;
                    if (_this._db) {
                        try {
                            _this._db.createObjectStore("scenes", { keyPath: "sceneUrl" });
                            _this._db.createObjectStore("versions", { keyPath: "sceneUrl" });
                            _this._db.createObjectStore("textures", { keyPath: "textureUrl" });
                        }
                        catch (ex) {
                            Logger.Error("Error while creating object stores. Exception: " + ex.message);
                            handleError();
                        }
                    }
                };
            }
            // DB has already been created and opened
            else {
                if (successCallback) {
                    successCallback();
                }
            }
        }
    };
    /**
     * Loads an image from the database
     * @param url defines the url to load from
     * @param image defines the target DOM image
     */
    Database.prototype.loadImage = function (url, image) {
        var _this = this;
        var completeURL = Database._ReturnFullUrlLocation(url);
        var saveAndLoadImage = function () {
            if (!_this._hasReachedQuota && _this._db !== null) {
                // the texture is not yet in the DB, let's try to save it
                _this._saveImageIntoDBAsync(completeURL, image);
            }
            // If the texture is not in the DB and we've reached the DB quota limit
            // let's load it directly from the web
            else {
                image.src = url;
            }
        };
        if (!this._mustUpdateRessources) {
            this._loadImageFromDBAsync(completeURL, image, saveAndLoadImage);
        }
        // First time we're download the images or update requested in the manifest file by a version change
        else {
            saveAndLoadImage();
        }
    };
    Database.prototype._loadImageFromDBAsync = function (url, image, notInDBCallback) {
        if (this._isSupported && this._db !== null) {
            var texture;
            var transaction = this._db.transaction(["textures"]);
            transaction.onabort = function () {
                image.src = url;
            };
            transaction.oncomplete = function () {
                var blobTextureURL;
                if (texture) {
                    var URL = window.URL || window.webkitURL;
                    blobTextureURL = URL.createObjectURL(texture.data);
                    image.onerror = function () {
                        Logger.Error("Error loading image from blob URL: " + blobTextureURL + " switching back to web url: " + url);
                        image.src = url;
                    };
                    image.src = blobTextureURL;
                }
                else {
                    notInDBCallback();
                }
            };
            var getRequest = transaction.objectStore("textures").get(url);
            getRequest.onsuccess = function (event) {
                texture = (event.target).result;
            };
            getRequest.onerror = function () {
                Logger.Error("Error loading texture " + url + " from DB.");
                image.src = url;
            };
        }
        else {
            Logger.Error("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
            image.src = url;
        }
    };
    Database.prototype._saveImageIntoDBAsync = function (url, image) {
        var _this = this;
        if (this._isSupported) {
            // In case of error (type not supported or quota exceeded), we're at least sending back XHR data to allow texture loading later on
            var generateBlobUrl = function () {
                var blobTextureURL;
                if (blob) {
                    var URL = window.URL || window.webkitURL;
                    try {
                        blobTextureURL = URL.createObjectURL(blob);
                    }
                    // Chrome is raising a type error if we're setting the oneTimeOnly parameter
                    catch (ex) {
                        blobTextureURL = URL.createObjectURL(blob);
                    }
                }
                if (blobTextureURL) {
                    image.src = blobTextureURL;
                }
            };
            if (Database.IsUASupportingBlobStorage) { // Create XHR
                var xhr = new WebRequest(), blob;
                xhr.open("GET", url);
                xhr.responseType = "blob";
                xhr.addEventListener("load", function () {
                    if (xhr.status === 200 && _this._db) {
                        // Blob as response (XHR2)
                        blob = xhr.response;
                        var transaction = _this._db.transaction(["textures"], "readwrite");
                        // the transaction could abort because of a QuotaExceededError error
                        transaction.onabort = function (event) {
                            try {
                                //backwards compatibility with ts 1.0, srcElement doesn't have an "error" according to ts 1.3
                                var srcElement = (event.srcElement || event.target);
                                var error = srcElement.error;
                                if (error && error.name === "QuotaExceededError") {
                                    _this._hasReachedQuota = true;
                                }
                            }
                            catch (ex) { }
                            generateBlobUrl();
                        };
                        transaction.oncomplete = function () {
                            generateBlobUrl();
                        };
                        var newTexture = { textureUrl: url, data: blob };
                        try {
                            // Put the blob into the dabase
                            var addRequest = transaction.objectStore("textures").put(newTexture);
                            addRequest.onsuccess = function () {
                            };
                            addRequest.onerror = function () {
                                generateBlobUrl();
                            };
                        }
                        catch (ex) {
                            // "DataCloneError" generated by Chrome when you try to inject blob into IndexedDB
                            if (ex.code === 25) {
                                Database.IsUASupportingBlobStorage = false;
                                _this._enableTexturesOffline = false;
                            }
                            image.src = url;
                        }
                    }
                    else {
                        image.src = url;
                    }
                }, false);
                xhr.addEventListener("error", function () {
                    Logger.Error("Error in XHR request in BABYLON.Database.");
                    image.src = url;
                }, false);
                xhr.send();
            }
            else {
                image.src = url;
            }
        }
        else {
            Logger.Error("Error: IndexedDB not supported by your browser or Babylon.js database is not open.");
            image.src = url;
        }
    };
    Database.prototype._checkVersionFromDB = function (url, versionLoaded) {
        var _this = this;
        var updateVersion = function () {
            // the version is not yet in the DB or we need to update it
            _this._saveVersionIntoDBAsync(url, versionLoaded);
        };
        this._loadVersionFromDBAsync(url, versionLoaded, updateVersion);
    };
    Database.prototype._loadVersionFromDBAsync = function (url, callback, updateInDBCallback) {
        var _this = this;
        if (this._isSupported && this._db) {
            var version;
            try {
                var transaction = this._db.transaction(["versions"]);
                transaction.oncomplete = function () {
                    if (version) {
                        // If the version in the JSON file is different from the version in DB
                        if (_this._manifestVersionFound !== version.data) {
                            _this._mustUpdateRessources = true;
                            updateInDBCallback();
                        }
                        else {
                            callback(version.data);
                        }
                    }
                    // version was not found in DB
                    else {
                        _this._mustUpdateRessources = true;
                        updateInDBCallback();
                    }
                };
                transaction.onabort = function () {
                    callback(-1);
                };
                var getRequest = transaction.objectStore("versions").get(url);
                getRequest.onsuccess = function (event) {
                    version = (event.target).result;
                };
                getRequest.onerror = function () {
                    Logger.Error("Error loading version for scene " + url + " from DB.");
                    callback(-1);
                };
            }
            catch (ex) {
                Logger.Error("Error while accessing 'versions' object store (READ OP). Exception: " + ex.message);
                callback(-1);
            }
        }
        else {
            Logger.Error("Error: IndexedDB not supported by your browser or Babylon.js database is not open.");
            callback(-1);
        }
    };
    Database.prototype._saveVersionIntoDBAsync = function (url, callback) {
        var _this = this;
        if (this._isSupported && !this._hasReachedQuota && this._db) {
            try {
                // Open a transaction to the database
                var transaction = this._db.transaction(["versions"], "readwrite");
                // the transaction could abort because of a QuotaExceededError error
                transaction.onabort = function (event) {
                    try { //backwards compatibility with ts 1.0, srcElement doesn't have an "error" according to ts 1.3
                        var error = event.srcElement['error'];
                        if (error && error.name === "QuotaExceededError") {
                            _this._hasReachedQuota = true;
                        }
                    }
                    catch (ex) { }
                    callback(-1);
                };
                transaction.oncomplete = function () {
                    callback(_this._manifestVersionFound);
                };
                var newVersion = { sceneUrl: url, data: this._manifestVersionFound };
                // Put the scene into the database
                var addRequest = transaction.objectStore("versions").put(newVersion);
                addRequest.onsuccess = function () {
                };
                addRequest.onerror = function () {
                    Logger.Error("Error in DB add version request in BABYLON.Database.");
                };
            }
            catch (ex) {
                Logger.Error("Error while accessing 'versions' object store (WRITE OP). Exception: " + ex.message);
                callback(-1);
            }
        }
        else {
            callback(-1);
        }
    };
    /**
     * Loads a file from database
     * @param url defines the URL to load from
     * @param sceneLoaded defines a callback to call on success
     * @param progressCallBack defines a callback to call when progress changed
     * @param errorCallback defines a callback to call on error
     * @param useArrayBuffer defines a boolean to use array buffer instead of text string
     */
    Database.prototype.loadFile = function (url, sceneLoaded, progressCallBack, errorCallback, useArrayBuffer) {
        var _this = this;
        var completeUrl = Database._ReturnFullUrlLocation(url);
        var saveAndLoadFile = function () {
            // the scene is not yet in the DB, let's try to save it
            _this._saveFileAsync(completeUrl, sceneLoaded, progressCallBack, useArrayBuffer, errorCallback);
        };
        this._checkVersionFromDB(completeUrl, function (version) {
            if (version !== -1) {
                if (!_this._mustUpdateRessources) {
                    _this._loadFileAsync(completeUrl, sceneLoaded, saveAndLoadFile);
                }
                else {
                    _this._saveFileAsync(completeUrl, sceneLoaded, progressCallBack, useArrayBuffer, errorCallback);
                }
            }
            else {
                if (errorCallback) {
                    errorCallback();
                }
            }
        });
    };
    Database.prototype._loadFileAsync = function (url, callback, notInDBCallback) {
        if (this._isSupported && this._db) {
            var targetStore;
            if (url.indexOf(".babylon") !== -1) {
                targetStore = "scenes";
            }
            else {
                targetStore = "textures";
            }
            var file;
            var transaction = this._db.transaction([targetStore]);
            transaction.oncomplete = function () {
                if (file) {
                    callback(file.data);
                }
                // file was not found in DB
                else {
                    notInDBCallback();
                }
            };
            transaction.onabort = function () {
                notInDBCallback();
            };
            var getRequest = transaction.objectStore(targetStore).get(url);
            getRequest.onsuccess = function (event) {
                file = (event.target).result;
            };
            getRequest.onerror = function () {
                Logger.Error("Error loading file " + url + " from DB.");
                notInDBCallback();
            };
        }
        else {
            Logger.Error("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
            callback();
        }
    };
    Database.prototype._saveFileAsync = function (url, callback, progressCallback, useArrayBuffer, errorCallback) {
        var _this = this;
        if (this._isSupported) {
            var targetStore;
            if (url.indexOf(".babylon") !== -1) {
                targetStore = "scenes";
            }
            else {
                targetStore = "textures";
            }
            // Create XHR
            var xhr = new WebRequest();
            var fileData;
            xhr.open("GET", url + "?" + Date.now());
            if (useArrayBuffer) {
                xhr.responseType = "arraybuffer";
            }
            if (progressCallback) {
                xhr.onprogress = progressCallback;
            }
            xhr.addEventListener("load", function () {
                if (xhr.status === 200 || (xhr.status < 400 && Database._ValidateXHRData(xhr, !useArrayBuffer ? 1 : 6))) {
                    // Blob as response (XHR2)
                    fileData = !useArrayBuffer ? xhr.responseText : xhr.response;
                    if (!_this._hasReachedQuota && _this._db) {
                        // Open a transaction to the database
                        var transaction = _this._db.transaction([targetStore], "readwrite");
                        // the transaction could abort because of a QuotaExceededError error
                        transaction.onabort = function (event) {
                            try {
                                //backwards compatibility with ts 1.0, srcElement doesn't have an "error" according to ts 1.3
                                var error = event.srcElement['error'];
                                if (error && error.name === "QuotaExceededError") {
                                    _this._hasReachedQuota = true;
                                }
                            }
                            catch (ex) { }
                            callback(fileData);
                        };
                        transaction.oncomplete = function () {
                            callback(fileData);
                        };
                        var newFile;
                        if (targetStore === "scenes") {
                            newFile = { sceneUrl: url, data: fileData, version: _this._manifestVersionFound };
                        }
                        else {
                            newFile = { textureUrl: url, data: fileData };
                        }
                        try {
                            // Put the scene into the database
                            var addRequest = transaction.objectStore(targetStore).put(newFile);
                            addRequest.onsuccess = function () {
                            };
                            addRequest.onerror = function () {
                                Logger.Error("Error in DB add file request in BABYLON.Database.");
                            };
                        }
                        catch (ex) {
                            callback(fileData);
                        }
                    }
                    else {
                        callback(fileData);
                    }
                }
                else {
                    if (xhr.status >= 400 && errorCallback) {
                        errorCallback(xhr);
                    }
                    else {
                        callback();
                    }
                }
            }, false);
            xhr.addEventListener("error", function () {
                Logger.Error("error on XHR request.");
                callback();
            }, false);
            xhr.send();
        }
        else {
            Logger.Error("Error: IndexedDB not supported by your browser or Babylon.js database is not open.");
            callback();
        }
    };
    /**
     * Validates if xhr data is correct
     * @param xhr defines the request to validate
     * @param dataType defines the expected data type
     * @returns true if data is correct
     */
    Database._ValidateXHRData = function (xhr, dataType) {
        // 1 for text (.babylon, manifest and shaders), 2 for TGA, 4 for DDS, 7 for all
        if (dataType === void 0) { dataType = 7; }
        try {
            if (dataType & 1) {
                if (xhr.responseText && xhr.responseText.length > 0) {
                    return true;
                }
                else if (dataType === 1) {
                    return false;
                }
            }
            if (dataType & 2) {
                // Check header width and height since there is no "TGA" magic number
                var tgaHeader = TGATools.GetTGAHeader(xhr.response);
                if (tgaHeader.width && tgaHeader.height && tgaHeader.width > 0 && tgaHeader.height > 0) {
                    return true;
                }
                else if (dataType === 2) {
                    return false;
                }
            }
            if (dataType & 4) {
                // Check for the "DDS" magic number
                var ddsHeader = new Uint8Array(xhr.response, 0, 3);
                if (ddsHeader[0] === 68 && ddsHeader[1] === 68 && ddsHeader[2] === 83) {
                    return true;
                }
                else {
                    return false;
                }
            }
        }
        catch (e) {
            // Global protection
        }
        return false;
    };
    /** Gets a boolean indicating if the user agent supports blob storage (this value will be updated after creating the first Database object) */
    Database.IsUASupportingBlobStorage = true;
    /**
     * Gets a boolean indicating if Database storate is enabled (off by default)
     */
    Database.IDBStorageEnabled = false;
    Database._ParseURL = function (url) {
        var a = document.createElement('a');
        a.href = url;
        var urlWithoutHash = url.substring(0, url.lastIndexOf("#"));
        var fileName = url.substring(urlWithoutHash.lastIndexOf("/") + 1, url.length);
        var absLocation = url.substring(0, url.indexOf(fileName, 0));
        return absLocation;
    };
    Database._ReturnFullUrlLocation = function (url) {
        if (url.indexOf("http:/") === -1 && url.indexOf("https:/") === -1 && typeof window !== "undefined") {
            return (Database._ParseURL(window.location.href) + url);
        }
        else {
            return url;
        }
    };
    return Database;
}());
export { Database };
//# sourceMappingURL=database.js.map