import BaseShaderMixin from "../mixins/base-shader-mixin.mjs";

/**
 * This class defines an interface which all shaders utilize.
 * @extends {PIXI.Shader}
 * @property {PIXI.Program} program The program to use with this shader.
 * @property {object} uniforms      The current uniforms of the Shader.
 * @mixes BaseShaderMixin
 * @abstract
 */
export default class AbstractBaseShader extends BaseShaderMixin(PIXI.Shader) {
  constructor(program, uniforms) {
    super(program, foundry.utils.deepClone(uniforms));

    /**
     * The initial values of the shader uniforms.
     * @type {object}
     */
    this.initialUniforms = uniforms;
  }

  /* -------------------------------------------- */

  /** @override */
  static create(uniforms, options) {
    const vert = this._vertexShaderCompatibility(options) ?? this._createVertexShader(options);
    const frag = this._fragmentShaderCompatibility(options) ?? this._createFragmentShader(options);
    const program = PIXI.Program.from(vert, frag);
    const u = foundry.utils.mergeObject(this.defaultUniforms, uniforms, {inplace: false, insertKeys: false});
    const shader = new this(program, u);
    shader._configure(options);
    return shader;
  }

  /* -------------------------------------------- */

  /**
   * Reset the shader uniforms back to their initial values.
   */
  reset() {
    for ( const [k, v] of Object.entries(this.initialUniforms) ) {
      this.uniforms[k] = foundry.utils.deepClone(v);
    }
  }

  /* ---------------------------------------- */

  /**
   * Perform operations which are required before binding the Shader to the Renderer.
   * @param {PIXI.DisplayObject} mesh      The mesh display object linked to this shader.
   * @param {PIXI.Renderer} renderer       The renderer
   * @protected
   */
  _preRender(_mesh, _renderer) {}
}

/**
 * Identify this class to be compatible with ShaderField
 * @type {boolean}
 * @internal
 * @readonly
 */
Object.defineProperty(AbstractBaseShader, "_isShaderFieldCompatible", {
  value: true,
  writable: false,
  enumerable: false,
  configurable: false
});
