import * as THREE from 'three';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { Font } from 'three/examples/jsm/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
import { lerp, randFloat } from 'three/src/math/MathUtils.js';
import { OrbitControls } from 'three/examples/jsm/Addons.js';
import { depth } from 'three/examples/jsm/nodes/Nodes.js';

class PickHelper {

	constructor() {
	  this.raycaster = new THREE.Raycaster();
	  this.pickedObject = null;
	  this.lastObjectPicked = null;
	  this.sameObjectPicked = false;
	}

	pick(normalizedPosition, scene, camera, time) {
	  // restore the color if there is a picked object
	  if (this.pickedObject) {
		this.lastObjectPicked = this.pickedObject;
		this.sameObjectPicked = false;
		this.pickedObject = undefined;
	  }
   
	  // cast a ray through the frustum
	  this.raycaster.setFromCamera(normalizedPosition, camera);
	  // get the list of objects the ray intersected
	  const intersectedObjects = this.raycaster.intersectObjects(scene.children);
	  if (intersectedObjects.length) {
		// pick the first object. It's the closest one
		for (let i = 0; i < intersectedObjects.length; i++){

			if(intersectedObjects[i].object.geometry.type != "SphereGeometry"){
				this.pickedObject = intersectedObjects[i].object;
				if(intersectedObjects[i].object == this.lastObjectPicked)
					this.sameObjectPicked = true;
			}
		}	
		
		if (this.sameObjectPicked)
			this.pickedObject = this.lastObjectPicked;
	  }
	}

}

class Article {

	constructor(texture, title, filename, id){
		this.geom = new THREE.TetrahedronGeometry(1, 2);
		this.material = new THREE.MeshLambertMaterial({map: texture});
		this.mesh = new THREE.Mesh(this.geom, this.material);
		this.html = filename;
		this.name = title;
		this.id = id;
		this.hover = false;
		this.hoverScale = false;
		this.hoverLerpTime = 0;
		this.speed = randFloat(1.5, 2);
		this.rotationSpeed = randFloat(0.5, 1.5);
		this.scale = 1;
	}

	AddToScene(scene, aspect){
		this.mesh.position.x = (Math.random() - 0.5) * 8 * aspect;
		this.mesh.position.y = 8 + Math.random() * 15;
		scene.add(this.mesh);
	}

	UpdateRotation(time){
		this.mesh.rotation.x += this.rotationSpeed * time;
		this.mesh.rotation.y += this.rotationSpeed * time;
	}

	UpdatePosition(time, picker){
		this.BoundsCheck();
		this.HoverCheck(picker, time)
		if (!this.hover)
			this.mesh.position.y -= this.speed * time;
	}

	BoundsCheck(){
		if (this.mesh.position.y < -8) {
			const aspect = window.innerWidth / (window.innerHeight - 100);
			this.mesh.position.y = 8 + (Math.random() * 3);
			this.mesh.position.x = (Math.random() - 0.5) * 8 * aspect;
			this.speed = randFloat(1.5, 2)
		}
	}

	UpdateScale(multipler){
		this.mesh.scale.set(multipler, multipler, multipler);
	}

	HoverCheck(picker, time){
		const hoverIncrement = 0.01;
		const offHoverDecrement = 0.01;
	
		if (picker.pickedObject === this.mesh) {
			this.hover = true;
			if (this.hoverLerpTime < 1) {
				this.hoverLerpTime += hoverIncrement;
			}
			if (this.hoverLerpTime > 1) {
				this.hoverLerpTime = 1;
			}
			this.scale = lerp(this.scale, 2, this.hoverLerpTime);
		} else {
			this.hover = false;
			if (this.hoverLerpTime > 0) {
				this.hoverLerpTime -= offHoverDecrement;
			}
			if (this.hoverLerpTime < 0) {
				this.hoverLerpTime = 0;
			}
			this.scale = lerp(this.scale, 1, 1 - this.hoverLerpTime);
		}
	
		this.UpdateScale(this.scale);
	}

	


}

let scene, camera, renderer, cube;
let texture, planeMat, mesh, moloch_txt;
let lastTime = 0; // Keep track of the last frame time
let textGeo, textWidth, textMaterial, textMesh;
let text_Geometries = [];
let isHovering = false;
const pickPosition = {x: 0, y: 0};
const pickHelper = new PickHelper();
const object_list = []
const object_count = 20;
const fontLoader = new FontLoader();

