/*global define*/
define([
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/FeatureDetection',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/RuntimeError'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        FeatureDetection,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Color,
        Matrix2,
        Matrix3,
        Matrix4,
        RuntimeError) {
    "use strict";
    /*global console*/

    var scratchUniformMatrix2;
    var scratchUniformMatrix3;
    var scratchUniformMatrix4;
    if (FeatureDetection.supportsTypedArrays()) {
        scratchUniformMatrix2 = new Float32Array(4);
        scratchUniformMatrix3 = new Float32Array(9);
        scratchUniformMatrix4 = new Float32Array(16);
    }

    /**
     * @private
     */
    var UniformArray = function(gl, activeUniform, uniformName, locations) {
        this._gl = gl;
        this._activeUniform = activeUniform;
        this._uniformName = uniformName;
        this.value = new Array(locations.length);
        this._value = new Array(locations.length);
        this._locations = locations;

        /**
         * @private
         */
        this.textureUnitIndex = undefined;

        var set;
        switch (activeUniform.type) {
            case gl.FLOAT:
                set = this.setFloat;
                break;
            case gl.FLOAT_VEC2:
                set = this.setFloatVec2;
                break;
            case gl.FLOAT_VEC3:
                set = this.setFloatVec3;
                break;
            case gl.FLOAT_VEC4:
                set = this.setFloatVec4;
                break;
            case gl.SAMPLER_2D:
            case gl.SAMPLER_CUBE:
                set = this.setSampler;
                break;
            case gl.INT:
            case gl.BOOL:
                set = this.setInt;
                break;
            case gl.INT_VEC2:
            case gl.BOOL_VEC2:
                set = this.setIntVec2;
                break;
            case gl.INT_VEC3:
            case gl.BOOL_VEC3:
                set = this.setIntVec3;
                break;
            case gl.INT_VEC4:
            case gl.BOOL_VEC4:
                set = this.setIntVec4;
                break;
            case gl.FLOAT_MAT2:
                set = this.setMat2;
                break;
            case gl.FLOAT_MAT3:
                set = this.setMat3;
                break;
            case gl.FLOAT_MAT4:
                set = this.setMat4;
                break;
            default:
                throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
        }

        this._set = set;

        if ((activeUniform.type === gl.SAMPLER_2D) || (activeUniform.type === gl.SAMPLER_CUBE)) {
            this._setSampler = function(textureUnitIndex) {
                this.textureUnitIndex = textureUnitIndex;

                var length = locations.length;
                for (var i = 0; i < length; ++i) {
                    var index = textureUnitIndex + i;
                    gl.uniform1i(locations[i], index);
                }

                return textureUnitIndex + length;
            };
        }
    };

    defineProperties(UniformArray.prototype, {
        name : {
            get : function() {
                return this._uniformName;
            }
        },
        datatype : {
            get : function() {
                return this._activeUniform.type;
            }
        }
    });

    UniformArray.prototype.setFloat = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (v !== _value[i]) {
                _value[i] = v;
                gl.uniform1f(locations[i], v);
            }
        }
    };

    UniformArray.prototype.setFloatVec2 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian2.equals(v, _value[i])) {
                _value[i] = Cartesian2.clone(v, _value[i]);
                gl.uniform2f(locations[i], v.x, v.y);
            }
        }
    };

    UniformArray.prototype.setFloatVec3 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (defined(v.red)) {
                if (!Color.equals(v, _value[i])) {
                    _value[i] = Color.clone(v, _value[i]);
                    gl.uniform3f(locations[i], v.red, v.green, v.blue);
                }
            } else if (defined(v.x)) {
                if (!Cartesian3.equals(v, _value[i])) {
                    _value[i] = Cartesian3.clone(v, _value[i]);
                    gl.uniform3f(locations[i], v.x, v.y, v.z);
                }
            } else {
                throw new DeveloperError('Invalid vec3 value.');
            }
        }
    };

    UniformArray.prototype.setFloatVec4 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (defined(v.red)) {
                if (!Color.equals(v, _value[i])) {
                    _value[i] = Color.clone(v, _value[i]);
                    gl.uniform4f(locations[i], v.red, v.green, v.blue, v.alpha);
                }
            } else if (defined(v.x)) {
                if (!Cartesian4.equals(v, _value[i])) {
                    _value[i] = Cartesian4.clone(v, _value[i]);
                    gl.uniform4f(locations[i], v.x, v.y, v.z, v.w);
                }
            } else {
                throw new DeveloperError('Invalid vec4 value.');
            }
        }
    };

    UniformArray.prototype.setSampler = function() {
        var gl = this._gl;
        var locations = this._locations;
        var textureUnitIndex = gl.TEXTURE0 + this.textureUnitIndex;

        var value = this.value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];
            gl.activeTexture(textureUnitIndex + i);
            gl.bindTexture(v._target, v._texture);
        }
    };

    UniformArray.prototype.setInt = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (v !== _value[i]) {
                _value[i] = v;
                gl.uniform1i(locations[i], v);
            }
        }
    };

    UniformArray.prototype.setIntVec2 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian2.equals(v, _value[i])) {
                _value[i] = Cartesian2.clone(v, _value[i]);
                gl.uniform2i(locations[i], v.x, v.y);
            }
        }
    };

    UniformArray.prototype.setIntVec3 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian3.equals(v, _value[i])) {
                _value[i] = Cartesian3.clone(v, _value[i]);
                gl.uniform3i(locations[i], v.x, v.y, v.z);
            }
        }
    };

    UniformArray.prototype.setIntVec4 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian4.equals(v, _value[i])) {
                _value[i] = Cartesian4.clone(v, _value[i]);
                gl.uniform4i(locations[i], v.x, v.y, v.z, v.w);
            }
        }
    };

    UniformArray.prototype.setMat2 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];
            if (!Matrix2.equals(v, _value[i])) {
                _value[i] = Matrix2.clone(v, _value[i]);
                gl.uniformMatrix2fv(locations[i], false, Matrix2.toArray(v, scratchUniformMatrix2));
            }
        }
    };

    UniformArray.prototype.setMat3 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];
            if (!Matrix3.equals(v, _value[i])) {
                _value[i] = Matrix3.clone(v, _value[i]);
                gl.uniformMatrix3fv(locations[i], false, Matrix3.toArray(value[i], scratchUniformMatrix3));
            }
        }
    };

    UniformArray.prototype.setMat4 = function() {
        var gl = this._gl;
        var locations = this._locations;

        var value = this.value;
        var _value = this._value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];
            if (!Matrix4.equals(v, _value[i])) {
                _value[i] = Matrix4.clone(v, _value[i]);
                gl.uniformMatrix4fv(locations[i], false, Matrix4.toArray(value[i], scratchUniformMatrix4));
            }
        }
    };

    return UniformArray;
});