import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import {TextGeometry} from 'three/addons/geometries/TextGeometry.js'
import Stats from 'three/addons/libs/stats.module.js';
import createSpheres from './setup'
import spheresAct from './physics'
import {FontLoader} from "three/addons";

const loader = new FontLoader();


const config = {
    gravitationalConstant: 6.67430e-11,
    timeStep: 1 * 3600,
    AU: 149_597_870_700,
    pause: false,
    displayNames: true,
    lines: true,
    currentTime: 1710633600 // Sun Mar 17 2024 00:00:00 GMT+0000
}

const maxPoints = 50_000;
const scene = new THREE.Scene();
const container = document.getElementById( 'container' );
const currentTimeSpan = document.getElementById( 'currentTime' );
const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
const stats = new Stats();
container.appendChild( stats.dom );
const camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 150 * config.AU);

const controls = new OrbitControls(camera, renderer.domElement);
const texts = [];
let lines = {}

let axesHelper;
const spheres = createSpheres();
const initialSpheres = JSON.parse(JSON.stringify(spheres));
loader.load(
    // resource URL
    "https://threejs.org/examples/fonts/helvetiker_regular.typeface.json", function(font) {
        initScene(font);
        animate()
    })


function animate() {
    requestAnimationFrame( animate );
    controls.update();
    stats.update();
    render();
}

function resetTrail(sphereConfig) {
    sphereConfig.line.material.dispose();
    sphereConfig.line.geometry.dispose();
    sphereConfig.group.remove(sphereConfig.line)
    scene.remove(sphereConfig.line)
}

function reset() {
    config.currentTime = 1710633600;
    spheres.spheres.forEach(sphereConfig => {
        const oldSphere = initialSpheres.spheres.find((sphere => sphere.name === sphereConfig.name))
        sphereConfig.x = oldSphere.x;
        sphereConfig.y = oldSphere.y;
        sphereConfig.z = oldSphere.z;
        sphereConfig.vx = oldSphere.vx;
        sphereConfig.vy = oldSphere.vy;
        sphereConfig.vz = oldSphere.vz;
        sphereConfig.group.position.x = sphereConfig.x;
        sphereConfig.group.position.y = sphereConfig.y;
        sphereConfig.group.position.z = sphereConfig.z;
        resetTrail(sphereConfig);
    })
    lines = {}
}

function render() {
    act()
    renderer.render(scene, camera);
}

function formatDateTime(input) {
    let epoch = new Date(0);
    epoch.setSeconds(parseInt(input));
    let date = epoch.toISOString();
    date = date.replace('T', ' ');
    return date.split('.')[0].split(' ')[0];
}


function act() {
    axesHelper.position.copy(controls.target);
    texts.forEach(text => {
        text.lookAt(camera.position)
    })
    if(config.pause) {
        return;
    }
    spheresAct(spheres.spheres, config.timeStep)
    config.currentTime += config.timeStep;
    currentTimeSpan.innerText = formatDateTime(config.currentTime)
    if(config.lines) {
        spheres.spheres.forEach(sphere => {
            if(!(sphere.name in lines)) {
                lines[sphere.name] = []
                const positions = new Float32Array(maxPoints * 3); // 3 vertices per point
                const geometry = new THREE.BufferGeometry();
                geometry.setDrawRange(0, 0);
                geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
                const material = new THREE.LineBasicMaterial( { color: getSphereColor(sphere) } );
                const line = new THREE.Line(geometry, material);
                scene.add(line);
                sphere.line = line;
            }
            const pos = {
                x: sphere.x,
                y: sphere.y,
                z: sphere.z
            }
            const positionAttribute = sphere.line.geometry.getAttribute( 'position' );
            lines[sphere.name].push(pos);
            let currentLength = lines[sphere.name].length - 1;
            positionAttribute.setXYZ(currentLength, sphere.x, sphere.y, sphere.z);
            sphere.line.geometry.setDrawRange(0, currentLength);
            positionAttribute.needsUpdate = true;
        })
    }
    spheres.spheres.forEach(sphere => {
        sphere.group.position.x = sphere.x;
        sphere.group.position.y = sphere.y;
        sphere.group.position.z = sphere.z;
    })
}

