// 
//  solarsystem.js
//  example 
//  
//  Created by Bridget Went, 5/28/15.
//  Copyright 2015 High Fidelity, Inc.
//  
//  A project to build a virtual physics classroom to simulate the solar system, gravity, and orbital physics. 
//
// 
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

CreateSimulation = function() {
    Script.include("https://hifi-public.s3.amazonaws.com/eric/scripts/tween.js");
    Script.include('games/satellite.js');

    var trailsEnabled = this.trailsEnabled = true;

    var DAMPING = this.DAMPING = 0.0;
    var LIFETIME = this.LIFETIME = 6000;
    var BASE_URL = this.BASE_URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/planets/";

    // Save intiial avatar and camera position
    var startingPosition = this.startingPosition = {
        x: 8000,
        y: 8000,
        z: 8000
    };
    MyAvatar.position = startingPosition;
    var cameraStart = this.cameraStart = Camera.getOrientation();


    // Place the sun
    var MAX_RANGE = this.MAX_RANGE = 100.0;
    var center = this.center = Vec3.sum(startingPosition, Vec3.multiply(MAX_RANGE, Vec3.normalize(Quat.getFront(Camera.getOrientation()))));
    var SUN_SIZE = this.SUN_SIZE = 7.0;

    var theSun = this.theSun = Entities.addEntity({
        type: "Model",
        modelURL: BASE_URL + "sun.fbx",
        position: center,
        dimensions: {
            x: SUN_SIZE,
            y: SUN_SIZE,
            z: SUN_SIZE
        },
        angularVelocity: {
            x: 0.0,
            y: 0.1,
            z: 0.0
        },
        angularDamping: DAMPING,
        damping: DAMPING,
        ignoreCollisions: false,
        lifetime: LIFETIME,
        dynamic: false
    });



    // Reference values for physical constants
    var referenceRadius = this.referenceRadius = 15.0;
    var referenceDiameter = this.referenceDiameter = 0.6;
    var referencePeriod = this.referencePeriod = 3.0;
    var LARGE_BODY_MASS = this.LARGE_BODY_MASS = 250.0;
    var SMALL_BODY_MASS = this.SMALL_BODY_MASS = LARGE_BODY_MASS * 0.000000333;
    var GRAVITY = this.GRAVITY = (Math.pow(referenceRadius, 3.0) / Math.pow((referencePeriod / (2.0 * Math.PI)), 2.0)) / LARGE_BODY_MASS;
    var REFERENCE_GRAVITY = this.REFERENCE_GRAVITY = GRAVITY;

    var planets = this.planets = [];
    var MAX_POINTS_PER_LINE = this.MAX_POINTS_PER_LINE = 40;
    var LINE_DIM = this.LINE_DIM = 200;
    var LINE_WIDTH = this.LINE_WIDTH = 20;

    var VELOCITY_OFFSET_Y = this.VELOCITY_OFFSET_Y = -0.3;
    var VELOCITY_OFFSET_Z = this.VELOCITY_OFFSET_Z = 0.9;

    var index = this.index = 0;

    this.computeLocalPoint = function(linePos, worldPoint) {
        var localPoint = Vec3.subtract(worldPoint, linePos);
        return localPoint;
    }

    // Create a new line
    this.newLine = function(lineStack, point, period, color) {
        if (elapsed < period) {
            var line = Entities.addEntity({
                position: point,
                type: "Line",
                color: color,
                dimensions: {
                    x: LINE_DIM,
                    y: LINE_DIM,
                    z: LINE_DIM
                },
                lifetime: LIFETIME,
                lineWidth: LINE_WIDTH
            });
            lineStack.push(line);
        } else {
            // Begin overwriting first lines after one full revolution (one period)
            var firstLine = lineStack.shift();
            Entities.editEntity(firstLine, {
                position: point,
                linePoints: [{
                    x: 0.0,
                    y: 0.0,
                    z: 0.0
                }]
            });
            lineStack.push(firstLine);

        }
        var points = [];
        points.push(computeLocalPoint(point, point));
        return points;
    }

    this.pause = function() {
        if (paused) {
            return;
        }
        for (var i = 0; i < planets.length; ++i) {
            Entities.editEntity(planets[i].planet, {
                velocity: {
                    x: 0.0,
                    y: 0.0,
                    z: 0.0
                }
            });
            planets[i].label = new PlanetLabel(planets[i].name, i);
            planets[i].label.show();
        }
        paused = true;
    }

    this.resume = function() {
        if (!paused) {
            return;
        }
        for (var i = 0; i < planets.length; ++i) {
            planets[i].label.hide();
        }
        paused = false;
    }

    var Planet = function(name, trailColor, radiusScale, sizeScale) {
        this.name = name;
        this.trailColor = trailColor;

        this.trail = [];
        this.lineStack = [];

        this.radius = radiusScale * referenceRadius;
        this.position = Vec3.sum(center, {
            x: this.radius,
            y: 0.0,
            z: 0.0
        });
        this.startPosition = this.position;
        this.period = (2.0 * Math.PI) * Math.sqrt(Math.pow(this.radius, 3.0) / (GRAVITY * LARGE_BODY_MASS));

        this.initialVelocity = Math.sqrt((GRAVITY * LARGE_BODY_MASS) / this.radius);

        this.velocity = Vec3.multiply(this.initialVelocity, Vec3.normalize({
            x: 0,
            y: VELOCITY_OFFSET_Y,
            z: VELOCITY_OFFSET_Z
        }));
        this.dimensions = sizeScale * referenceDiameter;
        this.sizeScale = sizeScale;

        this.planet = Entities.addEntity({
            type: "Model",
            modelURL: BASE_URL + name + ".fbx",
            position: this.position,
            dimensions: {
                x: this.dimensions,
                y: this.dimensions,
                z: this.dimensions
            },
            velocity: this.velocity,
            angularDamping: DAMPING,
            damping: DAMPING,
            ignoreCollisions: false,
            lifetime: LIFETIME,
            dynamic: false,
        });

        this.computeAcceleration = function() {
            var acc = -(GRAVITY * LARGE_BODY_MASS) * Math.pow(this.radius, (-2.0));
            return acc;
        };

        this.update = function(deltaTime) {
            var between = Vec3.subtract(this.position, center);
            var speed = this.computeAcceleration(this.radius) * deltaTime;
            var vel = Vec3.multiply(speed, Vec3.normalize(between));

            // Update velocity and position
            this.velocity = Vec3.sum(this.velocity, vel);
            this.position = Vec3.sum(this.position, Vec3.multiply(deltaTime, this.velocity));
            Entities.editEntity(this.planet, {
                velocity: this.velocity,
                position: this.position
            });

            if (trailsEnabled) {
                this.updateTrail();
            }
        };

        this.clearTrails = function() {
            elapsed = 0.0;
            for (var j = 0; j < this.lineStack.length; ++j) {
                Entities.editEntity(this.lineStack[j], {
                    visible: false
                });
            }

        }
        this.resetTrails = function() {
            elapsed = 0.0;
            this.trail = [];
            this.lineStack = [];
            //add the first line to both the line entity stack and the trail
            this.trail.push(newLine(this.lineStack, this.position, this.period, this.trailColor));

        };

        this.updateTrail = function() {
            var point = this.position;
            var linePos = Entities.getEntityProperties(this.lineStack[this.lineStack.length - 1]).position;

            this.trail.push(computeLocalPoint(linePos, point));

            Entities.editEntity(this.lineStack[this.lineStack.length - 1], {
                linePoints: this.trail
            });

            if (this.trail.length == MAX_POINTS_PER_LINE) {
                this.trail = newLine(this.lineStack, point, this.period, this.trailColor);
            }
        };

        this.zoom = function() {
            if (!paused) {
                pause();
            }
            this.tweening = true;
            var tweenTime = 1000;
            var startingPosition = MyAvatar.position;
            var endingPosition = Vec3.sum({
                x: 0,
                y: 0,
                z: this.sizeScale
            }, this.position);
            var currentProps = {
                x: startingPosition.x,
                y: startingPosition.y,
                z: startingPosition.z,
            };
            var endProps = {
                x: endingPosition.x,
                y: endingPosition.y,
                z: endingPosition.z,
            };

            var moveTween = new TWEEN.Tween(currentProps).
            to(endProps, tweenTime).
            easing(TWEEN.Easing.Cubic.InOut).
            onUpdate(function() {
                MyAvatar.position = {
                    x: currentProps.x,
                    y: currentProps.y,
                    z: currentProps.z
                };
            }).start();

            moveTween.onComplete(function() {
                Camera.lookAt(endingPosition);
                this.tweening = false;
            });

        }

        index++;
        this.resetTrails();

    }


    var MERCURY_LINE_COLOR = {
        red: 255,
        green: 255,
        blue: 255
    };
    var VENUS_LINE_COLOR = {
        red: 255,
        green: 160,
        blue: 110
    };
    var EARTH_LINE_COLOR = {
        red: 10,
        green: 150,
        blue: 160
    };
    var MARS_LINE_COLOR = {
        red: 180,
        green: 70,
        blue: 10
    };
    var JUPITER_LINE_COLOR = {
        red: 250,
        green: 140,
        blue: 0
    };
    var SATURN_LINE_COLOR = {
        red: 235,
        green: 215,
        blue: 0
    };
    var URANUS_LINE_COLOR = {
        red: 135,
        green: 205,
        blue: 240
    };
    var NEPTUNE_LINE_COLOR = {
        red: 30,
        green: 140,
        blue: 255
    };
    var PLUTO_LINE_COLOR = {
        red: 255,
        green: 255,
        blue: 255
    };

    this.initPlanets = function() {
        planets.push(new Planet("mercury", MERCURY_LINE_COLOR, 0.387, 0.383));
        planets.push(new Planet("venus", VENUS_LINE_COLOR, 0.723, 0.949));
        planets.push(new Planet("earth", EARTH_LINE_COLOR, 1.0, 1.0));
        planets.push(new Planet("mars", MARS_LINE_COLOR, 1.52, 0.532));
        planets.push(new Planet("jupiter", JUPITER_LINE_COLOR, 5.20, 11.21));
        planets.push(new Planet("saturn", SATURN_LINE_COLOR, 9.58, 9.45));
        planets.push(new Planet("uranus", URANUS_LINE_COLOR, 19.20, 4.01));
        planets.push(new Planet("neptune", NEPTUNE_LINE_COLOR, 30.05, 3.88));
        planets.push(new Planet("pluto", PLUTO_LINE_COLOR, 39.48, 0.186));
    }
    initPlanets();

    var LABEL_X = 8.0;
    var LABEL_Y = 3.0;
    var LABEL_Z = 1.0;
    var LABEL_DIST = 8.0;
    var TEXT_HEIGHT = 1.0;

    var PlanetLabel = function(name, index) {
        var text = name + "                    Speed: " + Vec3.length(planets[index].velocity).toFixed(2);
        this.labelPos = Vec3.sum(planets[index].position, {
            x: 0.0,
            y: LABEL_DIST,
            z: LABEL_DIST
        });
        this.linePos = planets[index].position;

        this.line = Entities.addEntity({
            type: "Line",
            position: this.linePos,
            dimensions: {
                x: 20,
                y: 20,
                z: 20
            },
            lineWidth: LINE_WIDTH,
            color: {
                red: 255,
                green: 255,
                blue: 255
            },
            linePoints: [{
                    x: 0,
                    y: 0,
                    z: 0
                },
                computeLocalPoint(this.linePos, this.labelPos)
            ],
            visible: false
        });

        this.label = Entities.addEntity({
            type: "Text",
            text: text,
            lineHeight: TEXT_HEIGHT,
            dimensions: {
                x: LABEL_X,
                y: LABEL_Y,
                z: LABEL_Z
            },
            position: this.labelPos,
            backgroundColor: {
                red: 10,
                green: 10,
                blue: 10
            },
            faceCamera: true,
            visible: false
        });

        this.show = function() {
            this.showing = true;
            Entities.editEntity(this.line, {
                visible: true
            });
            Entities.editEntity(this.label, {
                visible: true
            });
        }

        this.hide = function() {
            this.showing = false;
            Entities.editEntity(this.line, {
                visible: false
            });
            Entities.editEntity(this.label, {
                visible: false
            });
        }
    }

    var planetLines = [];
    var trails = this.trails = [];
    var paused = this.paused = false;

    var time = 0;
    var elapsed;
    var TIME_STEP = 80;

    this.update = function(deltaTime) {
        for (var i = 0; i < planets.length; ++i) {
            if (paused) {
                return;
            }
            planets[i].update(deltaTime);
        }

        time++;
        if (time % TIME_STEP == 0) {
            elapsed++;
        }

    }

    Script.include('planets-ui.js');

    // Clean up models, UI panels, lines, and button overlays
    this.scriptEnding = function() {

        Entities.deleteEntity(theSun);
        for (var i = 0; i < planets.length; ++i) {
            Entities.deleteEntity(planets[i].planet);
        }
        var e = Entities.findEntities(MyAvatar.position, 10000);
        for (i = 0; i < e.length; i++) {
            var props = Entities.getEntityProperties(e[i]);
            if (props.type === "Line" || props.type === "Text") {
                Entities.deleteEntity(e[i]);
            }
        }
    };

    this.restart = function() {
        if (paused) {
            resume();
        }
        for (var i = 0; i < planets.length; ++i) {
            Entities.deleteEntity(planets[i].planet);
            planets[i].clearTrails();
        }
        time = 0.0;
        elapsed = 0.0;
        planets.length = 0;
        initPlanets();


        MyAvatar.position = startingPosition;
        Camera.setPosition(cameraStart);
    };

}
CreateSimulation();

Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);