import * as THREE from 'three';

const RELEASE_NOTES = `
<p><b>v1.2 - 18/1/2026</b></p>

<p>Added a second visualisation of the 120-cell's 5-cells without the 120-cell links and with more colours added so you can get a sense of the individual 5-cells.</p>

<p><b>v1.1 - 1/1/2026</b></p>

<p>The 120-cell now includes a visualisation of its inscribed 5-cells, which honestly
looks like less of a mess than I expected it to.</p>

<p><b>v1.0 - 16/11/2025</b></p>

<p>It's been <a target="_blank" href="https://mikelynch.org/2023/Sep/02/120-cell/">two years</a> since
I first made this, and I haven't updated it in a while, but I got tapered links to
work without too much performance overhead, so that seemed worth a version.</p>

<p>The results flicker a bit at low opacities but otherwise I'm pretty happy with
it.</p>
`;



import * as POLYTOPES from './polytopes.js';
import { rotfn } from './rotation.js';
import { FourDGUI, DEFAULTS } from './gui.js';
import { FourDShape } from './fourDShape.js';
import { get_colours } from './colours.js';

const FACE_OPACITY = 0.3;
const CAMERA_K = 5;

// scene, lights and camera

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const light = new THREE.PointLight(0xffffff, 2);
light.position.set(10, 10, 10);
scene.add(light);
const light2 = new THREE.PointLight(0xffffff, 2);
light2.position.set(-10, 5, 10);
scene.add(light);
const amblight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(amblight);

camera.position.set(0, 0, CAMERA_K / 2);

camera.lookAt(0, 0, 0);
//camera.position.z = 4;

const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );

renderer.localClippingEnabled = true;


document.body.appendChild( renderer.domElement );

// set up colours and materials for gui callbacks

scene.background = new THREE.Color(DEFAULTS.background);
const node_colours = get_colours(DEFAULTS.colour);


const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c}));
const link_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c}));

node_ms.map((m) => {
	m.transparent =  true;
	m.opacity = 1.0;
	}
);

link_ms.map((m) => {
	m.transparent =  true;
	m.opacity = 0.5;
	}
);



const face_ms = [
	new THREE.MeshStandardMaterial( { color: 0x44ff44 } )
	];

for( const face_m of face_ms ) {
	face_m.transparent = true;	
	face_m.opacity = FACE_OPACITY;
}


const STRUCTURES = POLYTOPES.build_all();

const STRUCTURES_BY_NAME = {};

STRUCTURES.map((s) => STRUCTURES_BY_NAME[s.name] = s);

let shape = false;
let structure = false;
let node_show = [];
let link_show = []; 


function createShape(name, option) {
	if( shape ) {
		scene.remove(shape);
	}
	structure = STRUCTURES_BY_NAME[name];
	shape = new FourDShape(node_ms, link_ms, face_ms, structure);
	scene.add(shape);
	setVisibility(option ? option : structure.options[0].name);
}

function displayDocs(name) {
	const docdiv = document.getElementById("description");
	const description = STRUCTURES_BY_NAME[name].description;
	if( description ) {
		docdiv.innerHTML =`<p>${name}</p><p>${description}</p>`;
	} else {
		docdiv.innerHTML =`<p>${name}</p>`;
	}
}

function showDocs(visible) {
	const docdiv = document.getElementById("description");
	if( visible ) {
		docdiv.style.display = '';
	} else {
		docdiv.style.display = 'none';
	}
}

function releaseNotes() {
    showDocs(false);
    const reldiv = document.getElementById("release_notes");
    reldiv.style.display = '';
    reldiv.innerHTML = RELEASE_NOTES + '<p><a id="no_notes" href="#">[hide]</a>';
    const goaway = document.getElementById("no_notes");
    goaway.addEventListener('click', noNotes);
    }

function noNotes() {
    	const reldiv = document.getElementById("release_notes");
 	reldiv.style.display = 'none';
}

const relnotes = document.getElementById('show_notes');