function getSphereColor(sphereConfig) {
    return new THREE.Color(sphereConfig.color.r / 255, sphereConfig.color.g / 255, sphereConfig.color.b / 255);
}

function resetControls() {
    const sun = spheres.sphereObj.sun;
    camera.position.set(sun.x, sun.y, sun.z + config.AU)
    camera.lookAt(sun.x, sun.y, sun.z)
    controls.target.set(sun.x, sun.y, sun.z)
    controls.update();
    axesHelper.position.copy(controls.target);
}

function initScene(font) {
    const geometry = new THREE.SphereGeometry(0.5, 24, 12);
    let scale = config.AU / 10;
    spheres.spheres.forEach(sphereConfig => {

        let sphereColor = getSphereColor(sphereConfig);
        const material = new THREE.MeshBasicMaterial({color: sphereColor});

        const sphere = new THREE.Mesh(geometry, material);
        const group = new THREE.Group();
        group.position.x = sphereConfig.x;
        group.position.y = sphereConfig.y;
        group.position.z = sphereConfig.z;
        let objectRadius = Math.log(sphereConfig.radius) * 100000000 / 5;
        sphere.scale.multiplyScalar(objectRadius)
        group.add(sphere);

        const label = new TextGeometry(sphereConfig.name, {
            size: 0.25,
            font: font,
            height: 0.25
        });

        let textDistance = 2;
        let isMoon = sphereConfig.isMoon;
        if (isMoon) {
           textDistance = 5;
        }
        const textMesh = new THREE.Mesh(label, material);
        textMesh.scale.set( scale, scale, scale );
        textMesh.position.x = textDistance * objectRadius * (Math.random() * textDistance -  textDistance) + Math.random() * config.AU / 10;
        textMesh.position.y = textDistance * objectRadius * (Math.random() * textDistance -  textDistance) + Math.random() * config.AU / 10;
        if(!isMoon) {
            textMesh.renderOrder = 999;
            textMesh.material.depthTest = false
        }
        texts.push(textMesh)
        group.add(textMesh);
        sphereConfig.text = textMesh;
        sphereConfig.group = group;
        sphereConfig.sphereColor = sphereColor;

        const labelLine = [];
        labelLine.push(new THREE.Vector3(textMesh.position.x, textMesh.position.y, textMesh.position.z));
        labelLine.push(new THREE.Vector3(0, 0, 0 ));
        const lineMaterial = new THREE.LineBasicMaterial({color: sphereColor});
        const lineGeometry = new THREE.BufferGeometry().setFromPoints(labelLine);
        const line = new THREE.Line( lineGeometry, lineMaterial);
        sphereConfig.labelLine = line;
        group.add(line);
        scene.add(group);
    })

    scene.add(new THREE.AmbientLight( 0x777777 ) );

    const light = new THREE.DirectionalLight( 0xffffff, 3 );
    const sun = spheres.sphereObj.sun
    light.position.set(sun.x, sun.y, sun.z);
    scene.add(light);
    scene.position.set(sun.x, sun.y, sun.z)
    axesHelper = new THREE.AxesHelper(config.AU / 5);
    resetControls();
    scene.add(axesHelper)
}

function setTrailToNow() {
    spheres.spheres.forEach(sphereConfig => {
        resetTrail(sphereConfig);
    })
    lines = {}
}

function toggleNames() {
    config.displayNames = !config.displayNames;
    spheres.spheres.forEach(sphereConfig => {
        sphereConfig.text.visible = config.displayNames;
        sphereConfig.labelLine.visible = config.displayNames;
    })
}

function keyPress(event) {
    let keyCode = event.which;
    if(keyCode === 82) { // R
        reset()
    } else if(keyCode === 70) { // F
        resetControls()
    } else if(keyCode === 80) { // P
        config.pause = !config.pause;
    } else if(keyCode === 84) { // T
        setTrailToNow()
    } else if(keyCode === 78) { // N
        toggleNames()
    }
}

document.addEventListener("keydown", keyPress, false);


window.addEventListener("resize", onWindowResize, false);

function onWindowResize(){
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
}

