// Copyright (c) 2021 8th Wall, Inc.

// Returns a pipeline module that initializes the threejs scene when the camera feed starts, and
// handles subsequent spawning of a glb model whenever the scene is tapped.

/* globals XR8 XRExtras THREE TWEEN */


var lastTarget;
var sceneIsPlaying = false;


var nextPosition,
    currentPosition;
var updateNextPosition = function(detail) {
  nextPosition = {
    position: detail.position,
    rotation: (new THREE.Quaternion()).copy(detail.rotation),
    scale: detail.scale,
    scaledWidth: detail.scaledWidth,
    scaledHeight: detail.scaledHeight
  }
  if(!currentPosition) {
    currentPosition = nextPosition;
  }
};

var  Lerp = function(a, b, t){
	    return a + (b - a) * t
};

var tmpVector = new THREE.Vector3(0,0,0);
var updateWorld = function(dt) {

  var t = Math.min(dt/300,0.5);
  currentPosition.position = (new THREE.Vector3()).lerpVectors(currentPosition.position, nextPosition.position, t);
  currentPosition.rotation = currentPosition.rotation.slerp(nextPosition.rotation, t);
  currentPosition.scale = Lerp(currentPosition.scale, nextPosition.scale, t);
  currentPosition.scaledWidth = Lerp(currentPosition.scaledWidth, nextPosition.scaledWidth, t);
  currentPosition.scaledHeight = Lerp(currentPosition.scaledHeight, nextPosition.scaledHeight, t);

  plane1.position.copy(currentPosition.position);
  plane1.quaternion.copy(currentPosition.rotation);
  plane1.scale.x=currentPosition.scale*currentPosition.scaledWidth;
  plane1.scale.y=currentPosition.scale*currentPosition.scaledHeight;

  var point = plane1.position.clone();
  var rotation = plane1.rotation.clone();
  rotation.x+=Math.PI/2;
  point.add((new THREE.Vector3(0,0,0)).applyEuler(rotation))

  plane2.position.copy(point);
  plane2.rotation.copy(plane1.rotation)
  plane2.rotateX(-Math.PI/180*85)
  //plane2.position.add(((new THREE.Vector3(0,lastTarget.scale*0.57,-lastTarget.scale*0.33)).applyEuler(plane1.rotation)))
  plane2.scale.y = plane2.scale.x = plane1.scale.x;
  plane2.position.add(((new THREE.Vector3(0,plane1.scale.y/2,0)).applyEuler(plane1.rotation)))
  plane2.position.add(((new THREE.Vector3(0,plane2.scale.x/2,0)).applyEuler(plane2.rotation)))
  var o = camera;

  if(!sceneControl || !sceneControl.group)
    return;

  if(!sceneIsPlaying) {
    sceneControl.show()
    sceneControl.setTime( wrapz.time() )
    sceneIsPlaying = true;
  }

  Object.assign(sceneControl.group.position, plane2.position);//wrapz.position(o.position, o, lastTarget))

  //Object.assign(sceneControl.group.rotation, wrapz.rotation(plane2.rotation))
  Object.assign(sceneControl.group.quaternion, wrapz.q(plane2.quaternion))
  sceneControl.group.rotateX(Math.PI/2)
  sceneControl.group.scale.x = sceneControl.group.scale.y = sceneControl.group.scale.z =  plane2.scale.x;
}
var showTarget = ({detail}) => {
  console.log('show', detail.name);
  window.lastTarget = detail;

  updateNextPosition(detail)

  //Object.assign(sceneControl.group.quaternion, wrapz.q(o.quaternion))
  //Object.assign(sceneControl.group.position, wrapz.position(o.position, o, lastTarget))





  if (detail.name === 'model-target1') {
    model.position.copy(detail.position)
    model.quaternion.copy(detail.rotation)
    model.scale.set(detail.scale, detail.scale, detail.scale)
    model.visible = true
  }
}
var dst = document.querySelector('#distance');