relnotes.addEventListener('click', releaseNotes);


// initialise gui and read params from URL

// callbacks to do things which are triggered by controls: reset the shape,
// change the colors.  Otherwise we just read stuff from gui.params.

function setColours(c) {
    const nc = get_colours(c);
    for( let i = 0; i < node_ms.length; i++ ) {
    	node_ms[i].color = new THREE.Color(nc[i]);
    	link_ms[i].color = new THREE.Color(nc[i]);
    }
    if( shape ) {
        // taperedLink.set_color updates according to the link index
       shape.links.map((l) => l.object.set_colour(nc));
    }
}

function setBackground(c) {
	scene.background = new THREE.Color(c)
}

// taperedLinks have their own materials so we have to set opacity
// on them individually. And also set the base materials as they
// will get updated from it when the shape changes 

function setLinkOpacity(o, primary) {
    link_ms.map((lm) => lm.opacity = o);
    if( shape ) {
        shape.links.map((l) => {
            if( (primary && l.label == 0) || (!primary && l.label !== 0) ) {
                l.object.material.opacity = o
            }
        });
    }
}

function setNodeOpacity(o) {
	node_ms.map((nm) => nm.opacity = o);
}


let gui; 


function changeShape() {
	createShape(gui.params.shape);
	displayDocs(gui.params.shape);
}

function setVisibility(option_name) {
	const option = structure.options.filter((o) => o.name === option_name);
	if( option.length ) {
		node_show = option[0].nodes;
		link_show = option[0].links;
	} else {
		console.log(`Error: option '${option_name}' not found`);
	}
}


gui = new FourDGUI(
    {
	shapes: STRUCTURES,
	changeShape: changeShape,
	setColours: setColours,
	setBackground: setBackground,
	setNodeOpacity: setNodeOpacity,
	setLinkOpacity: setLinkOpacity,
	setVisibility: setVisibility,
	showDocs: showDocs,
    }
);

// these are here to pick up colour settings from the URL params
setColours(gui.params.colour);
setBackground(gui.params.background);

const dragK = 0.005;
const damping = 0.99;

let theta = 0;
let psi = 0;
let theta0 = 0;
let psi0 = 0;
let dragx0 = 0;
let dragy0 = 0;
let dragging = false;


renderer.domElement.addEventListener("pointerdown", (event) => {
	if( event.buttons === 1 ) {
		theta0 = theta;
		psi0 = psi;
		dragx0 = event.clientX;
		dragy0 = event.clientY;
		dragging = true;
	}
})

renderer.domElement.addEventListener("pointermove", (event) => {
	if( event.buttons === 1 ) {
		const theta1 = theta0 + (event.clientX - dragx0) * dragK;
		const psi1 = psi0 + (event.clientY - dragy0) * dragK;
		gui.params.dtheta = theta1 - theta;
		gui.params.dpsi = psi1 - psi;
		theta = theta1;
		psi = psi1;
	}
})

renderer.domElement.addEventListener("pointerup", (event) => {
	dragging = false;
})

createShape(gui.params.shape, gui.params.option);
displayDocs(gui.params.shape);

function animate() {
	requestAnimationFrame( animate );

	if( ! dragging ) {
		theta += gui.params.dtheta;
		psi += gui.params.dpsi;
		if( gui.params.damping ) {
			gui.params.dtheta = gui.params.dtheta * damping;
			gui.params.dpsi = gui.params.dpsi * damping;
		}
	}

	const rotations = [
		rotfn[gui.params.xRotate](theta), 
		rotfn[gui.params.yRotate](psi)
	];
	shape.hyperplane = 1 / gui.params.hyperplane;
	camera.position.set(0, 0, gui.params.zoom * CAMERA_K * gui.params.hyperplane);

	shape.node_scale = gui.params.nodesize;
	shape.link_scale = gui.params.linksize * gui.params.nodesize * 0.5;
	shape.render3(rotations, node_show, link_show);



	renderer.render( scene, camera );
}
animate();
