class Tween {
    startTime = 0;
    currentTime = 0;
    progress = 0;
    duration = 1;
    startValue = 0;
    currentValue = 0;
    endValue = 1;
    position = 0;
    delay = 0;
    enableDebug = false;

    


    onValueUpdated = (x)=>{};
    onComplete = ()=>{};

    constructor(startValue, endValue, duration, onUpdate) {
        this.startValue = startValue;
        this.endValue = endValue;
        this.duration = duration;
        if(onUpdate != null) {
            this.onValueUpdated = onUpdate;
        }
    }

    easingFunction = easeInOutSine;
    addTime(t) {
        if(this.delay>0)
        {
            this.delay -= t;
        }
        if(this.delay > 0) {
            return;
        }
        if(this.delay < 0) {
            t = -this.delay;
        }
        this.currentTime = Math.min(this.currentTime + t, this.duration);
        this.progress = this.currentTime/this.duration;
        this.position = this.easingFunction(this.progress);
        var newValue = this.startValue * (1-this.position) + this.endValue * this.position;        
        if(newValue != this.currentValue) {
            if(this.enableDebug) {
                //console.log(`currentTime: ${this.currentTime}`)
                //console.log(`progress: ${this.progress}`);
                //console.log(`position: ${this.position}`);
                console.log(`newValue: ${newValue} startValue: ${this.startValue}  endValue: ${this.endValue} position: ${this.position}`)
                //console.log(`new value: ${this.newValue}`);
            }
            this.currentValue = newValue;
            if(this.onValueUpdated != null)
            {
                this.onValueUpdated(this.currentValue);
            }
            if(this.isFinished()) {
                this.onComplete();
            }
        }
    }


    cancel() {
        this.endValue = this.currentValue;
        this.startValue = this.currentValue;
    }

    getValue() {
        return this.currentValue;
    }

    isFinished() {
        return this.currentValue == this.endValue;
    }
}

window.onload = start;
var renderer, scene, camera;
var frame = 0;
var tweenList = [];
var zoomTween;
var fovTween;
var dollyTween;
var model;
var uvCoords;
var rotateTween;
var material;
var power = -1.226;
var multiplier = 168;
var rotateTexture = false;
var tiltTexture = false;



function easeInOutSine(x) {
    return -(Math.cos(Math.PI * x) - 1) / 2;
}

function easeInOutCirc(x) {
    return x < 0.5
    ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2
    : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2;
}

function easeOutCubic(x) {
    return 1 - Math.pow(1 - x, 3);
}

function easeInExpo(x) {
    return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
}

var lastTime = new Date().getTime();
function animate() {

    var currentTime = new Date().getTime();
    timePassed = currentTime - this.lastTime;
    var timePassed = (currentTime - lastTime) /1000;
    this.lastTime = currentTime;
    for(var ti=tweenList.length-1;ti>=0;ti--) {
        var t = tweenList[ti];
        t.addTime(timePassed);
        if(t.isFinished()) {
            tweenList.splice(ti,1)
        }
    }

    ++frame;
  renderer.render( scene, camera );
}


var camearZTween, cameraYTween;

function onCameraZPositionChanged(x) {
    var newZPosition = parseFloat(document.getElementById('cameraZPositionSlider').value);    
    applyCameraZPosition(newZPosition);
    document.getElementById('fine-control').checked = false;

}

function onCameraZPositionFineChanged(x) {
    var newZPosition = parseFloat(document.getElementById('cameraZPositionFineSlider').value);        
    applyCameraZPosition(newZPosition);
    document.getElementById('fine-control').checked = true;
}

