/*global define*/
define(['Core/defaultValue', 'Core/defined', 'Core/destroyObject', 'Core/DeveloperError', 'Core/ComponentDatatype'], function(
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        ComponentDatatype) {
    "use strict";

    function addAttribute(attributes, attribute, index) {
        if (!attribute.vertexBuffer && !attribute.value) {
            throw new DeveloperError('attribute must have a vertexBuffer or a value.');
        }

        if (attribute.vertexBuffer && attribute.value) {
            throw new DeveloperError('attribute cannot have both a vertexBuffer and a value.  It must have either a vertexBuffer property defining per-vertex data or a value property defining data for all vertices.');
        }

        var componentsPerAttribute = attribute.value ? attribute.value.length : attribute.componentsPerAttribute;

        if ((componentsPerAttribute !== 1) &&
            (componentsPerAttribute !== 2) &&
            (componentsPerAttribute !== 3) &&
            (componentsPerAttribute !== 4)) {
            if (attribute.value) {
                throw new DeveloperError('attribute.value.length must be in the range [1, 4].');
            }

            throw new DeveloperError('attribute.componentsPerAttribute must be in the range [1, 4].');
        }

        if (attribute.componentDatatype) {
            var datatype = attribute.componentDatatype;
            if (!ComponentDatatype.validate(datatype)) {
                throw new DeveloperError('attribute must have a valid componentDatatype or not specify it.');
            }
        }

        if (attribute.strideInBytes && (attribute.strideInBytes > 255)) {
            // WebGL limit.  Not in GL ES.
            throw new DeveloperError('attribute must have a strideInBytes less than or equal to 255 or not specify it.');
        }

        // Shallow copy the attribute; we do not want to copy the vertex buffer.
        var attr = {
            index : defaultValue(attribute.index, index),
            enabled : defaultValue(attribute.enabled, true),
            vertexBuffer : attribute.vertexBuffer,
            value : attribute.value ? attribute.value.slice(0) : undefined,
            componentsPerAttribute : componentsPerAttribute,
            componentDatatype : attribute.componentDatatype || ComponentDatatype.FLOAT,
            normalize : attribute.normalize || false,
            offsetInBytes : attribute.offsetInBytes || 0,
            strideInBytes : attribute.strideInBytes || 0
        };

        if (attr.vertexBuffer) {
            // Common case: vertex buffer for per-vertex data
            attr.vertexAttrib = function(gl) {
                gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer._getBuffer());
                gl.vertexAttribPointer(this.index, this.componentsPerAttribute, this.componentDatatype.value, this.normalize, this.strideInBytes, this.offsetInBytes);
                gl.enableVertexAttribArray(this.index);
            };

            attr.disableVertexAttribArray = function(gl) {
                gl.disableVertexAttribArray(this.index);
            };
        } else {
            // Less common case: value array for the same data for each vertex
            switch (attr.componentsPerAttribute) {
            case 1:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib1fv(this.index, this.value);
                };
                break;
            case 2:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib2fv(this.index, this.value);
                };
                break;
            case 3:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib3fv(this.index, this.value);
                };
                break;
            case 4:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib4fv(this.index, this.value);
                };
                break;
            }

            attr.disableVertexAttribArray = function(gl) {
            };
        }

        attributes.push(attr);
    }

    function bind(gl, attributes, indexBuffer) {
        for ( var i = 0; i < attributes.length; ++i) {
            var attribute = attributes[i];
            if (attribute.enabled) {
                attribute.vertexAttrib(gl);
            }
        }

        if (defined(indexBuffer)) {
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer());
        }
    }

    /**
     * DOC_TBA
     *
     * @alias VertexArray
     *
     * @internalConstructor
     *
     * @see {@link Context#createVertexArray}
     * @see {@link Context#createVertexArrayFromGeometry}
     */
    var VertexArray = function(gl, vertexArrayObject, attributes, indexBuffer) {
        var vaAttributes = [];

        if (defined(attributes)) {
            for ( var i = 0; i < attributes.length; ++i) {
                addAttribute(vaAttributes, attributes[i], i);
            }
        }

        // Verify all attribute names are unique
        var uniqueIndices = {};
        for ( var j = 0; j < vaAttributes.length; ++j) {
            var index = vaAttributes[j].index;
            if (uniqueIndices[index]) {
                throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
            }

            uniqueIndices[index] = true;
        }

        var vao;

        // Setup VAO if extension is supported
        if (vertexArrayObject !== null) {
            vao = vertexArrayObject.createVertexArrayOES();
            vertexArrayObject.bindVertexArrayOES(vao);
            bind(gl, vaAttributes, indexBuffer);
            vertexArrayObject.bindVertexArrayOES(null);
        }

        this._gl = gl;
        this._vaoExtension = vertexArrayObject;
        this._vao = vao;
        this._attributes = vaAttributes;
        this._indexBuffer = indexBuffer;
    };

    /**
     * DOC_TBA
     *
     * index is the location in the array of attributes, not the index property of an attribute.
     *
     * @memberof VertexArray
     *
     * @exception {DeveloperError} index is required.
     * @exception {DeveloperError} This vertex array was destroyed, i.e., destroy() was called.
     */
    VertexArray.prototype.getAttribute = function(index) {
        if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }

        return this._attributes[index];
    };

    /**
    * DOC_TBA
    *
    * @memberof VertexArray
    *
    * @exception {DeveloperError} This vertex array was destroyed, i.e., destroy() was called.
    */
    VertexArray.prototype.getNumberOfAttributes = function() {
        return this._attributes.length;
    };

    /**
     * DOC_TBA
     *
     * @memberof VertexArray
     *
     * @returns {Buffer} DOC_TBA.
     * @exception {DeveloperError} This vertex array was destroyed, i.e., destroy() was called.
     */
    VertexArray.prototype.getIndexBuffer = function() {
        return this._indexBuffer;
    };

    VertexArray.prototype._bind = function() {
        if (defined(this._vao)) {
            this._vaoExtension.bindVertexArrayOES(this._vao);
        } else {
            bind(this._gl, this._attributes, this._indexBuffer);
        }
    };

    VertexArray.prototype._unBind = function() {
        if (defined(this._vao)) {
            this._vaoExtension.bindVertexArrayOES(null);
        } else {
            var attributes = this._attributes;
            var gl = this._gl;

            for ( var i = 0; i < attributes.length; ++i) {
                var attribute = attributes[i];
                if (attribute.enabled) {
                    attribute.disableVertexAttribArray(gl);
                }
            }
            if (this._indexBuffer) {
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
            }
        }
    };

    /**
     * This assumes that each vertex buffer in the vertex array has the same number of vertices.
     * @private
     */
    VertexArray.prototype._getNumberOfVertices = function() {
        if (this._attributes.length > 0) {
            var attribute = this._attributes[0];
            var bytes = attribute.strideInBytes || (attribute.componentsPerAttribute * attribute.componentDatatype.sizeInBytes);

            return attribute.vertexBuffer.getSizeInBytes() / bytes;
        }

        return 0;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @memberof VertexArray
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see VertexArray#destroy
     */
    VertexArray.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Only call this if the vertex array owns the vertex buffers referenced by the attributes and owns its
     * index buffer; otherwise, the owner of the buffers is responsible for destroying them.  A vertex or
     * index buffer is only destroyed if it's <code>getVertexArrayDestroyable</code> function returns
     * <code>true</code> (the default).  This allows combining destroyable and non-destroyable buffers
     * in the same vertex array.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @memberof VertexArray
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This vertex array was destroyed, i.e., destroy() was called.
     *
     * @see VertexArray#isDestroyed
     * @see Buffer#getVertexArrayDestroyable
     * @see <a href='http://www.khronos.org/opengles/sdk/2.0/docs/man/glDeleteBuffers.xml'>glDeleteBuffers</a>
     *
     * @example
     * // Destroying the vertex array implicitly calls destroy for each of its vertex
     * // buffers and its index buffer.
     * var vertexBuffer = context.createVertexBuffer(new Float32Array([0, 0, 0]),
     *     BufferUsage.STATIC_DRAW);
     * var vertexArray = context.createVertexArray({
     *     vertexBuffer : vertexBuffer,
     *     componentsPerAttribute : 3
     * });
     * // ...
     * vertexArray = vertexArray.destroy();
     * // Calling vertexBuffer.destroy() would throw DeveloperError at this point.
     */
    VertexArray.prototype.destroy = function() {
        var attributes = this._attributes;
        for ( var i = 0; i < attributes.length; ++i) {
            var vertexBuffer = attributes[i].vertexBuffer;
            if (vertexBuffer && !vertexBuffer.isDestroyed() && vertexBuffer.getVertexArrayDestroyable()) {
                vertexBuffer.destroy();
            }
        }

        var indexBuffer = this._indexBuffer;
        if (indexBuffer && !indexBuffer.isDestroyed() && indexBuffer.getVertexArrayDestroyable()) {
            indexBuffer.destroy();
        }

        if (defined(this._vao)) {
            this._vaoExtension.deleteVertexArrayOES(this._vao);
        }

        return destroyObject(this);
    };

    return VertexArray;
});