import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Matrix2 from "../Core/Matrix2.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";

/**
 * @private
 * @constructor
 */
function createUniform(gl, activeUniform, uniformName, location) {
  switch (activeUniform.type) {
    case gl.FLOAT:
      return new UniformFloat(gl, activeUniform, uniformName, location);
    case gl.FLOAT_VEC2:
      return new UniformFloatVec2(gl, activeUniform, uniformName, location);
    case gl.FLOAT_VEC3:
      return new UniformFloatVec3(gl, activeUniform, uniformName, location);
    case gl.FLOAT_VEC4:
      return new UniformFloatVec4(gl, activeUniform, uniformName, location);
    case gl.SAMPLER_2D:
    case gl.SAMPLER_3D:
    case gl.SAMPLER_CUBE:
      return new UniformSampler(gl, activeUniform, uniformName, location);
    case gl.UNSIGNED_INT_SAMPLER_2D:
      return new UniformSampler(gl, activeUniform, uniformName, location);
    case gl.INT:
    case gl.BOOL:
      return new UniformInt(gl, activeUniform, uniformName, location);
    case gl.INT_VEC2:
    case gl.BOOL_VEC2:
      return new UniformIntVec2(gl, activeUniform, uniformName, location);
    case gl.INT_VEC3:
    case gl.BOOL_VEC3:
      return new UniformIntVec3(gl, activeUniform, uniformName, location);
    case gl.INT_VEC4:
    case gl.BOOL_VEC4:
      return new UniformIntVec4(gl, activeUniform, uniformName, location);
    case gl.FLOAT_MAT2:
      return new UniformMat2(gl, activeUniform, uniformName, location);
    case gl.FLOAT_MAT3:
      return new UniformMat3(gl, activeUniform, uniformName, location);
    case gl.FLOAT_MAT4:
      return new UniformMat4(gl, activeUniform, uniformName, location);
    default:
      throw new RuntimeError(
        `Unrecognized uniform type: ${activeUniform.type} for uniform "${uniformName}".`,
      );
  }
}

/**
 * @private
 * @constructor
 */
function UniformFloat(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = 0.0;

  this._gl = gl;
  this._location = location;
}

UniformFloat.prototype.set = function () {
  if (this.value !== this._value) {
    this._value = this.value;
    this._gl.uniform1f(this._location, this.value);
  }
};

///////////////////////////////////////////////////////////////////////////

/**
 * @private
 * @constructor
 */
function UniformFloatVec2(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Cartesian2();

  this._gl = gl;
  this._location = location;
}

UniformFloatVec2.prototype.set = function () {
  const v = this.value;
  if (!Cartesian2.equals(v, this._value)) {
    Cartesian2.clone(v, this._value);
    this._gl.uniform2f(this._location, v.x, v.y);
  }
};

///////////////////////////////////////////////////////////////////////////

/**
 * @private
 * @constructor
 */
function UniformFloatVec3(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = undefined;

  this._gl = gl;
  this._location = location;
}

UniformFloatVec3.prototype.set = function () {
  const v = this.value;

  if (defined(v.red)) {
    if (!Color.equals(v, this._value)) {
      this._value = Color.clone(v, this._value);
      this._gl.uniform3f(this._location, v.red, v.green, v.blue);
    }
  } else if (defined(v.x)) {
    if (!Cartesian3.equals(v, this._value)) {
      this._value = Cartesian3.clone(v, this._value);
      this._gl.uniform3f(this._location, v.x, v.y, v.z);
    }
  } else {
    //>>includeStart('debug', pragmas.debug);
    throw new DeveloperError(`Invalid vec3 value for uniform "${this.name}".`);
    //>>includeEnd('debug');
  }
};

///////////////////////////////////////////////////////////////////////////

/**
 * @private
 * @constructor
 */
function UniformFloatVec4(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = undefined;

  this._gl = gl;
  this._location = location;
}

UniformFloatVec4.prototype.set = function () {
  const v = this.value;

  if (defined(v.red)) {
    if (!Color.equals(v, this._value)) {
      this._value = Color.clone(v, this._value);
      this._gl.uniform4f(this._location, v.red, v.green, v.blue, v.alpha);
    }
  } else if (defined(v.x)) {
    if (!Cartesian4.equals(v, this._value)) {
      this._value = Cartesian4.clone(v, this._value);
      this._gl.uniform4f(this._location, v.x, v.y, v.z, v.w);
    }
  } else {
    //>>includeStart('debug', pragmas.debug);
    throw new DeveloperError(`Invalid vec4 value for uniform "${this.name}".`);
    //>>includeEnd('debug');
  }
};

