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

THREE.GLTFLoaderUtils = Object.create(Object, {

    // errors
    MISSING_DESCRIPTION: { value: "MISSING_DESCRIPTION" },
    INVALID_PATH: { value: "INVALID_PATH" },
    INVALID_TYPE: { value: "INVALID_TYPE" },
    XMLHTTPREQUEST_STATUS_ERROR: { value: "XMLHTTPREQUEST_STATUS_ERROR" },
    NOT_FOUND: { value: "NOT_FOUND" },
    // misc constants
    ARRAY_BUFFER: { value: "ArrayBuffer" },

    _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 self = this;

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

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

	var xhr = new XMLHttpRequest();
	xhr.open('GET', path, true);
	xhr.responseType = (type === this.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.addEventListener( 'load', function ( event ) {
		delegate.streamAvailable(path, xhr.response);
	}, false );
	xhr.addEventListener( 'error', function ( event ) {
		delegate.handleError(THREE.GLTFLoaderUtils.XMLHTTPREQUEST_STATUS_ERROR, xhr.status);
	}, false );
	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.path];
	if (streamStatus && streamStatus.status === "loading" )
            {
		streamStatus.requests.push(request);
		return;
	}
            
	this._streamsStatus[request.path] = { 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.path, request.type, processResourceDelegate);
        }
    },


    _elementSizeForGLType: {
        value: function(glType) {
	switch (glType) {
		case WebGLRenderingContext.FLOAT :
			return Float32Array.BYTES_PER_ELEMENT;
		case WebGLRenderingContext.UNSIGNED_BYTE :
			return Uint8Array.BYTES_PER_ELEMENT;
		case WebGLRenderingContext.UNSIGNED_SHORT :
			return Uint16Array.BYTES_PER_ELEMENT;
		case WebGLRenderingContext.FLOAT_VEC2 :
			return Float32Array.BYTES_PER_ELEMENT * 2;
		case WebGLRenderingContext.FLOAT_VEC3 :
			return Float32Array.BYTES_PER_ELEMENT * 3;
		case WebGLRenderingContext.FLOAT_VEC4 :
			return Float32Array.BYTES_PER_ELEMENT * 4;
		case WebGLRenderingContext.FLOAT_MAT3 :
			return Float32Array.BYTES_PER_ELEMENT * 9;
		case WebGLRenderingContext.FLOAT_MAT4 :
			return Float32Array.BYTES_PER_ELEMENT * 16;
		default:
			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.type) * wrappedBufferView.count) + byteOffset ];

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

	var savedBuffer = this._getResource(wrappedBufferView.id);
	if (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,
                "path" : request.path,
                "range" : [ 0 ],
                "type" : "text",
                "delegate" : delegate,
                "ctx" : ctx }, null);
    	
	return null;
}
	},    
});