function applyCameraZPosition(newZPosition) {
    document.getElementById('zPositionText').innerText = newZPosition;
    if(camearZTween != null) {
        camearZTween.cancel();
    }
    if(fovTween) { fovTween.cancel();}
    if(dollyTween) { dollyTween.cancel(); }
    
    var oldPosition = camera.position.z;


    
    if(isDollyZoomEnabled) {
        var newFOV = 360 * (2 * Math.asin((subjectWidth/2)/newZPosition))/Math.PI;        
        var oldFOV = camera.fov;
        
        console.log(`dolly zoom to z: ${newZPosition} fov: ${newFOV}`);
        var dollyTween = new Tween(0,1,0.6,(t)=>{
            camera.position.z = oldPosition * (1-t) + newZPosition * t;
            camera.fov = 360 * (2 * Math.asin((subjectWidth/2)/camera.position.z))/Math.PI;        
            camera.updateProjectionMatrix();
        });
        tweenList.push(dollyTween);
        dollyTween.onComplete = ()=>{updateUI();}
        /*
        fovTween = new Tween(camera.fov, newFOV, 0.6, (fov) => {
            camera.fov = fov; 
            camera.updateProjectionMatrix(); 
        });
        fovTween.onComplete = ()=>{updateUI();}
        fovTween.easingFunction = easeOutCubic;
        tweenList.push(fovTween);        
        */
    } else  if(isPowerZoomEnabled) {
        fovTween = new Tween(0,1,0.6,(t)=>{
            camera.position.z = oldPosition * (1-t) + newZPosition * t
            
            console.log(`power zoom to z: ${camera.position.z}`);
            //var newFOV = Math.pow(camera.position.z, -1.247) * 148.57;
            var newFOV = Math.pow(camera.position.z, power) * multiplier;
            console.log(` input distance: ${camera.position.z} new fov: ${newFOV}`);
            camera.fov = newFOV;
            camera.updateProjectionMatrix();
        });
        tweenList.push(fovTween);
        fovTween.onComplete = ()=>{updateUI();}

    } else
    {
        camearZTween = new Tween(camera.position.z, newZPosition, 0.6, (z)=>{
            camera.position.z = z;
            camera.updateProjectionMatrix();        
        });
        camearZTween.onComplete = ()=>{updateUI();}
        tweenList.push(camearZTween);

    }
    document.getElementById('coarse-contro').checked = true;
}


function onCameraYPositionChanged(x) {
    var newYPosition = parseFloat(document.getElementById('cameraYPositionSlider').value);
    document.getElementById('yPositionText').innerText = newYPosition;
    
    if(cameraYTween != null) {
        cameraYTween.cancel();
    }
    cameraYTween = new Tween(camera.position.y, newYPosition, 0.6, (y) => {
        camera.position.y = y;
        camera.updateProjectionMatrix();
    })
    tweenList.push(cameraYTween);
}


function onCameraZoomChanged(x) {
    var cameraFOV = parseFloat(document.getElementById('cameraFOV').value);
    document.getElementById('fovText').innerText = cameraFOV;
    if(zoomTween != null) {
        zoomTween.cancel();
    }
    //camera.fov = cameraFOV;
    camera.updateProjectionMatrix(); 
    zoomTween = new Tween(camera.fov, cameraFOV,0.6,  (x) => {
        camera.fov = x;
        camera.updateProjectionMatrix(); 
    });
    tweenList.push(zoomTween);
 
}

var tiltTween;

function onGlobeTiltChanged(x) {
    var tiltRotation = parseFloat(document.getElementById('globeTilt').value);
    document.getElementById('tiltText').innerText = `${(tiltRotation/Math.PI).toFixed(3)}`;
     if(rotateTexture || tiltTexture) {
        rotateCoordinates();
        if(tiltTexture) {
            return;
        }
    }    
    if(tiltTween != null) {
        tiltTween.cancel();
    }
    tiltTween = new Tween(model.rotation.x, tiltRotation, 0.66,  (  z  )=>
    {
        model.rotation.x =   z  ; // theta 
    });
    tweenList.push(tiltTween);   
}