///////////////////////////////////////////////////////////////////////////

/**
 * @private
 * @constructor
 */
function UniformSampler(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;

  this._gl = gl;
  this._location = location;

  this.textureUnitIndex = undefined;
}

UniformSampler.prototype.set = function () {
  const gl = this._gl;
  gl.activeTexture(gl.TEXTURE0 + this.textureUnitIndex);

  const v = this.value;
  gl.bindTexture(v._target, v._texture);
};

UniformSampler.prototype._setSampler = function (textureUnitIndex) {
  this.textureUnitIndex = textureUnitIndex;
  this._gl.uniform1i(this._location, textureUnitIndex);
  return textureUnitIndex + 1;
};

///////////////////////////////////////////////////////////////////////////

/**
 * @private
 * @constructor
 */
function UniformInt(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = 0.0;

  this._gl = gl;
  this._location = location;
}

UniformInt.prototype.set = function () {
  if (this.value !== this._value) {
    this._value = this.value;
    this._gl.uniform1i(this._location, this.value);
  }
};

///////////////////////////////////////////////////////////////////////////
/**
 * @private
 * @constructor
 */
function UniformIntVec2(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Cartesian2();

  this._gl = gl;
  this._location = location;
}

UniformIntVec2.prototype.set = function () {
  const v = this.value;
  if (!Cartesian2.equals(v, this._value)) {
    Cartesian2.clone(v, this._value);
    this._gl.uniform2i(this._location, v.x, v.y);
  }
};

///////////////////////////////////////////////////////////////////////////
/**
 * @private
 * @constructor
 */
function UniformIntVec3(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Cartesian3();

  this._gl = gl;
  this._location = location;
}

UniformIntVec3.prototype.set = function () {
  const v = this.value;
  if (!Cartesian3.equals(v, this._value)) {
    Cartesian3.clone(v, this._value);
    this._gl.uniform3i(this._location, v.x, v.y, v.z);
  }
};

///////////////////////////////////////////////////////////////////////////
/**
 * @private
 * @constructor
 */
function UniformIntVec4(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Cartesian4();

  this._gl = gl;
  this._location = location;
}

UniformIntVec4.prototype.set = function () {
  const v = this.value;
  if (!Cartesian4.equals(v, this._value)) {
    Cartesian4.clone(v, this._value);
    this._gl.uniform4i(this._location, v.x, v.y, v.z, v.w);
  }
};

///////////////////////////////////////////////////////////////////////////

const scratchUniformArray = new Float32Array(4);
/**
 * @private
 * @constructor
 */
function UniformMat2(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Matrix2();

  this._gl = gl;
  this._location = location;
}

UniformMat2.prototype.set = function () {
  if (!Matrix2.equalsArray(this.value, this._value, 0)) {
    Matrix2.clone(this.value, this._value);

    const array = Matrix2.toArray(this.value, scratchUniformArray);
    this._gl.uniformMatrix2fv(this._location, false, array);
  }
};

///////////////////////////////////////////////////////////////////////////

const scratchMat3Array = new Float32Array(9);
/**
 * @private
 * @constructor
 */
function UniformMat3(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Matrix3();

  this._gl = gl;
  this._location = location;
}

UniformMat3.prototype.set = function () {
  if (!Matrix3.equalsArray(this.value, this._value, 0)) {
    Matrix3.clone(this.value, this._value);

    const array = Matrix3.toArray(this.value, scratchMat3Array);
    this._gl.uniformMatrix3fv(this._location, false, array);
  }
};

///////////////////////////////////////////////////////////////////////////

const scratchMat4Array = new Float32Array(16);
/**
 * @private
 * @constructor
 */
function UniformMat4(gl, activeUniform, uniformName, location) {
  /**
   * @type {string}
   * @readonly
   */
  this.name = uniformName;

  this.value = undefined;
  this._value = new Matrix4();

  this._gl = gl;
  this._location = location;
}

UniformMat4.prototype.set = function () {
  if (!Matrix4.equalsArray(this.value, this._value, 0)) {
    Matrix4.clone(this.value, this._value);

    const array = Matrix4.toArray(this.value, scratchMat4Array);
    this._gl.uniformMatrix4fv(this._location, false, array);
  }
};
export default createUniform;
