import * as THREE from '../node_modules/three/src/Three';



const _vec2 = new THREE.Vector2()
const _vec3 = new THREE.Vector3()


const raycaster = new THREE.Raycaster();
raycaster.params.Line.threshold = 1;
raycaster.params.Points.threshold = 1;


const color = {
  // background: 0x181818,
  background: 0x404040,
  lighting: 0xFFFFFF,
  emissive: 0x072534,
  meshTempHover: 0x9DCFED,

  point: 0xffffff,
  selpoint: 0xff0000,
  line: 0xffffff,
  mesh: 0x9DCFED,
  dimension: 0x0000ff,

  plane: 0xffff00,
  planeBorder: 0x2e2e00,
  opacity: 0.02
}

const hoverColor = {
  emissive: 0x343407,
  point: 0x10B981,
  selpoint: 0xff0000,
  line: 0x10B981,
  mesh: 0xFAB601,
  dimension: 0x10B981,

  plane: 0xffff00,
  planeBorder: 0x919100,
  opacity: 0.06
}


const lineMaterial = new THREE.LineBasicMaterial({
  linewidth: 1,
  color: color.line,
  depthTest: false
})


const pointMaterial = new THREE.PointsMaterial({
  color: color.point,
  size: 4,
  depthTest: false
})


const ptObj = (n, visibility = true) => {
  const ret = new THREE.Points(
    new THREE.BufferGeometry().setAttribute('position',
      new THREE.Float32BufferAttribute(n || 3, 3)
    ),
    pointMaterial.clone()
  );
  ret.name = "p" + id++
  ret.userData.type = 'point'
  ret.visible = visibility
  ret.renderOrder = 1
  return ret
}

const lineObj = (n = 1) => {
  const ret = new THREE.Line(
    new THREE.BufferGeometry().setAttribute('position',
      new THREE.Float32BufferAttribute(3 * (n + 1), 3)
    ),
    lineMaterial.clone()
  );
  ret.name = 'l' + id++
  ret.userData.type = 'line'
  ret.renderOrder = 1
  return ret
}


async function awaitSelection(...criteria) {

  function fullfilled() {
    for (let i = criteria.length - 1; i >= 0; i--) {
      const crit = criteria[i]
      let nfilled = 0;
      for (let k in counter) {
        if (!crit[k] || counter[k] > crit[k]) {
          criteria.splice(i, 1)
          break;
        } else if (counter[k] == crit[k]) {
          nfilled += 1
        }
      }
      if (nfilled == Object.keys(crit).length) return true
    }
    return false
  }

  const counter = {}

  let references = (this.selected || this.scene.selected).slice()

  for (let ob of references) {
    const type = ob.userData.type
    if (counter[type]) {
      counter[type] += 1;
    } else {
      counter[type] = 1;
    }
  }
  if (fullfilled()) return references

  let end = false;
  while (criteria.length && !end) {
    let pt;
    let onEnd, onKey;
    try {

      pt = await new Promise((res, rej) => {
        onKey = (e) => e.key == 'Escape' && rej()
        onEnd = (e) => this.hovered.length && res(this.hovered[0])
        this.canvas.addEventListener('pointerdown', onEnd)
        window.addEventListener('keydown', onKey)
      })

      references.push(pt)
      const type = pt.userData.type

      if (counter[type]) {
        counter[type] += 1;
      } else {
        counter[type] = 1;
      }

      if (fullfilled()) return references

    } catch (e) {
      end = true;
    }

    this.canvas.removeEventListener('pointerdown', onEnd)
    window.removeEventListener('keydown', onKey)
  }

  console.log('fail')
  return null
}


function setHover(obj, state, meshHover = true) {
  let colObj = state == 1 ? hoverColor : color

  switch (obj.userData.type) {
    case 'plane':
      obj.material.opacity = colObj.opacity
      obj.children[0].material.color.set(colObj['planeBorder'])
      break;
    case 'sketch':
      obj.traverse(ele => {
        if (ele.userData.type == 'line') {
          ele.material.color.set(colObj['line'])
        }
      })
      break;
    case 'mesh':
      if (meshHover) {
        obj.material.emissive.set(colObj.emissive)
      } else {
        break
      }
    case 'point':
      obj.material.color.set(colObj[obj.userData.type])
      obj.material.size = state ? 8 : 4
    default:
      obj.material.color.set(colObj[obj.userData.type])
      break;
  }

}


const vertexShader = `
  uniform float edgeSize;
  uniform float pointWidth;
  void main() {
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_PointSize = (pointWidth + edgeSize);
    gl_Position = projectionMatrix * mvPosition;
  }
`

const fragmentShader = `
  uniform vec3 color;
  uniform vec3 edgeColor;
  uniform float edgeSize;
  uniform float pointWidth;
  void main() {
    gl_FragColor = vec4(color, 1.0);
    float distance = length(2.0 * gl_PointCoord - 1.0);
    float totalWidth = pointWidth + edgeSize;
    float edgeStart = pointWidth;
    float edgeEnd = pointWidth + 2.0;
    float sEdge = smoothstep(edgeStart, edgeEnd, distance * totalWidth);
    // transition from edgeColor to color along the edge
    gl_FragColor = ( vec4(edgeColor, 1.0) * sEdge) + ((1.0 - sEdge) * gl_FragColor);

    if (distance > 1.0) {
      discard;
    }
  }
`
const sz = 4
const custPtMat = new THREE.ShaderMaterial({
  uniforms: {
    color: { value: new THREE.Color(0xff0000) },
    edgeColor: { value: new THREE.Color(0x770000) },
    pointWidth: { value: sz },
    edgeSize: { value: sz },
  },
  vertexShader,
  fragmentShader,
  depthTest: false
  // depthWrite:false
});









window.rc = raycaster


export { lineMaterial, pointMaterial, custPtMat, _vec2, _vec3, raycaster, color, hoverColor, ptObj, lineObj, awaitSelection, setHover }