function init() {
  
  // Texture Loader 
  const loader = new THREE.TextureLoader();
  texture = loader.load('/images/website/checker.png');

  // Create a renderer and attach it to our document
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.shadowMap.enabled = true;
  renderer.setSize(window.innerWidth, window.innerHeight - 100);
  document.getElementById('container').appendChild(renderer.domElement);
  document.getElementById('container').style.overflowY = 'hidden';

  // Create the scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color('black');

  // Camera Setup
  const aspect = window.innerWidth / (window.innerHeight - 100); // Adjust for nav height
  const frustumSize = 10;
  camera = new THREE.OrthographicCamera(
  	frustumSize * aspect / -2,
  	frustumSize * aspect / 2,
  	frustumSize / 2,
  	frustumSize / -2,
  	0.1,
  	5000
  );
  
  //const controls = new OrbitControls(camera, renderer.domElement);

  camera.position.z = 20;

  // Fetch JSON data
  fetch('../json/articles.json')
  .then(response => response.json())
  .then(jsonData => {
	for (let i = 0; i < jsonData.length; i++) {
		let temp_txt = loader.load('../images/' + jsonData[i]['image']);
		temp_txt.minFilter = THREE.NearestFilter;
		let title = jsonData[i]['name']
		let filename =  jsonData[i]['filename']
		let article = new Article(temp_txt, title, filename, i);
		article.AddToScene(scene, aspect);
		object_list.push(article);
	  }
  })

  

  // Plane Setup
  {
	const planeSize = 40;

	
	texture.wrapS = THREE.RepeatWrapping;
	texture.wrapT = THREE.RepeatWrapping;
	texture.magFilter = THREE.NearestFilter;
	texture.colorSpace = THREE.SRGBColorSpace;
	const repeats = planeSize / 1;
	texture.repeat.set(repeats, repeats);

	const planeGeo = new THREE.SphereGeometry(10);
	planeMat = new THREE.MeshPhongMaterial({
		map: texture,
		side: THREE.DoubleSide,
	});

	mesh = new THREE.Mesh(planeGeo, planeMat);
	mesh.position.z = -20
	scene.add(mesh);
  }

  // Light Setup
  {
    const color = 0xFFFFFF;
    const intensity = 3;
    const light = new THREE.DirectionalLight(color, intensity);
    light.castShadow = true;
    light.position.set(1, 1, 10);
    light.target.position.set(-0, 0, -0);
    light.shadow.camera.top = 25;
    light.shadow.camera.bottom = -25;
    light.shadow.camera.left = -25;
    light.shadow.camera.right = 25;
    light.shadow.camera.zoom = 1;
    scene.add(light);
    scene.add(light.target);
  }

  // Start with an initial timestamp
  animate(0); 
}

function animate(time) {
	requestAnimationFrame(animate);

	// Calculate the time elapsed since the last frame
	const deltaTime = (time - lastTime) / 1000; // Convert time to seconds
	lastTime = time;

	pickHelper.pick(pickPosition, scene, camera, time);

	for (let i = 0; i < object_list.length; i++){
		object_list[i].UpdateRotation(deltaTime);
		object_list[i].UpdatePosition(deltaTime, pickHelper);
	}
  
	// Update the plane texture offset
	const scrollSpeed = 0.2;
	planeMat.map.offset.y += scrollSpeed * deltaTime;
	planeMat.map.offset.x += scrollSpeed / 0.75 * deltaTime;
	
	ChangeCursor();
	// Render the scene from the perspective of the camera
	renderer.render(scene, camera);
}

// Handle window resize
window.addEventListener('resize', () => {
  const aspect = window.innerWidth / (window.innerHeight - 100);
  const frustumSize = 10;
  camera.left = -frustumSize * aspect / 2;
  camera.right = frustumSize * aspect / 2;
  camera.top = frustumSize / 2;
  camera.bottom = -frustumSize / 2;
  camera.updateProjectionMatrix();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight - 100);
});



  function getCanvasRelativePosition(event) {
	const rect = document.querySelector('#container').getBoundingClientRect();
	return {
	  x: (event.clientX - rect.left) * window.innerWidth  / rect.width,
	  y: (event.clientY - rect.top ) * (window.innerHeight - 100) / rect.height,
	};
  }
   
  function setPickPosition(event) {
	const pos = getCanvasRelativePosition(event);
	pickPosition.x = (pos.x / window.innerWidth ) *  2 - 1;
	pickPosition.y = (pos.y / ( window.innerHeight-100 ) ) * -2 + 1;  // note we flip Y
  }
   
  function clearPickPosition() {
	// unlike the mouse which always has a position
	// if the user stops touching the screen we want
	// to stop picking. For now we just pick a value
	// unlikely to pick something
	pickPosition.x = -100000;
	pickPosition.y = -100000;
  }

  function objectClicked(event) {
    if (pickHelper.pickedObject) {
        // Find the corresponding Article object
        const pickedArticle = object_list.find(article => article.mesh === pickHelper.pickedObject);
        if (pickedArticle) {
            window.location.href = pickedArticle.html;
        }
    }
  }

  function ChangeCursor(){
	const pickedArticle = object_list.find(article => article.mesh === pickHelper.pickedObject);
        if (pickedArticle) {
			document.body.style.cursor = 'pointer';
			if (!isHovering)
				UpdateText(pickedArticle.name);
        }else{
			document.body.style.cursor = 'default';
			ClearTextGeoList();
			isHovering = false
		}
  }

  function UpdateText(text) {
    MeasureText(text);
	isHovering = true
}