function rotateCoordinates() {
    var newCoords = uvCoords.clone();
    var globeRotation = parseFloat(document.getElementById('globeRotation').value);
    var tiltRotation = parseFloat(document.getElementById('globeTilt').value);
    //TO DO: rotate the UV coordinates based on the current rotation of the
    // model.
    for(var i=0;i<newCoords.count; ++i) {
        
        var u = newCoords.getX(i);
        var v = newCoords.getY(i);      
        if(rotateTexture) {
            u = (u + globeRotation/(2*Math.PI)) % 1.0;
        }
        if(tiltTexture) {
            v = (v - tiltRotation/Math.PI) % 1.0;
        }
        if(v<0) v += 1.0;
        if(u<0) u += 1.0;
        newCoords.setXY(i, u, v);
    }
    model.geometry.setAttribute('uv', newCoords);
    model.geometry.attributes.uv.needsUpdate = true;

}
function onGlobeRotateChanged(x) {
    var globeRotation = parseFloat(document.getElementById('globeRotation').value);
    document.getElementById('rotationText').innerText = `${(globeRotation/Math.PI).toFixed(3)}`;

    if(rotateTexture||tiltTexture) {
        rotateCoordinates();
        if(rotateTexture) {
            return;
        }
    }
    if(rotateTween != null) {
        rotateTween.cancel();
    }
    rotateTween = new Tween(model.rotation.y, globeRotation, 0.33, (x) => { model.rotation.y = x; })
    tweenList.push(rotateTween);
}

function onGlobeScaleChanged(x) {
    var newScale = parseFloat(document.getElementById('globeScale').value);
    document.getElementById('globeScaleText').innerText = newScale.toFixed(2);
    model.scale.set(newScale, newScale, newScale);
}

function onRotationOrderChanged(x) {
    var rotationOrder = document.getElementById('rotationOrder').value;
    model.rotation.order = rotationOrder;
}

function onMapSelectionChanged() {
    var newMapName = document.getElementById('mapImage').value;
    var newMapUrl = `images/${newMapName}`;
    const texture = new THREE.TextureLoader().load(newMapUrl);
    material.map = texture;
    material.needsUpdate =true;
}


function applyPreset(p) {
const presets = {
    Africa: {
        distance: 1854,
        fov: 0.064,
        tilt:0.00641,
        rotation: -1.76159,
        scale: 1
    },
    Italy: {
        distance: 2000,
        fov: 0.015,
        tilt:0.42341,
        rotation: -1.76159,
        scale: 1
    },
    USA: {
        distance: 2000,
        fov: 0.014,
        tilt:0.42341,
        rotation: 0.16841,
        scale: 1
    },
    U3: {
        distance: 2000,
        fov: 0.014,
        tilt:0.12841,
        rotation: 3.44841,
        scale: 1
    },
    U4: {
        distance: 14.22,
        fov: 3.913,
        tilt:0.12841,
        rotation: 3.44841,
        scale: 1   
    },
    India1: {
        distance: 7.95,
        fov: 9.714,
        tilt:0.12841,
        rotation: 3.44841,
        scale: 1
    },
    India2: {
        distance: 6.32,
        fov: 22.299,
        tilt:0.12841,
        rotation: 2.98841,
        scale: 1
    }
}    
    var preset = presets[p];
    if(preset == null) {
        return;
    }
    //Stop any animation in motion.

    if(zoomTween) zoomTween.cancel();
    if(rotateTween) rotateTween.cancel();
    if(camearZTween) camearZTween.cancel();
    if(cameraYTween) cameraYTween.cancel();
    if(tiltTween) tiltTween.cancel();
    if(fovTween) fovTween.cancel();

    
    tiltTween = new Tween(model.rotation.y, preset.rotation, 0.4, (theta)=>{model.rotation.y = theta;})
    tweenList.push(tiltTween);
    //model.rotation.y = preset.rotation;
    camearZTween = new Tween(camera.position.z, preset.distance, 0.4, (d)=>{camera.position.z = d; camera.updateProjectionMatrix(); });
    tweenList.push(camearZTween);
    //camera.position.z = preset.distance;

    tiltTween = new Tween(model.rotation.x, preset.tilt, 0.5, (theta)=> { model.rotation.x = theta});
    tweenList.push(tiltTween);
    //model.rotation.x = preset.tilt;    

    fovTween = new Tween(camera.fov, preset.fov, 0.4, (theta)=> { camera.fov = theta; camera.updateProjectionMatrix();} );
    tweenList.push(fovTween);
    //camera.fov = preset.fov;
    model.scale.set(preset.scale, preset.scale, preset.scale);
    
    camera.updateProjectionMatrix();
    updateUI();
}

