//
//  flockOfbirds.js
//  examples
//
//  Copyright 2014 High Fidelity, Inc.
//  Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined
//  at the start of the script.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html


//  The rectangular area in the domain where the flock will fly
var lowerCorner = { x: 0, y: 0, z: 0 };
var upperCorner = { x: 30, y: 10, z: 30  };
var STARTING_FRACTION = 0.25;

var NUM_BIRDS = 50;
var UPDATE_INTERVAL = 0.016;
var playSounds = true;
var SOUND_PROBABILITY = 0.001;
var STARTING_LIFETIME = (1.0 / SOUND_PROBABILITY) * UPDATE_INTERVAL * 10;
var numPlaying = 0;
var BIRD_SIZE = 0.08;
var BIRD_MASTER_VOLUME = 0.1;
var FLAP_PROBABILITY = 0.005;
var RANDOM_FLAP_VELOCITY = 1.0;
var FLAP_UP = 1.0;
var BIRD_GRAVITY = -0.5;
var LINEAR_DAMPING = 0.2;
var FLAP_FALLING_PROBABILITY = 0.025;
var MIN_ALIGNMENT_VELOCITY = 0.0;
var MAX_ALIGNMENT_VELOCITY = 1.0;
var VERTICAL_ALIGNMENT_COUPLING = 0.0;
var ALIGNMENT_FORCE = 1.5;
var COHESION_FORCE = 1.0;
var MAX_COHESION_VELOCITY = 0.5;

var followBirds = false;
var AVATAR_FOLLOW_RATE = 0.001;
var AVATAR_FOLLOW_VELOCITY_TIMESCALE = 2.0;
var AVATAR_FOLLOW_ORIENTATION_RATE = 0.005;
var floor = false;
var MAKE_FLOOR = false;

var averageVelocity = { x: 0, y: 0, z: 0 };
var averagePosition = { x: 0, y: 0, z: 0 };

var birdsLoaded = false;

var oldAvatarOrientation;
var oldAvatarPosition;

var birds = [];
var playing = [];

function randomVector(scale) {
    return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 };
}

function updateBirds(deltaTime) {
    if (!Entities.serversExist() || !Entities.canRez()) {
        return;
    }
    if (!birdsLoaded) {
        loadBirds(NUM_BIRDS);
        birdsLoaded = true;
        return;
    }
    var sumVelocity = { x: 0, y: 0, z: 0 };
    var sumPosition = { x: 0, y: 0, z: 0 };
    var birdPositionsCounted = 0;
    var birdVelocitiesCounted = 0;
    for (var i = 0; i < birds.length; i++) {
        if (birds[i].entityId) {
            var properties = Entities.getEntityProperties(birds[i].entityId);
            //  If Bird has been deleted, bail
            if (properties.id != birds[i].entityId) {
                birds[i].entityId = false;
                return;
            }
            //  Sum up average position and velocity
            if (Vec3.length(properties.velocity) > MIN_ALIGNMENT_VELOCITY) {
                sumVelocity = Vec3.sum(sumVelocity, properties.velocity);
                birdVelocitiesCounted += 1;
            }
            sumPosition = Vec3.sum(sumPosition, properties.position);
            birdPositionsCounted += 1;

            var downwardSpeed = (properties.velocity.y < 0) ? -properties.velocity.y : 0.0;
            if ((properties.position.y < upperCorner.y) && (Math.random() < (FLAP_PROBABILITY + (downwardSpeed * FLAP_FALLING_PROBABILITY)))) {
                //  More likely to flap if falling
                var randomVelocity = randomVector(RANDOM_FLAP_VELOCITY);
                randomVelocity.y = FLAP_UP + Math.random() * FLAP_UP;

                //  Alignment Velocity
                var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity)));
                var alignmentVelocity = Vec3.multiply(alignmentVelocityMagnitude, Vec3.normalize(averageVelocity));
                alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING;

                //  Cohesion
                var distanceFromCenter = Vec3.length(Vec3.subtract(averagePosition, properties.position));
                var cohesionVelocitySize = Math.min(distanceFromCenter * COHESION_FORCE, MAX_COHESION_VELOCITY);
                var cohesionVelocity = Vec3.multiply(cohesionVelocitySize, Vec3.normalize(Vec3.subtract(averagePosition, properties.position)));

                var newVelocity = Vec3.sum(randomVelocity, Vec3.sum(alignmentVelocity, cohesionVelocity));

                Entities.editEntity(birds[i].entityId, { velocity: Vec3.sum(properties.velocity, newVelocity) });

            }

            //  Check whether to play a chirp
            if (playSounds && (!birds[i].audioId || !birds[i].audioId.playing) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) {
                var options = {
                          position: properties.position,
                    volume: BIRD_MASTER_VOLUME
                };
                // Play chirp
                if (birds[i].audioId) {
                    birds[i].audioId.setOptions(options);
                    birds[i].audioId.restart();
                } else {
                    birds[i].audioId = Audio.playSound(birds[i].sound, options);
                }
                numPlaying++;
                // Change size, and update lifetime to keep bird alive
                Entities.editEntity(birds[i].entityId, { dimensions: Vec3.multiply(1.5, properties.dimensions),
                                                         lifetime: properties.ageInSeconds + STARTING_LIFETIME});

            } else if (birds[i].audioId) {
                // If bird is playing a chirp
                if (!birds[i].audioId.playing) {
                    Entities.editEntity(birds[i].entityId, { dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }});
                    numPlaying--;
                }
            }

            //  Keep birds in their 'cage'
            var bounce = false;
            var newVelocity = properties.velocity;
            var newPosition = properties.position;
            if (properties.position.x < lowerCorner.x) {
                newPosition.x = lowerCorner.x;
                newVelocity.x *= -1.0;
                bounce = true;
            } else if (properties.position.x > upperCorner.x) {
                newPosition.x = upperCorner.x;
                newVelocity.x *= -1.0;
                bounce = true;
            }
            if (properties.position.y < lowerCorner.y) {
                newPosition.y = lowerCorner.y;
                newVelocity.y *= -1.0;
                bounce = true;
            } else if (properties.position.y > upperCorner.y) {
                newPosition.y = upperCorner.y;
                newVelocity.y *= -1.0;
                bounce = true;
            }
            if (properties.position.z < lowerCorner.z) {
                newPosition.z = lowerCorner.z;
                newVelocity.z *= -1.0;
                bounce = true;
            } else if (properties.position.z > upperCorner.z) {
                newPosition.z = upperCorner.z;
                newVelocity.z *= -1.0;
                bounce = true;
            }
            if (bounce) {
                Entities.editEntity(birds[i].entityId, { position: newPosition, velocity: newVelocity });
            }
        }
    }
    //  Update average velocity and position of flock
    if (birdVelocitiesCounted > 0) {
        averageVelocity = Vec3.multiply(1.0 / birdVelocitiesCounted, sumVelocity);
        //print(Vec3.length(averageVelocity));
        if (followBirds) {
            MyAvatar.motorVelocity = averageVelocity;
            MyAvatar.motorTimescale = AVATAR_FOLLOW_VELOCITY_TIMESCALE;
            var polarAngles = Vec3.toPolar(Vec3.normalize(averageVelocity));
            if (!isNaN(polarAngles.x) && !isNaN(polarAngles.y)) {
                var birdDirection = Quat.fromPitchYawRollRadians(polarAngles.x, polarAngles.y + Math.PI, polarAngles.z);
                MyAvatar.orientation = Quat.mix(MyAvatar.orientation, birdDirection, AVATAR_FOLLOW_ORIENTATION_RATE);
            }
        }
    }
    if (birdPositionsCounted > 0) {
        averagePosition = Vec3.multiply(1.0 / birdPositionsCounted, sumPosition);
            //  If Following birds, update position
        if (followBirds) {
            MyAvatar.position = Vec3.sum(Vec3.multiply(AVATAR_FOLLOW_RATE, MyAvatar.position), Vec3.multiply(1.0 - AVATAR_FOLLOW_RATE, averagePosition));
        }
    }

}

