/**
 * @author Tony Parisi / http://www.tonyparisi.com/
 */

THREE.GLTFLoaderUtils = Object.create(Object, {

    // errors
    INVALID_PATH_ERROR: { value: "INVALID_PATH" },
    INVALID_TYPE_ERROR: { value: "INVALID_TYPE" },
    XMLHTTPREQUEST_STATUS_ERROR: { value: "XMLHTTPREQUEST_STATUS_ERROR" },
    // misc constants
    ARRAY_BUFFER: { value: "ArrayBuffer" },
    // webgl constants (from https://www.opengl.org/registry/api/GL/glcorearb.h)
    WEBGL_CONSTANTS: {
        value: {
            FLOAT: 5126,
            FLOAT_MAT2: 35674,
            FLOAT_MAT3: 35675,
            FLOAT_MAT4: 35676,
            FLOAT_VEC2: 35664,
            FLOAT_VEC3: 35665,
            FLOAT_VEC4: 35666,
            LINEAR: 9729,
            REPEAT: 10497,
            SAMPLER_2D: 35678,
            TRIANGLES: 4,
            UNSIGNED_BYTE: 5121,
            UNSIGNED_SHORT: 5123
        }
    },

    _streams : { value:{}, writable: true },

    _streamsStatus: { value: {}, writable: true },

    _resources: { value: {}, writable: true },

    _resourcesStatus: { value: {}, writable: true },

    // initialization
    init: {
        value: function() {
	        this._streams = {};
	        this._streamsStatus = {};
            this._resources = {};
            this._resourcesStatus = {};
        }
    },

    //manage entries
    _containsResource: {
        enumerable: false,
        value: function(resourceID) {
            return this._resources[resourceID] ? true : false;
        }
    },

    _storeResource: {
        enumerable: false,
        value: function(resourceID, resource) {
            if (!resourceID) {
                console.log("ERROR: entry does not contain id, cannot store");
                return;
            }

            if (this._containsResource[resourceID]) {
                console.log("WARNING: resource:"+resourceID+" is already stored, overriding");
            }

           this._resources[resourceID] = resource;
        }
    },

    _getResource: {
        enumerable: false,
        value: function(resourceID) {
            return this._resources[resourceID];
        }
    },

    _loadStream: {
        value: function(path, type, delegate) {



            var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;

            function decodeDataUriText(isBase64, data) {
                var result = decodeURIComponent(data);
                if (isBase64) {
                    return atob(result);
                }
                return result;
            }

            function decodeDataUriArrayBuffer(isBase64, data) {
                var byteString = decodeDataUriText(isBase64, data);
                var buffer = new ArrayBuffer(byteString.length);
                var view = new Uint8Array(buffer);
                for (var i = 0; i < byteString.length; i++) {
                    view[i] = byteString.charCodeAt(i);
                }
                return buffer;
            }

            function decodeDataUri(dataUriRegexResult, responseType) {
                responseType = typeof responseType !== 'undefined' ? responseType : '';
                var mimeType = dataUriRegexResult[1];
                var isBase64 = !!dataUriRegexResult[2];
                var data = dataUriRegexResult[3];

                switch (responseType) {
                case '':
                case 'text':
                    return decodeDataUriText(isBase64, data);
                case 'ArrayBuffer':
                    return decodeDataUriArrayBuffer(isBase64, data);
                case 'blob':
                    var buffer = decodeDataUriArrayBuffer(isBase64, data);
                    return new Blob([buffer], {
                        type : mimeType
                    });
                case 'document':
                    var parser = new DOMParser();
                    return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
                case 'json':
                    return JSON.parse(decodeDataUriText(isBase64, data));
                default:
                    throw 'Unhandled responseType: ' + responseType;
                }
            }

            var dataUriRegexResult = dataUriRegex.exec(path);
            if (dataUriRegexResult !== null) {
                delegate.streamAvailable(path, decodeDataUri(dataUriRegexResult, type));
                return;
            }

            var self = this;

            if (!type) {
                delegate.handleError(THREE.GLTFLoaderUtils.INVALID_TYPE_ERROR, null);
                return;
            }

            if (!path) {
                delegate.handleError(THREE.GLTFLoaderUtils.INVALID_PATH_ERROR);
                return;
            }

            var xhr = new XMLHttpRequest();
            xhr.open('GET', path, true);
            xhr.responseType = (type === THREE.GLTFLoaderUtils.ARRAY_BUFFER) ? "arraybuffer" : "text";

            //if this is not specified, 1 "big blob" scenes fails to load.
            xhr.setRequestHeader("If-Modified-Since", "Sat, 01 Jan 1970 00:00:00 GMT");
            xhr.onload = function(e) {
                if ((xhr.status == 200) || (xhr.status == 206)) {

                    delegate.streamAvailable(path, xhr.response);

                } else {
                    delegate.handleError(THREE.GLTFLoaderUtils.XMLHTTPREQUEST_STATUS_ERROR, this.status);
                }
            };
            xhr.send(null);
        }
    },

    send: { value: 0, writable: true },
    requested: { value: 0, writable: true },

    _handleRequest: {
        value: function(request) {
            var resourceStatus = this._resourcesStatus[request.id];
            if (resourceStatus)
            {
            	this._resourcesStatus[request.id]++;
            }
            else
            {
            	this._resourcesStatus[request.id] = 1;
            }

            var streamStatus = this._streamsStatus[request.uri];
            if (streamStatus && streamStatus.status === "loading" )
            {
            	streamStatus.requests.push(request);
                return;
            }

            this._streamsStatus[request.uri] = { status : "loading", requests : [request] };

            var self = this;
            var processResourceDelegate = {};

            processResourceDelegate.streamAvailable = function(path, res_) {
            	var streamStatus = self._streamsStatus[path];
            	var requests = streamStatus.requests;
                requests.forEach( function(req_) {
                    var subArray = res_.slice(req_.range[0], req_.range[1]);
                    var convertedResource = req_.delegate.convert(subArray, req_.ctx);
                    self._storeResource(req_.id, convertedResource);
                    req_.delegate.resourceAvailable(convertedResource, req_.ctx);
                    --self._resourcesStatus[req_.id];

                }, this);

                delete self._streamsStatus[path];

            };

            processResourceDelegate.handleError = function(errorCode, info) {
                request.delegate.handleError(errorCode, info);
            }

            this._loadStream(request.uri, request.type, processResourceDelegate);
        }
    },


    _elementSizeForGLType: {
        value: function(componentType, type) {

    		var nElements = 0;
    		switch(type) {
	            case "SCALAR" :
	                nElements = 1;
	                break;
	            case "VEC2" :
	                nElements = 2;
	                break;
	            case "VEC3" :
	                nElements = 3;
	                break;
	            case "VEC4" :
	                nElements = 4;
	                break;
	            case "MAT2" :
	                nElements = 4;
	                break;
	            case "MAT3" :
	                nElements = 9;
	                break;
	            case "MAT4" :
	                nElements = 16;
	                break;
	            default :
	            	debugger;
	            	break;
    		}

            switch (componentType) {
                case THREE.GLTFLoaderUtils.WEBGL_CONSTANTS.FLOAT :
                    return Float32Array.BYTES_PER_ELEMENT * nElements;
                case THREE.GLTFLoaderUtils.WEBGL_CONSTANTS.UNSIGNED_BYTE :
                    return Uint8Array.BYTES_PER_ELEMENT * nElements;
                case THREE.GLTFLoaderUtils.WEBGL_CONSTANTS.UNSIGNED_SHORT :
                    return Uint16Array.BYTES_PER_ELEMENT * nElements;
                default :
                	debugger;
                    return null;
            }
        }
    },

    _handleWrappedBufferViewResourceLoading: {
        value: function(wrappedBufferView, delegate, ctx) {
            var bufferView = wrappedBufferView.bufferView;
            var buffer = bufferView.buffer;
            var byteOffset = wrappedBufferView.byteOffset + bufferView.description.byteOffset;
            var range = [byteOffset , (this._elementSizeForGLType(wrappedBufferView.componentType, wrappedBufferView.type) * wrappedBufferView.count) + byteOffset];

            this._handleRequest({   "id" : wrappedBufferView.id,
                                    "range" : range,
                                    "type" : buffer.description.type,
                                    "uri" : buffer.description.uri,
                                    "delegate" : delegate,
                                    "ctx" : ctx }, null);
        }
    },

    getBuffer: {

            value: function(wrappedBufferView, delegate, ctx) {

            var savedBuffer = this._getResource(wrappedBufferView.id);
            if (false) { // savedBuffer) {
                return savedBuffer;
            } else {
                this._handleWrappedBufferViewResourceLoading(wrappedBufferView, delegate, ctx);
            }

            return null;
        }
    },

    getFile: {

        value: function(request, delegate, ctx) {

    		request.delegate = delegate;
    		request.ctx = ctx;

            this._handleRequest({   "id" : request.id,
                "uri" : request.uri,
                "range" : [0],
                "type" : "text",
                "delegate" : delegate,
                "ctx" : ctx }, null);

            return null;
	    }
	},
});