var subjectWidth = 0;
var isDollyZoomEnabled = false;
var isPowerZoomEnabled = false;

function onEnableScatterChanged() {

    var isScatterEnabled = document.getElementById('enableScatter').checked;    
    scatterGroup.visible = isScatterEnabled;
}

function onEnablePowerZoomChanged() {
    isPowerZoomEnabled = document.getElementById('enablePowerZoom').checked;    
    if(isDollyZoomEnabled) {
        document.getElementById('enableDollyZoom').checked = false;
        isDollyZoomEnabled = false;
    }
    if(isPowerZoomEnabled) {
        document.getElementById('cameraFOV').setAttribute('disabled', true);
        if(zoomTween) zoomTween.cancel();
        if(fovTween) fovTween.cancel();        
        document.getElementById('cameraZPositionSlider').setAttribute('min', 4)
        document.getElementById('cameraZPositionFineSlider').setAttribute('min',4)
    } else {
                document.getElementById('cameraZPositionSlider').setAttribute('min', 0.01)
        document.getElementById('cameraZPositionFineSlider').setAttribute('min', 0.01)

    }
}



function onEnableDollyZoomChanged() {
    isDollyZoomEnabled = document.getElementById('enableDollyZoom').checked;
    if(isDollyZoomEnabled) {
        document.getElementById('enablePowerZoom').checked = false;
        isPowerZoomEnabled = false;        
        document.getElementById('cameraFOV').setAttribute('disabled', true);
        var radAngle = camera.fov * Math.PI / 180;
        subjectWidth = camera.position.z * Math.sin(radAngle/2);
        console.log(`dolly camera enabled. Keeping origin plan at a with of ${(180 * subjectWidth/Math.PI).toFixed(3)}` )
    } else {
        document.getElementById('cameraFOV').removeAttribute('disabled');
    }
}

function serializeSettings() {
    var obj = {};
    obj.fov = camera.fov;
    obj.camera_z = camera.position.z;
    obj.camera_y = camera.position.y;
    obj.globe_scale = model.scale.x;
    obj.globe_tilt = model.rotation.x;
    obj.globe_rotation = model.rotation.y;
    obj.rotation_order = model.rotation.order;
    obj.map_image = document.getElementById('mapImage').value;
    obj.power = power;
    obj.multiplier = multiplier;
    obj.rotateTexture = rotateTexture;
    obj.tiltTexture = tiltTexture;
    var objString = JSON.stringify(obj, null, 2);
    document.getElementById('viewParams').value = objString;
}

function applySerializedSettings() {
    var objString = document.getElementById('viewParams').value;
    try {
        var obj = JSON.parse(objString);
        if(obj.fov != null) {
            document.getElementById('cameraFOV').value = obj.fov;
            onCameraZoomChanged();
        }
        if(obj.camera_z != null) {
            document.getElementById('cameraZPositionSlider').value = obj.camera_z;
            onCameraZPositionChanged();
        }
        if(obj.camera_y != null) {
            document.getElementById('cameraYPositionSlider').value = obj.camera_y;
            onCameraYPositionChanged();
        }
        if(obj.globe_scale != null) {
            document.getElementById('globeScale').value = obj.globe_scale;
            onGlobeScaleChanged();
        }
        if(obj.globe_tilt != null) {
            document.getElementById('globeTilt').value = obj.globe_tilt;
            onGlobeTiltChanged();
        }
        if(obj.globe_rotation != null) {
            document.getElementById('globeRotation').value = obj.globe_rotation;
            onGlobeRotateChanged();
        }
        if(obj.rotation_order != null) {
            document.getElementById('rotationOrder').value = obj.rotation_order;
            onRotationOrderChanged();
        }
        if(obj.map_image != null) {
            document.getElementById('mapImage').value = obj.map_image;
            onMapSelectionChanged();
        }
        if(obj.power != null) {
            power = parseFloat(obj.power);
        }
        if(obj.multiplier != null) {
            multiplier = parseFloat(obj.multiplier);
        }
        if(obj.rotateTexture != null) {
            rotateTexture =  obj.rotateTexture == true;
        }
        if(obj.tiltTexture != null) {
            tiltTexture =  obj.tiltTexture == true;
        }
    }
    catch(e) {
        alert('Error parsing JSON string. Please check the format');
        return;
    }
}