function ClearTextGeoList() {
    for (let i = 0; i < text_Geometries.length; i++) {
        scene.remove(text_Geometries[i]);
    }
    text_Geometries.length = 0;
}

function MeasureText(text) {
    fontLoader.load('fonts/Redaction 50_Regular.json', (font) => {
        let initialFontSize = 1;
        if (window.innerWidth < 1024) {
            initialFontSize = 0.5;
        } else if (window.innerWidth < 512) {
            initialFontSize = 0.25;
        }

        const aspect = window.innerWidth / (window.innerHeight - 100); // Adjust for nav height
        const frustumSize = 10;
        const orthoWidth = (frustumSize * aspect / 2) - (frustumSize * aspect / -2);

        function createTextGeometry(text, size) {
            return new TextGeometry(text, {
                height: 2,
                depth: 1,
                font: font,
                size: size
            });
        }

        // Split text into words
        const split = text.split(" ");
        const word_count = split.length;
        const sentences = [];

        let currentText = "";
        let currentFontSize = initialFontSize;
        let textGeo;

        for (let i = 0; i < word_count; i++) {
            const testText = currentText + (currentText ? " " : "") + split[i];
            textGeo = createTextGeometry(testText, currentFontSize);
            textGeo.computeBoundingBox();
            const proportion = textGeo.boundingBox.max.x / orthoWidth;

            if (proportion > 0.8) {
                if (currentText) {
                    sentences.push(currentText);
                }
                currentText = split[i];
            } else {
                currentText = testText;
            }
        }
        if (currentText) {
            sentences.push(currentText);
        }

        ClearTextGeoList();

        const numSentences = sentences.length;
        const totalHeight = (numSentences - 1) * (1.5 * currentFontSize);
        const startY = totalHeight / 2;

        for (let i = 0; i < sentences.length; i++) {
            textGeo = createTextGeometry(sentences[i], currentFontSize);
            textGeo.computeBoundingBox();
            const proportion = textGeo.boundingBox.max.x / orthoWidth;

            if (proportion > 0.8) {
                currentFontSize *= 0.8 / proportion;
                textGeo = createTextGeometry(sentences[i], currentFontSize);
                textGeo.computeBoundingBox();
            }

            const textMaterial = new THREE.MeshNormalMaterial();
            const textMesh = new THREE.Mesh(textGeo, textMaterial);

            const centerOffsetX = (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x) / 2;
            const centerOffsetY = (textGeo.boundingBox.max.y - textGeo.boundingBox.min.y) / 2;
            const centerOffsetZ = (textGeo.boundingBox.max.z - textGeo.boundingBox.min.z) / 2;

            textGeo.translate(-centerOffsetX, -centerOffsetY, -centerOffsetZ);
            textMesh.rotation.x = Math.PI / 2 * 0.05;
            textMesh.position.y = startY - (i * (1.5 * currentFontSize));
            textMesh.position.z = 5;
            text_Geometries.push(textMesh);
        }

        for (let i = 0; i < text_Geometries.length; i++) {
            scene.add(text_Geometries[i]);
        }
    });
}
   
  window.addEventListener('mousemove', setPickPosition);
  window.addEventListener('mouseout', clearPickPosition);
  window.addEventListener('mouseleave', clearPickPosition);
  window.addEventListener('click', objectClicked)

  // Add touch event listeners
	window.addEventListener('touchstart', onTouchStart, {passive: false});
	window.addEventListener('touchmove', onTouchMove, {passive: false});
	window.addEventListener('touchend', onTouchEnd, {passive: false});
	window.addEventListener('touchcancel', clearPickPosition);

	let touchStartTime;
	const touchHoldDuration = 500; // Duration in milliseconds to distinguish between tap and hold

	function onTouchStart(event) {
		touchStartTime = Date.now();
		setPickPosition(event.touches[0]);
	}

	function onTouchMove(event) {
		setPickPosition(event.touches[0]);
	}

	function onTouchEnd(event) {
		const touchDuration = Date.now() - touchStartTime;
		clearPickPosition();
		if (touchDuration < touchHoldDuration) {
			// It's a tap
			objectClicked(event);
		} else {
			// It's a hold
			// Do nothing extra, as hover effect should already be handled by setPickPosition
		}
	}

// Initialize the application
init();