var updateTarget = ({detail}) => {
  updateNextPosition(detail)

  console.log('update', detail.name);
  /*plane1.position.copy(currentPosition.position);
  plane1.quaternion.copy(currentPosition.rotation);
  plane1.scale.x=currentPosition.scale*currentPosition.scaledWidth;
  plane1.scale.y=currentPosition.scale*currentPosition.scaledHeight;

  var point = plane1.position.clone();
  var rotation = plane1.rotation.clone();
  rotation.x+=Math.PI/2;
  point.add((new THREE.Vector3(0,0,0)).applyEuler(rotation))

  plane2.position.copy(point);
  plane2.rotation.copy(plane1.rotation)
  plane2.rotateX(-Math.PI/180*85)
  //plane2.position.add(((new THREE.Vector3(0,lastTarget.scale*0.57,-lastTarget.scale*0.33)).applyEuler(plane1.rotation)))
  plane2.scale.y = plane2.scale.x = plane1.scale.x;
  plane2.position.add(((new THREE.Vector3(0,plane1.scale.y/2,0)).applyEuler(plane1.rotation)))
  plane2.position.add(((new THREE.Vector3(0,plane2.scale.x/2,0)).applyEuler(plane2.rotation)))

  if(!sceneControl || !sceneControl.group)
    return;
  Object.assign(sceneControl.group.position, plane2.position);*/
}
var hideTarget = ({detail}) => {
  console.log('hide', detail.name)
  if (detail.name === 'model-target1') {
    model.position.copy(detail.position)
    model.quaternion.copy(detail.rotation)
    model.scale.set(detail.scale, detail.scale, detail.scale)
    model.visible = true
  }
}
var sceneControl;
var o = window.o = new THREE.Object3D();
var plane1, plane2;
const placegroundScenePipelineModule = () => {
  const modelFile = 'tree.glb'                            // 3D model to spawn at tap
  const startScale = new THREE.Vector3(0.01, 0.01, 0.01)  // Initial scale value for our model
  const endScale = new THREE.Vector3(2, 2, 2)             // Ending scale value for our model
  const animationMillis = 750                             // Animate over 0.75 seconds

  const raycaster = new THREE.Raycaster()
  const tapPosition = new THREE.Vector2()
  const loader = new THREE.GLTFLoader()  // This comes from GLTFLoader.js.

  let surface  // Transparent surface for raycasting for object placement.

  // Populates some object into an XR scene and sets the initial camera position. The scene and
  // camera come from xr3js, and are only available in the camera loop lifecycle onStart() or later.
  const initXrScene = ({scene, camera, renderer}) => {
    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = THREE.PCFSoftShadowMap

    sceneControl = initScene({scene, camera, renderer})

    /*const light = new THREE.DirectionalLight(0xffffff, 1, 100)
    light.position.set(1, 4.3, 2.5)  // default

    scene.add(light)  // Add soft white light to the scene.*/
    scene.add(new THREE.AmbientLight(0x404040, 5))  // Add soft white light to the scene.

    /*light.shadow.mapSize.width = 1024  // default
    light.shadow.mapSize.height = 1024  // default
    light.shadow.camera.near = 0.5  // default
    light.shadow.camera.far = 500  // default
    light.castShadow = true*/

    surface = new THREE.Mesh(
      new THREE.PlaneGeometry(100, 100, 1, 1),
      new THREE.ShadowMaterial({
        opacity: 0.5,
      })
    )

    plane1 = new THREE.Mesh(
      new THREE.PlaneGeometry(1,1,1,1),
      new THREE.MeshStandardMaterial({color: 0xff0000, wireframe: true})
    );
    scene.add(plane1);

    plane2 = new THREE.Mesh(
      new THREE.PlaneGeometry(1,1,1,1),
      new THREE.MeshStandardMaterial({color: 0x00cc77, wireframe: true})
    );
    scene.add(plane2);


    surface.rotateX(-Math.PI / 2)
    surface.position.set(0, 0, 0)
    surface.receiveShadow = true
    scene.add(surface)

    // Set the initial camera position relative to the scene we just laid out. This must be at a
    // height greater than y=0.
    camera.position.set(0, 3, 0)
  }

  const animateIn = (model, pointX, pointZ, yDegrees) => {
    const scale = {...startScale}

    model.scene.rotation.set(0.0, yDegrees, 0.0)
    model.scene.position.set(pointX, 0.0, pointZ)
    model.scene.scale.set(scale.x, scale.y, scale.z)
    model.scene.children[0].children[0].children[0].castShadow = true
    XR8.Threejs.xrScene().scene.add(model.scene)

    new TWEEN.Tween(scale)
      .to(endScale, animationMillis)
      .easing(TWEEN.Easing.Elastic.Out)  // Use an easing function to make the animation smooth.
      .onUpdate(() => {
        model.scene.scale.set(scale.x, scale.y, scale.z)
      })
      .start()  // Start the tween immediately.
  }

  // Load the glb model at the requested point on the surface.
  const placeObject = (pointX, pointZ) => {
    loader.load(
      modelFile,  // resource URL.
      (gltf) => {
        animateIn(gltf, pointX, pointZ, Math.random() * 360)
      }
    )
  }

  const placeObjectTouchHandler = (e) => {
    // Call XrController.recenter() when the canvas is tapped with two fingers. This resets the
    // AR camera to the position specified by XrController.updateCameraProjectionMatrix() above.
    if (e.touches.length === 2) {
      XR8.XrController.recenter()
    }

    if (e.touches.length > 2) {
      return
    }

    // If the canvas is tapped with one finger and hits the "surface", spawn an object.
    const {camera} = XR8.Threejs.xrScene()

    // calculate tap position in normalized device coordinates (-1 to +1) for both components.
    tapPosition.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1
    tapPosition.y = -(e.touches[0].clientY / window.innerHeight) * 2 + 1

    // Update the picking ray with the camera and tap position.
    raycaster.setFromCamera(tapPosition, camera)

    // Raycast against the "surface" object.
    const intersects = raycaster.intersectObject(surface)

    if (intersects.length === 1 && intersects[0].object === surface) {
      placeObject(intersects[0].point.x, intersects[0].point.z)
    }
  }

  return {
    // Pipeline modules need a name. It can be whatever you want but must be unique within your app.
    name: 'placeground',

    listeners: [
      {event: 'reality.imagefound', process: showTarget},
      {event: 'reality.imageupdated', process: updateTarget},
      {event: 'reality.imagelost', process: hideTarget},
    ],

    // onStart is called once when the camera feed begins. In this case, we need to wait for the
    // XR8.Threejs scene to be ready before we can access it to add content. It was created in
    // XR8.Threejs.pipelineModule()'s onStart method.
    onStart: ({canvas}) => {
      const {scene, camera, renderer} = window.xrScene = XR8.Threejs.xrScene()  // Get the 3js sceen from xr3js.

      // Add objects to the scene and set starting camera position.
      initXrScene({scene, camera, renderer})

      //canvas.addEventListener('touchstart', placeObjectTouchHandler, true)  // Add touch listener.

      // prevent scroll/pinch gestures on canvas
      /*canvas.addEventListener('touchmove', (event) => {
        event.preventDefault()
      })*/

      // Enable TWEEN animations.
      var t = +new Date();
      const animate = (time) => {
        requestAnimationFrame(animate)



        if(!window.sceneControl || !sceneControl.group)
          return;

        if(currentPosition)
          updateWorld(-(t - (t = +new Date())));
        !dst && (dst = document.querySelector('#distance'));

        dst.innerText = 'Distance: '+(camera.position.distanceTo(plane1.position)/plane1.scale.x).toFixed(1)+'\n';

        //o.rotation.y+=0.1*sceneControl.getTime();

        //TWEEN.update(time)
      }

      animate()

      // Sync the xr controller's 6DoF position and camera paremeters with our scene.
      XR8.XrController.updateCameraProjectionMatrix({
        origin: camera.position,
        facing: camera.quaternion,
      })
    },
  }
}
window.wrapz = {
  position: function(p) {
    return p;
  },
  rotation: function(p) {
    return p;
  },
  q: function(p) {
    return p;
  },
  time: function(t) {
    return 0;
  }
};
wrapz.position = (a,o, d)=>{
  var point = o.position.clone();
  var v = new THREE.Vector3(0,-1,-4)
  v.applyEuler(o.rotation);
  point.add(v)
  return point;
}

const onxrloaded = () => {
  XR8.addCameraPipelineModules([  // Add camera pipeline modules.
    // Existing pipeline modules.
    XR8.GlTextureRenderer.pipelineModule(),      // Draws the camera feed.
    XR8.Threejs.pipelineModule(),                // Creates a ThreeJS AR Scene.
    XR8.XrController.pipelineModule(),           // Enables SLAM tracking.
    XRExtras.AlmostThere.pipelineModule(),       // Detects unsupported browsers and gives hints.
    XRExtras.FullWindowCanvas.pipelineModule(),  // Modifies the canvas to fill the window.
    XRExtras.Loading.pipelineModule(),           // Manages the loading screen on startup.
    XRExtras.RuntimeError.pipelineModule(),      // Shows an error image on runtime error.
    // Custom pipeline modules.
    placegroundScenePipelineModule(),
  ])

  // Open the camera and start running the camera run loop.
  XR8.run({canvas: document.getElementById('camerafeed')})
}

// Show loading screen before the full XR library has been loaded.
const load = () => { XRExtras.Loading.showLoading({onxrloaded}) }
window.onload = () => { window.XRExtras ? load() : window.addEventListener('xrextrasloaded', load) }