function updateUI() {

    document.getElementById('fovText').innerText =  camera.fov.toFixed(3);    
    document.getElementById('cameraFOV').value = camera.fov;


    document.getElementById('zPositionText').innerText = camera.position.z.toFixed(3);    
    document.getElementById('cameraZPositionSlider').value = camera.position.z;

    document.getElementById('yPositionText').innerText = camera.position.y.toFixed(3);
    
    document.getElementById('cameraYPositionSlider').value = camera.position.y;

    document.getElementById('globeScaleText').innerText = model.scale.x;
    

}

var isMinimized = false;
function onMinimizeToggleClicked() {
    isMinimized = !isMinimized;
    var cameraPane = document.getElementsByClassName('camera-pane')[0];
    var presets = document.getElementsByClassName('presets')[0];
    var visualRoot = document.getElementById('visualRoot');
    if(isMinimized) {

        cameraPane.classList.add('minimized');
        presets.classList.add('hidden');
    } else {
        cameraPane.classList.remove('minimized');
        presets.classList.remove('hidden');
    }
}

var scatterGroup;
function start()
{
    console.log('starting');

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera( 22, window.innerWidth / window.innerHeight, 0.1, 2000 );
    renderer = new THREE.WebGLRenderer({ alpha: true });    
    renderer.setClearColor(0x000000, 0);
    renderer.setSize( window.innerWidth, window.innerHeight );
    
    var visualRoot = document.getElementById('visualRoot')
    visualRoot.appendChild( renderer.domElement );
    const geometry = new THREE.SphereGeometry(1, 128, 128); // Radius: 1, 
    const uvAttribute = geometry.attributes.uv;
    uvCoords = uvAttribute.clone();
    const texture = new THREE.TextureLoader().load('images/legalland2.png?t='+(new Date()).getTime());
    material = new THREE.MeshBasicMaterial({ map: texture });        
    model = new THREE.Mesh( geometry, material );
    model.rotation.order = "XYZ";
    scene.add( model );

    scatterGroup = new THREE.Group();
    const boxMaterialRed = new THREE.MeshBasicMaterial({ color: 0xff0000 });
    const boxMaterialPurple = new THREE.MeshBasicMaterial({ color: 0x800080 });
    const boxMaterialBlue = new THREE.MeshBasicMaterial({ color: 0x0000ff });
    var materialList = [boxMaterialRed, boxMaterialPurple, boxMaterialBlue, boxMaterialPurple]
    var modelCount = 0;

    const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
    for(var z= 0;z<2; ++z)
    {
    for(var y=-32;y<=32;y += 2) {
        for(var x = -32; x<=32;x+= 2) {
            const boxMesh = new THREE.Mesh(boxGeometry, materialList[modelCount % materialList.length]);
            ++modelCount;
            boxMesh.position.x = x;
            boxMesh.position.y = y;
            boxMesh.position.z = -256 + z*8
            scatterGroup.add(boxMesh);
        }
    }
}
    scatterGroup.visible = false;
    scene.add(scatterGroup);

    camera.position.z = 5;
    
        
    
    /////
    rotateTween = new Tween(0,-Math.PI* 0,1,  (x) => { model.rotation.y = x; });
    rotateTween.delay = 2.5;
    tweenList.push(rotateTween);
    
    //////
    var tiltTween = new Tween(0, 0.41,1, (x) => { model.rotation.x = x; });
    tiltTween.delay = 1.75;
    tweenList.push(tiltTween);
    
    ////
    var moveTween = new Tween(camera.position.y, 5, 3, (x)=>{camera.position.y = x; camera.updateProjectionMatrix();});
    

    /////
    zoomTween = new Tween(camera.fov, 10,4,  (x) => {
        camera.fov = x;
        camera.updateProjectionMatrix(); 
    });
    tweenList.push(zoomTween);
    zoomTween.onComplete = () => {        
        
        
        //tweenList.push(moveTween);
    }
    
    renderer.setAnimationLoop( animate );
    updateUI();
}