import { mat3, mat4 } from 'gl-matrix';
import { n as newInstance$1, o as obj, c as macro } from '../../macros2.js';
import vtkBufferObject from './BufferObject.js';
import vtkHardwareSelector from './HardwareSelector.js';
import vtkProperty from '../Core/Property.js';
import vtkOpenGLPolyDataMapper from './PolyDataMapper.js';
import vtkShaderProgram from './ShaderProgram.js';
import { registerOverride } from './ViewNodeFactory.js';

const {
  vtkErrorMacro
} = macro;
const {
  Representation
} = vtkProperty;
const {
  ObjectType
} = vtkBufferObject;
const {
  PassTypes
} = vtkHardwareSelector;
const StartEvent = {
  type: 'StartEvent'
};
const EndEvent = {
  type: 'EndEvent'
};

// ----------------------------------------------------------------------------
// vtkOpenGLSphereMapper methods
// ----------------------------------------------------------------------------

function vtkOpenGLGlyph3DMapper(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkOpenGLGlyph3DMapper');

  // Capture 'parentClass' api for internal use
  const superClass = {
    ...publicAPI
  };
  publicAPI.renderPiece = (ren, actor) => {
    publicAPI.invokeEvent(StartEvent);
    if (!model.renderable.getStatic()) {
      model.renderable.update();
    }
    model.currentInput = model.renderable.getInputData(1);
    publicAPI.invokeEvent(EndEvent);
    if (!model.currentInput) {
      vtkErrorMacro('No input!');
      return;
    }

    // if there are no points then we are done
    if (!model.currentInput.getPoints || !model.currentInput.getPoints().getNumberOfValues()) {
      return;
    }

    // apply faceCulling
    const gl = model.context;
    if (model._openGLRenderWindow.getWebgl2()) {
      model.hardwareSupport = true;
      model.extension = null;
    } else if (!model.extension) {
      model.extension = model.context.getExtension('ANGLE_instanced_arrays');
      model.hardwareSupport = !!model.extension;
    }
    // to test without extension support uncomment the next two lines
    // model.extension = null;
    // model.hardwareSupport = !!model.extension;

    const backfaceCulling = actor.getProperty().getBackfaceCulling();
    const frontfaceCulling = actor.getProperty().getFrontfaceCulling();
    if (!backfaceCulling && !frontfaceCulling) {
      model._openGLRenderWindow.disableCullFace();
    } else if (frontfaceCulling) {
      model._openGLRenderWindow.enableCullFace();
      gl.cullFace(gl.FRONT);
    } else {
      model._openGLRenderWindow.enableCullFace();
      gl.cullFace(gl.BACK);
    }
    publicAPI.renderPieceStart(ren, actor);
    publicAPI.renderPieceDraw(ren, actor);
    publicAPI.renderPieceFinish(ren, actor);
  };
  publicAPI.multiply4x4WithOffset = (out, a, b, off) => {
    const a00 = a[0];
    const a01 = a[1];
    const a02 = a[2];
    const a03 = a[3];
    const a10 = a[4];
    const a11 = a[5];
    const a12 = a[6];
    const a13 = a[7];
    const a20 = a[8];
    const a21 = a[9];
    const a22 = a[10];
    const a23 = a[11];
    const a30 = a[12];
    const a31 = a[13];
    const a32 = a[14];
    const a33 = a[15];

    // Cache only the current line of the second matrix
    let b0 = b[off];
    let b1 = b[off + 1];
    let b2 = b[off + 2];
    let b3 = b[off + 3];
    out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
    out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
    out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
    out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
    b0 = b[off + 4];
    b1 = b[off + 5];
    b2 = b[off + 6];
    b3 = b[off + 7];
    out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
    out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
    out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
    out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
    b0 = b[off + 8];
    b1 = b[off + 9];
    b2 = b[off + 10];
    b3 = b[off + 11];
    out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
    out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
    out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
    out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
    b0 = b[off + 12];
    b1 = b[off + 13];
    b2 = b[off + 14];
    b3 = b[off + 15];
    out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
    out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
    out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
    out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
  };
  publicAPI.replaceShaderNormal = (shaders, ren, actor) => {
    if (model.hardwareSupport) {
      const lastLightComplexity = model.lastBoundBO.getReferenceByName('lastLightComplexity');
      if (lastLightComplexity > 0) {
        let VSSource = shaders.Vertex;
        if (model.lastBoundBO.getCABO().getNormalOffset()) {
          VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Normal::Dec', ['attribute vec3 normalMC;', 'attribute mat3 gNormal;', 'uniform mat3 normalMatrix;', 'varying vec3 normalVCVSOutput;']).result;
          VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Normal::Impl', ['normalVCVSOutput = normalMatrix * gNormal * normalMC;']).result;
        }
        shaders.Vertex = VSSource;
      }
    }
    superClass.replaceShaderNormal(shaders, ren, actor);
  };
  publicAPI.replaceShaderColor = (shaders, ren, actor) => {
    if (model.hardwareSupport && model.renderable.getColorArray()) {
      let VSSource = shaders.Vertex;
      let GSSource = shaders.Geometry;
      let FSSource = shaders.Fragment;
      const lastLightComplexity = model.lastBoundBO.getReferenceByName('lastLightComplexity');

      // create the material/color property declarations, and VS implementation
      // these are always defined
      let colorDec = ['uniform float ambient;', 'uniform float diffuse;', 'uniform float specular;', 'uniform float opacityUniform; // the fragment opacity'];
      // add more for specular
      if (lastLightComplexity) {
        colorDec = colorDec.concat(['uniform vec3 specularColorUniform;', 'uniform float specularPowerUniform;']);
      }

      // now handle the more complex fragment shader implementation
      // the following are always defined variables.  We start
      // by assigning a default value from the uniform
      let colorImpl = ['vec3 ambientColor;', '  vec3 diffuseColor;', '  float opacity;'];
      if (lastLightComplexity) {
        colorImpl = colorImpl.concat(['  vec3 specularColor;', '  float specularPower;']);
      }
      colorImpl = colorImpl.concat(['  opacity = opacityUniform;']);
      if (lastLightComplexity) {
        colorImpl = colorImpl.concat(['  specularColor = specularColorUniform;', '  specularPower = specularPowerUniform;']);
      }
      if (!model.drawingEdges) {
        colorDec = colorDec.concat(['varying vec4 vertexColorVSOutput;']);
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Dec', ['attribute vec4 gColor;', 'varying vec4 vertexColorVSOutput;']).result;
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Impl', ['vertexColorVSOutput = gColor;']).result;
        GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Dec', ['in vec4 vertexColorVSOutput[];', 'out vec4 vertexColorGSOutput;']).result;
        GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Impl', ['vertexColorGSOutput = vertexColorVSOutput[i];']).result;
        colorImpl = colorImpl.concat(['  diffuseColor = vertexColorVSOutput.rgb;', '  ambientColor = vertexColorVSOutput.rgb;', '  opacity = opacity*vertexColorVSOutput.a;']);
      }
      FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', colorImpl).result;
      FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Dec', colorDec).result;
      shaders.Vertex = VSSource;
      shaders.Geometry = GSSource;
      shaders.Fragment = FSSource;
    }
    superClass.replaceShaderColor(shaders, ren, actor);
  };
  publicAPI.replaceShaderPositionVC = (shaders, ren, actor) => {
    if (model.hardwareSupport) {
      let VSSource = shaders.Vertex;

      // do we need the vertex in the shader in View Coordinates
      const lastLightComplexity = model.lastBoundBO.getReferenceByName('lastLightComplexity');
      if (lastLightComplexity > 0) {
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::PositionVC::Impl', ['vec4 gVertexMC = gMatrix * vertexMC;', 'vertexVCVSOutput = MCVCMatrix * gVertexMC;', '  gl_Position = MCPCMatrix * gVertexMC;']).result;
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', ['attribute mat4 gMatrix;', 'uniform mat4 MCPCMatrix;', 'uniform mat4 MCVCMatrix;']).result;
      } else {
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', ['attribute mat4 gMatrix;', 'uniform mat4 MCPCMatrix;']).result;
        VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::PositionVC::Impl', ['vec4 gVertexMC = gMatrix * vertexMC;', '  gl_Position = MCPCMatrix * gVertexMC;']).result;
      }
      shaders.Vertex = VSSource;
    }
    superClass.replaceShaderPositionVC(shaders, ren, actor);
  };
  publicAPI.replaceShaderPicking = (shaders, ren, actor) => {
    if (model.hardwareSupport) {
      let FSSource = shaders.Fragment;
      let VSSource = shaders.Vertex;
      VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Picking::Dec', ['attribute vec3 mapperIndexVS;', 'varying vec3 mapperIndexVSOutput;']).result;
      VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Picking::Impl', '  mapperIndexVSOutput = mapperIndexVS;').result;
      shaders.Vertex = VSSource;
      FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Dec', ['varying vec3 mapperIndexVSOutput;', 'uniform vec3 mapperIndex;', 'uniform int picking;']).result;
      FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', ['  vec4 pickColor = picking == 2 ? vec4(mapperIndexVSOutput,1.0) : vec4(mapperIndex,1.0);', '  gl_FragData[0] = picking != 0 ? pickColor : gl_FragData[0];']).result;
      shaders.Fragment = FSSource;
    } else {
      superClass.replaceShaderPicking(shaders, ren, actor);
    }
  };
  publicAPI.updateGlyphShaderParameters = (normalMatrixUsed, mcvcMatrixUsed, cellBO, carray, garray, narray, p, selector) => {
    const program = cellBO.getProgram();
    if (normalMatrixUsed) {
      const a = model.normalMatrix;
      const b = narray;
      const ofs = p * 9;
      const out = model.tmpMat3;
      const a00 = a[0];
      const a01 = a[1];
      const a02 = a[2];
      const a10 = a[3];
      const a11 = a[4];
      const a12 = a[5];
      const a20 = a[6];
      const a21 = a[7];
      const a22 = a[8];
      const b00 = b[ofs];
      const b01 = b[ofs + 1];
      const b02 = b[ofs + 2];
      const b10 = b[ofs + 3];
      const b11 = b[ofs + 4];
      const b12 = b[ofs + 5];
      const b20 = b[ofs + 6];
      const b21 = b[ofs + 7];
      const b22 = b[ofs + 8];
      out[0] = b00 * a00 + b01 * a10 + b02 * a20;
      out[1] = b00 * a01 + b01 * a11 + b02 * a21;
      out[2] = b00 * a02 + b01 * a12 + b02 * a22;
      out[3] = b10 * a00 + b11 * a10 + b12 * a20;
      out[4] = b10 * a01 + b11 * a11 + b12 * a21;
      out[5] = b10 * a02 + b11 * a12 + b12 * a22;
      out[6] = b20 * a00 + b21 * a10 + b22 * a20;
      out[7] = b20 * a01 + b21 * a11 + b22 * a21;
      out[8] = b20 * a02 + b21 * a12 + b22 * a22;
      program.setUniformMatrix3x3('normalMatrix', model.tmpMat3);
    }
    publicAPI.multiply4x4WithOffset(model.tmpMat4, model.mcpcMatrix, garray, p * 16);
    program.setUniformMatrix('MCPCMatrix', model.tmpMat4);
    if (mcvcMatrixUsed) {
      publicAPI.multiply4x4WithOffset(model.tmpMat4, model.mcvcMatrix, garray, p * 16);
      program.setUniformMatrix('MCVCMatrix', model.tmpMat4);
    }

    // set color
    if (carray) {
      const cdata = carray.getData();
      model.tmpColor[0] = cdata[p * 4] / 255.0;
      model.tmpColor[1] = cdata[p * 4 + 1] / 255.0;
      model.tmpColor[2] = cdata[p * 4 + 2] / 255.0;
      program.setUniform3fArray('ambientColorUniform', model.tmpColor);
      program.setUniform3fArray('diffuseColorUniform', model.tmpColor);
    }
    if (selector) {
      program.setUniform3fArray('mapperIndex', selector.getPropColorValue());
    }
  };
  publicAPI.renderPieceDraw = (ren, actor) => {
    const representation = actor.getProperty().getRepresentation();
    const gl = model.context;
    const drawSurfaceWithEdges = actor.getProperty().getEdgeVisibility() && representation === Representation.SURFACE;

    // [WMVP]C == {world, model, view, projection} coordinates
    // E.g., WCPC == world to projection coordinate transformation
    const keyMats = model.openGLCamera.getKeyMatrices(ren);
    const actMats = model.openGLActor.getKeyMatrices();

    // precompute the actor+camera mats once
    mat3.multiply(model.normalMatrix, keyMats.normalMatrix, actMats.normalMatrix);
    mat4.multiply(model.mcpcMatrix, keyMats.wcpc, actMats.mcwc);
    mat4.multiply(model.mcvcMatrix, keyMats.wcvc, actMats.mcwc);
    const garray = model.renderable.getMatrixArray();
    const narray = model.renderable.getNormalArray();
    const carray = model.renderable.getColorArray();
    const numPts = garray.length / 16;
    let compositePass = false;
    if (model._openGLRenderer.getSelector()) {
      if (model._openGLRenderer.getSelector().getCurrentPass() === PassTypes.COMPOSITE_INDEX_PASS) {
        compositePass = true;
      }
    }

    // for every primitive type
    for (let i = model.primTypes.Start; i < model.primTypes.End; i++) {
      // if there are entries
      const cabo = model.primitives[i].getCABO();
      if (cabo.getElementCount()) {
        // are we drawing edges
        model.drawingEdges = drawSurfaceWithEdges && (i === model.primTypes.TrisEdges || i === model.primTypes.TriStripsEdges);
        model.lastBoundBO = model.primitives[i];
        model.primitives[i].updateShaders(ren, actor, publicAPI);
        const program = model.primitives[i].getProgram();
        const mode = model.primitives[i].getOpenGLMode(representation);
        const normalMatrixUsed = program.isUniformUsed('normalMatrix');
        const mcvcMatrixUsed = program.isUniformUsed('MCVCMatrix');
        if (model.hardwareSupport) {
          if (model.extension) {
            model.extension.drawArraysInstancedANGLE(mode, 0, cabo.getElementCount(), numPts);
          } else {
            gl.drawArraysInstanced(mode, 0, cabo.getElementCount(), numPts);
          }
        } else {
          // draw the array multiple times with different cam matrix
          for (let p = 0; p < numPts; ++p) {
            if (compositePass) {
              model._openGLRenderer.getSelector().renderCompositeIndex(p);
            }
            publicAPI.updateGlyphShaderParameters(normalMatrixUsed, mcvcMatrixUsed, model.primitives[i], carray, garray, narray, p, compositePass ? model._openGLRenderer.getSelector() : null);
            gl.drawArrays(mode, 0, cabo.getElementCount());
          }
        }
      }
    }
  };
  publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => {
    if (cellBO.getCABO().getElementCount() && (model.glyphBOBuildTime.getMTime() > cellBO.getAttributeUpdateTime().getMTime() || cellBO.getShaderSourceTime().getMTime() > cellBO.getAttributeUpdateTime().getMTime())) {
      if (cellBO.getProgram().isAttributeUsed('gMatrix')) {
        if (!cellBO.getVAO().addAttributeMatrixWithDivisor(cellBO.getProgram(), model.matrixBuffer, 'gMatrix', 0, 64, model.context.FLOAT, 4, false, 1)) {
          vtkErrorMacro('Error setting gMatrix in shader VAO.');
        }
      } else {
        cellBO.getVAO().removeAttributeArray('gMatrix');
      }
      if (cellBO.getProgram().isAttributeUsed('gNormal')) {
        if (!cellBO.getVAO().addAttributeMatrixWithDivisor(cellBO.getProgram(), model.normalBuffer, 'gNormal', 0, 36, model.context.FLOAT, 3, false, 1)) {
          vtkErrorMacro('Error setting gNormal in shader VAO.');
        }
      } else {
        cellBO.getVAO().removeAttributeArray('gNormal');
      }
      if (cellBO.getProgram().isAttributeUsed('gColor')) {
        if (!cellBO.getVAO().addAttributeArrayWithDivisor(cellBO.getProgram(), model.colorBuffer, 'gColor', 0, 4, model.context.UNSIGNED_BYTE, 4, true, 1, false)) {
          vtkErrorMacro('Error setting gColor in shader VAO.');
        }
      } else {
        cellBO.getVAO().removeAttributeArray('gColor');
      }
      if (cellBO.getProgram().isAttributeUsed('mapperIndexVS')) {
        if (!cellBO.getVAO().addAttributeArrayWithDivisor(cellBO.getProgram(), model.pickBuffer, 'mapperIndexVS', 0, 4, model.context.UNSIGNED_BYTE, 4, true, 1, false)) {
          vtkErrorMacro('Error setting mapperIndexVS in shader VAO.');
        }
      } else {
        cellBO.getVAO().removeAttributeArray('mapperIndexVS');
      }
      superClass.setMapperShaderParameters(cellBO, ren, actor);
      cellBO.getAttributeUpdateTime().modified();
      return;
    }
    superClass.setMapperShaderParameters(cellBO, ren, actor);
  };
  publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => {
    model.renderable.buildArrays();

    // first do a coarse check
    // Note that the actor's mtime includes it's properties mtime
    const vmtime = model.VBOBuildTime.getMTime();
    if (vmtime < model.renderable.getBuildTime().getMTime()) {
      return true;
    }
    return superClass.getNeedToRebuildBufferObjects(ren, actor);
  };
  publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
    if (superClass.getNeedToRebuildShaders(cellBO, ren, actor) || cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() || cellBO.getShaderSourceTime().getMTime() < model.currentInput.getMTime()) {
      return true;
    }
    return false;
  };
  publicAPI.buildBufferObjects = (ren, actor) => {
    if (model.hardwareSupport) {
      // update the buffer objects if needed
      const garray = model.renderable.getMatrixArray();
      const narray = model.renderable.getNormalArray();
      const carray = model.renderable.getColorArray();
      if (!model.matrixBuffer) {
        model.matrixBuffer = vtkBufferObject.newInstance();
        model.matrixBuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
        model.normalBuffer = vtkBufferObject.newInstance();
        model.normalBuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
        model.colorBuffer = vtkBufferObject.newInstance();
        model.colorBuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
        model.pickBuffer = vtkBufferObject.newInstance();
        model.pickBuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
      }
      if (model.renderable.getBuildTime().getMTime() > model.glyphBOBuildTime.getMTime()) {
        model.matrixBuffer.upload(garray, ObjectType.ARRAY_BUFFER);
        model.normalBuffer.upload(narray, ObjectType.ARRAY_BUFFER);
        if (carray) {
          model.colorBuffer.upload(carray.getData(), ObjectType.ARRAY_BUFFER);
        } else {
          model.colorBuffer.releaseGraphicsResources();
        }
        const numPts = garray.length / 16;
        const parray = new Uint8Array(4 * numPts);
        for (let i = 0; i < numPts; ++i) {
          let value = i + 1;
          const offset = i * 4;
          parray[offset] = value % 256;
          value -= parray[offset];
          value /= 256;
          parray[offset + 1] = value % 256;
          value -= parray[offset + 1];
          value /= 256;
          parray[offset + 2] = value % 256;
          parray[offset + 3] = 255;
        }
        model.pickBuffer.upload(parray, ObjectType.ARRAY_BUFFER);
        model.glyphBOBuildTime.modified();
      }
    }
    return superClass.buildBufferObjects(ren, actor);
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  normalMatrix: null,
  mcpcMatrix: null,
  mcwcMatrix: null
};

// ----------------------------------------------------------------------------

function extend(publicAPI, model) {
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  vtkOpenGLPolyDataMapper.extend(publicAPI, model, initialValues);
  model.tmpMat3 = mat3.identity(new Float64Array(9));
  model.normalMatrix = mat3.identity(new Float64Array(9));
  model.mcpcMatrix = mat4.identity(new Float64Array(16));
  model.mcvcMatrix = mat4.identity(new Float64Array(16));
  model.tmpColor = [];
  model.glyphBOBuildTime = {};
  obj(model.glyphBOBuildTime, {
    mtime: 0
  });

  // Object methods
  vtkOpenGLGlyph3DMapper(publicAPI, model);
}

// ----------------------------------------------------------------------------

const newInstance = newInstance$1(extend, 'vtkOpenGLGlyph3DMapper');

// ----------------------------------------------------------------------------

var vtkGlyph3DMapper = {
  newInstance,
  extend
};

// Register ourself to OpenGL backend if imported
registerOverride('vtkGlyph3DMapper', newInstance);

export { vtkGlyph3DMapper as default, extend, newInstance };