// Connect a call back that happens every frame
Script.update.connect(updateBirds);

//  Delete our little friends if script is stopped
Script.scriptEnding.connect(function() {
    for (var i = 0; i < birds.length; i++) {
        Entities.deleteEntity(birds[i].entityId);
    }
    if (floor) {
        Entities.deleteEntity(floor);
    }
    MyAvatar.orientation = oldAvatarOrientation;
    MyAvatar.position = oldAvatarPosition;
});

function loadBirds(howMany) {
    oldAvatarOrientation = MyAvatar.orientation;
    oldAvatarPosition = MyAvatar.position;

  var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"];
  /* Here are more sounds/species you can use
                        , "mexicanWhipoorwill.raw",
                           "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav",
                           "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav",
                           "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav",
                           "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav",
                           "whistlingduck.wav", "woodpecker.wav"];
                         */

  var colors = [
              { red: 242, green: 207, blue: 013 },
            { red: 238, green: 94, blue: 11 },
            { red: 81, green: 30, blue: 7 },
            { red: 195, green: 176, blue: 81 },
            { red: 235, green: 190, blue: 152 },
            { red: 167, green: 99, blue: 52 },
            { red: 199, green: 122, blue: 108 },
            { red: 246, green: 220, blue: 189 },
            { red: 208, green: 145, blue: 65 },
            { red: 173, green: 120 , blue: 71 },
            { red: 132, green: 147, blue: 174 },
            { red: 164, green: 74, blue: 40 },
            { red: 131, green: 127, blue: 134 },
            { red: 209, green: 157, blue: 117 },
            { red: 205, green: 191, blue: 193 },
            { red: 193, green: 154, blue: 118 },
            { red: 205, green: 190, blue: 169 },
            { red: 199, green: 111, blue: 69 },
            { red: 221, green: 223, blue: 228 },
            { red: 115, green: 92, blue: 87 },
            { red: 214, green: 165, blue: 137 },
            { red: 160, green: 124, blue: 33 },
            { red: 117, green: 91, blue: 86 },
            { red: 113, green: 104, blue: 107 },
            { red: 216, green: 153, blue: 99 },
            { red: 242, green: 226, blue: 64 }
  ];

  var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/";

  for (var i = 0; i < howMany; i++) {
    var whichBird = Math.floor(Math.random() * sound_filenames.length);
    var position = {
        x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION,
        y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION,
        z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION
    };

    birds.push({
        sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]),
        entityId: Entities.addEntity({
                    type: "Sphere",
                    position: position,
                    dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE },
                    gravity: {  x: 0, y: BIRD_GRAVITY, z: 0 },
                    velocity: { x: 0, y: -0.1, z: 0 },
                    damping: LINEAR_DAMPING,
                    dynamic: true,
                    lifetime: STARTING_LIFETIME,
                    color: colors[whichBird]
        }),
        audioId: false,
        isPlaying: false
      });
    }
    if (MAKE_FLOOR) {
        var FLOOR_THICKNESS = 0.05;
        floor = Entities.addEntity({ type: "Box", position: {   x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0,
                                                                y: lowerCorner.y,
                                                                z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0  },
                                                   dimensions: { x: (upperCorner.x - lowerCorner.x), y: FLOOR_THICKNESS, z: (upperCorner.z - lowerCorner.z)},
                                                   color: {red: 100, green: 100, blue: 100}
                                    });
    }
}