//
//  tutorial.js
//
//  Created by Ryan Huffman on 9/1/16.
//  Copyright 2016 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

Script.include("entityData.js");
Script.include("lighter/createButaneLighter.js");
Script.include("tutorialEntityIDs.js");

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

var DEBUG = true;
function debug() {
    if (DEBUG) {
        var args = Array.prototype.slice.call(arguments);
        args.unshift("tutorial.js | ");
        print.apply(this, args);
    }
}

var INFO = true;
function info() {
    if (INFO) {
        var args = Array.prototype.slice.call(arguments);
        args.unshift("tutorial.js | ");
        print.apply(this, args);
    }
}

const CONTROLLER_TOUCH = 'touch';
const CONTROLLER_VIVE = 'vive';

var NEAR_BOX_SPAWN_NAME = "tutorial/nearGrab/box_spawn";
var FAR_BOX_SPAWN_NAME = "tutorial/farGrab/box_spawn";
var GUN_SPAWN_NAME = "tutorial/gun_spawn";
var TELEPORT_PAD_NAME = "tutorial/teleport/pad"

var successSound = SoundCache.getSound("atp:/tutorial_sounds/good_one.L.wav");
var firecrackerSound = SoundCache.getSound("atp:/tutorial_sounds/Pops_Firecracker.wav");


var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
function setAwayEnabled(value) {
    var message = value ? 'enable' : 'disable';
    Messages.sendLocalMessage(CHANNEL_AWAY_ENABLE, message);
}

findEntity = function(properties, searchRadius, filterFn) {
    var entities = findEntities(properties, searchRadius, filterFn);
    return entities.length > 0 ? entities[0] : null;
}

// Return all entities with properties `properties` within radius `searchRadius`
findEntities = function(properties, searchRadius, filterFn) {
    if (!filterFn) {
        filterFn = function(properties, key, value) {
            return value == properties[key];
        }
    }
    searchRadius = searchRadius ? searchRadius : 100000;
    var entities = Entities.findEntities({ x: 0, y: 0, z: 0 }, searchRadius);
    var matchedEntities = [];
    var keys = Object.keys(properties);
    for (var i = 0; i < entities.length; ++i) {
        var match = true;
        var candidateProperties = Entities.getEntityProperties(entities[i], keys);
        for (var key in properties) {
            if (!filterFn(properties, key, candidateProperties[key])) {
                // This isn't a match, move to next entity
                match = false;
                break;
            }
        }
        if (match) {
            matchedEntities.push(entities[i]);
        }
    }

    return matchedEntities;
}

function findEntitiesWithTag(tag) {
    return findEntities({ userData: "" }, 10000, function(properties, key, value) {
        data = parseJSON(value);
        return data.tag === tag;
    });
}

/**
 * A controller in made up of parts, and each part can have multiple "layers,"
 * which are really just different texures. For example, the "trigger" part
 * has "normal" and "highlight" layers.
 */
function setControllerPartLayer(part, layer) {
    data = {};
    data[part] = layer;
    Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data));
}

/**
 * Spawn entities and return the newly created entity's ids.
 * @param {object[]} entityPropertiesList A list of properties of the entities
 * to spawn.
 */
function spawn(entityPropertiesList, transform, modifyFn) {
    if (!transform) {
        transform = {
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0, w: 1 }
        }
    }
    var ids = [];
    for (var i = 0; i < entityPropertiesList.length; ++i) {
        var data = entityPropertiesList[i];
        data.position = Vec3.sum(transform.position, data.position);
        data.rotation = Quat.multiply(data.rotation, transform.rotation);
        if (modifyFn) {
            data = modifyFn(data);
        }
        var id = Entities.addEntity(data);
        ids.push(id);
    }
    return ids;
}

/**
 * @function parseJSON
 * @param {string} jsonString The string to parse.
 * @return {object} Return an empty if the string was not valid JSON, otherwise
 *   the parsed object is returned.
 */
function parseJSON(jsonString) {
    var data;
    try {
        data = JSON.parse(jsonString);
    } catch(e) {
        data = {};
    }
    return data;
}

/**
 * Spawn entities with `tag` in the userData.
 * @function spawnWithTag
 */
function spawnWithTag(entityData, transform, tag) {
    function modifyFn(data) {
        var userData = parseJSON(data.userData);
        userData.tag = tag;
        data.userData = JSON.stringify(userData);
        debug("In modify", tag, userData, data.userData);
        return data;
    }
    return spawn(entityData, transform, modifyFn);
}

/**
 * Delete all entities with the tag `tag` in their userData.
 * @function deleteEntitiesWithTag
 */
function deleteEntitiesWithTag(tag) {
    debug("searching for...:", tag);
    var entityIDs = findEntitiesWithTag(tag);
    for (var i = 0; i < entityIDs.length; ++i) {
        Entities.deleteEntity(entityIDs[i]);
    }
}

function editEntitiesWithTag(tag, propertiesOrFn) {
    var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];

    debug("Editing tag: ", tag);
    if (entities) {
        for (entityID in entities) {
            debug("Editing: ", entityID, ", ", propertiesOrFn, ", Is in local tree: ", isEntityInLocalTree(entityID));
            if (isFunction(propertiesOrFn)) {
                Entities.editEntity(entityID, propertiesOrFn(entityIDs[i]));
            } else {
                Entities.editEntity(entityID, propertiesOrFn);
            }
        }
    }
}

// From http://stackoverflow.com/questions/5999998/how-can-i-check-if-a-javascript-variable-is-function-type
function isFunction(functionToCheck) {
    var getType = {};
    return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}

/**
 * Return `true` if `entityID` can be found in the local entity tree, otherwise `false`.
 */
function isEntityInLocalTree(entityID) {
    return Entities.getEntityProperties(entityID, 'visible').visible !== undefined;
}

/**
 *
 */
function showEntitiesWithTags(tags) {
    for (var i = 0; i < tags.length; ++i) {
        showEntitiesWithTag(tags[i]);
    }
}

function showEntitiesWithTag(tag) {
    var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
    if (entities) {
        for (entityID in entities) {
            var data = entities[entityID];

            var collisionless = data.visible === false ? true : false;
            if (data.collidable !== undefined) {
                collisionless = data.collidable === true ? false : true;
            }
            if (data.soundKey) {
                data.soundKey.playing = true;
            }
            var newProperties = {
                visible: data.visible == false ? false : true,
                collisionless: collisionless,
                userData: JSON.stringify(data),
            };
            debug("Showing: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
            Entities.editEntity(entityID, newProperties);
        }
    } else {
        debug("ERROR | No entities for tag: ", tag);
    }

    return;
    // Dynamic method, suppressed for now
    //editEntitiesWithTag(tag, function(entityID) {
    //    var userData = Entities.getEntityProperties(entityID, "userData").userData;
    //    var data = parseJSON(userData);
    //    var collisionless = data.visible === false ? true : false;
    //    if (data.collidable !== undefined) {
    //        collisionless = data.collidable === true ? false : true;
    //    }
    //    if (data.soundKey) {
    //        data.soundKey.playing = true;
    //    }
    //    var newProperties = {
    //        visible: data.visible == false ? false : true,
    //        collisionless: collisionless,
    //        userData: JSON.stringify(data),
    //    };
    //    Entities.editEntity(entityID, newProperties);
    //});
}

function hideEntitiesWithTags(tags) {
    for (var i = 0; i < tags.length; ++i) {
        hideEntitiesWithTag(tags[i]);
    }
}

function hideEntitiesWithTag(tag) {
    var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
    if (entities) {
        for (entityID in entities) {
            var data = entities[entityID];

            if (data.soundKey) {
                data.soundKey.playing = false;
            }
            var newProperties = {
                visible: false,
                collisionless: 1,
                ignoreForCollisions: 1,
                userData: JSON.stringify(data),
            };

            debug("Hiding: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
            Entities.editEntity(entityID, newProperties);
        }
    }

    return;
    // Dynamic method, suppressed for now
    //editEntitiesWithTag(tag, function(entityID) {
    //    var userData = Entities.getEntityProperties(entityID, "userData").userData;
    //    var data = parseJSON(userData);
    //    if (data.soundKey) {
    //        data.soundKey.playing = false;
    //    }
    //    var newProperties = {
    //        visible: false,
    //        collisionless: 1,
    //        ignoreForCollisions: 1,
    //        userData: JSON.stringify(data),
    //    };
    //    Entities.editEntity(entityID, newProperties);
    //});
}

/**
 * Return the entity properties for an entity with a given name if it is in our
 * cached list of entities. Otherwise, return undefined.
 */
function getEntityWithName(name) {
    debug("Getting entity with name:", name);
    var entityID = TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP[name];
    debug("Entity id: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
    return entityID;
}

function playSuccessSound() {
    Audio.playSound(successSound, {
        position: MyAvatar.position,
        volume: 0.7,
        loop: false
    });
}

function playFirecrackerSound(position) {
    Audio.playSound(firecrackerSound, {
        position: position,
        volume: 0.5,
        loop: false
    });
}

/**
 * This disables everything, including:
 *
 *   - The door to leave the tutorial
 *   - Overlays
 *   - Hand controlelrs
 *   - Teleportation
 *   - Advanced movement
 *   - Equip and far grab
 *   - Away mode
 */
function disableEverything() {
    editEntitiesWithTag('door', { visible: true, collisionless: false });
    Menu.setIsOptionChecked("Overlays", false);
    Controller.disableMapping('handControllerPointer-click');
    Messages.sendLocalMessage('Hifi-Advanced-Movement-Disabler', 'disable');
    Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'both');
    Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
        nearGrabEnabled: true,
        holdEnabled: false,
        farGrabEnabled: false,
        myAvatarScalingEnabled: false,
        objectScalingEnabled: false,
    }));
    setControllerPartLayer('touchpad', 'blank');
    setControllerPartLayer('trigger', 'blank');
    setControllerPartLayer('joystick', 'blank');
    setControllerPartLayer('grip', 'blank');
    setControllerPartLayer('button_a', 'blank');
    setControllerPartLayer('button_b', 'blank');
    setControllerPartLayer('tips', 'blank');

    hideEntitiesWithTag('finish');

    setAwayEnabled(false);
}

/**
 * This reenables everything that disableEverything() disables. This can be
 * used when leaving the tutorial to ensure that nothing is left disabled.
 */
function reenableEverything() {
    editEntitiesWithTag('door', { visible: false, collisionless: true });
    Menu.setIsOptionChecked("Overlays", true);
    Controller.enableMapping('handControllerPointer-click');
    Messages.sendLocalMessage('Hifi-Advanced-Movement-Disabler', 'enable');
    Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none');
    Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
        nearGrabEnabled: true,
        holdEnabled: true,
        farGrabEnabled: true,
        myAvatarScalingEnabled: true,
        objectScalingEnabled: true,
    }));
    setControllerPartLayer('touchpad', 'blank');
    setControllerPartLayer('trigger', 'blank');
    setControllerPartLayer('joystick', 'blank');
    setControllerPartLayer('grip', 'blank');
    setControllerPartLayer('button_a', 'blank');
    setControllerPartLayer('button_b', 'blank');
    setControllerPartLayer('tips', 'blank');
    MyAvatar.shouldRenderLocally = true;
    setAwayEnabled(true);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: DISABLE CONTROLLERS                                                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepStart = function() {
    this.name = 'start';
};
stepStart.prototype = {
    start: function(onFinish) {
        disableEverything();

        HMD.requestShowHandControllers();

        onFinish();
    },
    cleanup: function() {
    }
};



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: ENABLE CONTROLLERS                                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

var stepEnableControllers = function() {
    this.shouldLog = false;
};
stepEnableControllers.prototype = {
    start: function(onFinish) {
        reenableEverything();
        HMD.requestHideHandControllers();
        onFinish();
    },
    cleanup: function() {
    }
};



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Orient and raise hands above head                                   //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepOrient = function(tutorialManager) {
    this.name = 'orient';
    this.tags = ["orient", "orient-" + tutorialManager.controllerName];
}
stepOrient.prototype = {
    start: function(onFinish) {
        this.active = true;

        var tag = this.tag;

        // Spawn content set
        //editEntitiesWithTag(this.tag, { visible: true });
        showEntitiesWithTags(this.tags);

        this.checkIntervalID = null;
        function checkForHandsAboveHead() {
            debug("Orient | Checking for hands above head");
            if (MyAvatar.getLeftPalmPosition().y > (MyAvatar.getHeadPosition().y + 0.1)) {
                Script.clearInterval(this.checkIntervalID);
                this.checkIntervalID = null;
                location = "/tutorial";
                Script.setTimeout(playSuccessSound, 150);
                this.active = false;
                onFinish();
            }
        }
        this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500);
    },
    cleanup: function() {
        debug("Orient | Cleanup");
        if (this.active) {
            this.active = false;
        }
        if (this.overlay) {
            this.overlay.destroy();
            this.overlay = null;
        }
        if (this.checkIntervalID) {
            Script.clearInterval(this.checkIntervalID);
            this.checkIntervalID = null;
        }
        //editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 });
        hideEntitiesWithTags(this.tags);
    }
};



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Near Grab                                                           //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepNearGrab = function(tutorialManager) {
    this.name = 'nearGrab';
    this.tags = ["bothGrab", "nearGrab", "nearGrab-" + tutorialManager.controllerName];
    this.tempTag = "nearGrab-temporary";
    this.birdIDs = [];

    this.controllerName = tutorialManager.controllerName;

    Messages.subscribe("Entity-Exploded");
    Messages.messageReceived.connect(this.onMessage.bind(this));
}
stepNearGrab.prototype = {
    start: function(onFinish) {
        this.finished = false;
        this.onFinish = onFinish;

        if (this.controllerName === CONTROLLER_TOUCH) {
            setControllerPartLayer('tips', 'both_triggers');
            setControllerPartLayer('trigger', 'highlight');
            setControllerPartLayer('grip', 'highlight');
        } else {
            setControllerPartLayer('tips', 'trigger');
            setControllerPartLayer('trigger', 'highlight');
        }

        // Show content set
        showEntitiesWithTags(this.tags);

        var boxSpawnPosition = getEntityWithName(NEAR_BOX_SPAWN_NAME).position;
        function createBlock(fireworkNumber) {
            fireworkBaseProps.position = boxSpawnPosition;
            fireworkBaseProps.modelURL = fireworkURLs[fireworkNumber % fireworkURLs.length];
            debug("Creating firework with url: ", fireworkBaseProps.modelURL);
            return spawnWithTag([fireworkBaseProps], null, this.tempTag)[0];
        }

        this.birdIDs = [];
        this.birdIDs.push(createBlock.bind(this)(0));
        this.birdIDs.push(createBlock.bind(this)(1));
        this.birdIDs.push(createBlock.bind(this)(2));
        this.positionWatcher = new PositionWatcher(this.birdIDs, boxSpawnPosition, -0.4, 4);
    },
    onMessage: function(channel, message, seneder) {
        if (this.finished) {
            return;
        }
        if (channel == "Entity-Exploded") {
            debug("NearGrab | Got entity-exploded message: ", message);

            var data = parseJSON(message);
            if (this.birdIDs.indexOf(data.entityID) >= 0) {
                debug("NearGrab | It's one of the firecrackers");
                playFirecrackerSound(data.position);
                playSuccessSound();
                this.finished = true;
                this.onFinish();
            }
        }
    },
    cleanup: function() {
        debug("NearGrab | Cleanup");
        this.finished = true;
        setControllerPartLayer('tips', 'blank');
        setControllerPartLayer('trigger', 'normal');
        setControllerPartLayer('grip', 'normal');
        hideEntitiesWithTags(this.tags);
        deleteEntitiesWithTag(this.tempTag);
        if (this.positionWatcher) {
            this.positionWatcher.destroy();
            this.positionWatcher = null;
        }
    }
};



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Far Grab                                                            //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepFarGrab = function() {
    this.name = 'farGrab';
    this.tag = "farGrab";
    this.tempTag = "farGrab-temporary";
    this.finished = true;
    this.birdIDs = [];

    Messages.subscribe("Entity-Exploded");
    Messages.messageReceived.connect(this.onMessage.bind(this));
}
stepFarGrab.prototype = {
    start: function(onFinish) {
        this.finished = false;
        this.onFinish = onFinish;

        showEntitiesWithTag('bothGrab', { visible: true });

        setControllerPartLayer('tips', 'trigger');
        setControllerPartLayer('trigger', 'highlight');
        Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
            farGrabEnabled: true,
        }));
        var tag = this.tag;

        // Spawn content set
        showEntitiesWithTag(this.tag);

        var boxSpawnPosition = getEntityWithName(FAR_BOX_SPAWN_NAME).position;
        function createBlock(fireworkNumber) {
            fireworkBaseProps.position = boxSpawnPosition;
            fireworkBaseProps.modelURL = fireworkURLs[fireworkNumber % fireworkURLs.length];
            debug("Creating firework with url: ", fireworkBaseProps.modelURL);
            return spawnWithTag([fireworkBaseProps], null, this.tempTag)[0];
        }

        this.birdIDs = [];
        this.birdIDs.push(createBlock.bind(this)(3));
        this.birdIDs.push(createBlock.bind(this)(4));
        this.birdIDs.push(createBlock.bind(this)(5));
        this.positionWatcher = new PositionWatcher(this.birdIDs, boxSpawnPosition, -0.4, 4);
    },
    onMessage: function(channel, message, seneder) {
        if (this.finished) {
            return;
        }
        if (channel == "Entity-Exploded") {
            debug("FarGrab | Got entity-exploded message: ", message);
            var data = parseJSON(message);
            if (this.birdIDs.indexOf(data.entityID) >= 0) {
                debug("FarGrab | It's one of the firecrackers");
                playFirecrackerSound(data.position);
                playSuccessSound();
                this.finished = true;
                this.onFinish();
            }
        }
    },
    cleanup: function() {
        debug("FarGrab | Cleanup");
        this.finished = true;
        setControllerPartLayer('tips', 'blank');
        setControllerPartLayer('trigger', 'normal');
        hideEntitiesWithTag(this.tag, { visible: false});
        deleteEntitiesWithTag(this.tempTag);
        if (this.positionWatcher) {
            this.positionWatcher.destroy();
            this.positionWatcher = null;
        }
    }
};

function PositionWatcher(entityIDs, originalPosition, minY, maxDistance) {
    debug("Creating position watcher");
    this.watcherIntervalID = Script.setInterval(function() {
        for (var i = 0; i < entityIDs.length; ++i) {
            var entityID = entityIDs[i];
            var props = Entities.getEntityProperties(entityID, ['position']);
            if (props.position.y < minY || Vec3.distance(originalPosition, props.position) > maxDistance) {
                Entities.editEntity(entityID, {
                    position: originalPosition,
                    velocity: { x: 0, y: -0.01, z: 0 },
                    angularVelocity: { x: 0, y: 0, z: 0 }
                });
            }
        }
    }, 1000);
}

PositionWatcher.prototype = {
    destroy: function() {
        debug("Destroying position watcher");
        Script.clearInterval(this.watcherIntervalID);
    }
};



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Equip                                                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepEquip = function(tutorialManager) {
    const controllerName = tutorialManager.controllerName;
    this.controllerName = controllerName;

    this.name = 'equip';

    this.tags = ["equip", "equip-" + controllerName];
    this.tagsPart1 = ["equip-part1", "equip-part1-" + controllerName];
    this.tagsPart2 = ["equip-part2", "equip-part2-" + controllerName];
    this.tempTag = "equip-temporary";

    this.PART1 = 0;
    this.PART2 = 1;
    this.PART3 = 2;
    this.COMPLETE = 3;

    Messages.subscribe('Tutorial-Spinner');
    Messages.messageReceived.connect(this.onMessage.bind(this));
}
stepEquip.prototype = {
    start: function(onFinish) {
        if (this.controllerName === CONTROLLER_TOUCH) {
            setControllerPartLayer('tips', 'grip');
            setControllerPartLayer('grip', 'highlight');
        } else {
            setControllerPartLayer('tips', 'trigger');
            setControllerPartLayer('trigger', 'highlight');
        }

        Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
            holdEnabled: true,
        }));

        var tag = this.tag;

        // Spawn content set
        showEntitiesWithTags(this.tags);
        showEntitiesWithTags(this.tagsPart1);

        this.currentPart = this.PART1;

        function createLighter() {
            var transform = {};

            var boxSpawnProps = getEntityWithName(GUN_SPAWN_NAME);
            transform.position = boxSpawnProps.position;
            transform.rotation = boxSpawnProps.rotation;
            transform.velocity = { x: 0, y: -0.01, z: 0 };
            transform.angularVelocity = { x: 0, y: 0, z: 0 };
            this.spawnTransform = transform;
            return doCreateButaneLighter(transform).id;
        }


        this.lighterID = createLighter.bind(this)();
        this.startWatchingLighter();
        debug("Created lighter", this.lighterID);
        this.onFinish = onFinish;
    },
    startWatchingLighter: function() {
        if (!this.watcherIntervalID) {
            debug("Starting to watch lighter position");
            this.watcherIntervalID = Script.setInterval(function() {
                debug("Checking lighter position");
                var props = Entities.getEntityProperties(this.lighterID, ['position']);
                if (props.position.y < -0.4
                        || Vec3.distance(this.spawnTransform.position, props.position) > 4) {
                    debug("Moving lighter back to table");
                    Entities.editEntity(this.lighterID, this.spawnTransform);
                }
            }.bind(this), 1000);
        }
    },
    stopWatchingGun: function() {
        if (this.watcherIntervalID) {
            debug("Stopping watch of lighter position");
            Script.clearInterval(this.watcherIntervalID);
            this.watcherIntervalID = null;
        }
    },
    onMessage: function(channel, message, sender) {
        if (this.currentPart == this.COMPLETE) {
            return;
        }

        debug("Equip | Got message", channel, message, sender, MyAvatar.sessionUUID);

        if (channel == "Tutorial-Spinner") {
            if (this.currentPart == this.PART1 && message == "wasLit") {
                this.currentPart = this.PART2;
                debug("Equip | Starting part 2");
                Script.setTimeout(function() {
                    debug("Equip | Starting part 3");
                    this.currentPart = this.PART3;
                    hideEntitiesWithTags(this.tagsPart1);
                    showEntitiesWithTags(this.tagsPart2);
                    setControllerPartLayer('trigger', 'normal');
                    setControllerPartLayer('grip', 'highlight');
                    setControllerPartLayer('tips', 'grip');
                    Messages.subscribe('Hifi-Object-Manipulation');
                    debug("Equip | Finished starting part 3");
                }.bind(this), 9000);
            }
        } else if (channel == "Hifi-Object-Manipulation") {
            if (this.currentPart == this.PART3) {
                var data = parseJSON(message);
                if (data.action == 'release' && data.grabbedEntity == this.lighterID) {
                    debug("Equip | Got release, finishing step");
                    this.stopWatchingGun();
                    this.currentPart = this.COMPLETE;
                    playSuccessSound();
                    Script.setTimeout(this.onFinish.bind(this), 1500);
                }
            }
        }
    },
    cleanup: function() {
        debug("Equip | Got yaw action");
        if (this.watcherIntervalID) {
            Script.clearInterval(this.watcherIntervalID);
            this.watcherIntervalID = null;
        }

        setControllerPartLayer('tips', 'blank');
        setControllerPartLayer('grip', 'normal');
        setControllerPartLayer('trigger', 'normal');
        this.stopWatchingGun();
        this.currentPart = this.COMPLETE;

        if (this.checkCollidesTimer) {
            Script.clearInterval(this.checkCollidesTimer);
            this.checkColllidesTimer = null;
        }

        hideEntitiesWithTags(this.tagsPart1);
        hideEntitiesWithTags(this.tagsPart2);
        hideEntitiesWithTags(this.tags);
        deleteEntitiesWithTag(this.tempTag);
    }
};




///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Turn Around                                                         //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepTurnAround = function(tutorialManager) {
    this.name = 'turnAround';

    this.tags = ["turnAround", "turnAround-" + tutorialManager.controllerName];
    this.tempTag = "turnAround-temporary";

    this.onActionBound = this.onAction.bind(this);
    this.numTimesSnapTurnPressed = 0;
    this.numTimesSmoothTurnPressed = 0;
}
stepTurnAround.prototype = {
    start: function(onFinish) {
        setControllerPartLayer('joystick', 'highlight');
        setControllerPartLayer('touchpad', 'arrows');
        setControllerPartLayer('tips', 'arrows');

        showEntitiesWithTags(this.tags);

        this.numTimesSnapTurnPressed = 0;
        this.numTimesSmoothTurnPressed = 0;
        this.smoothTurnDown = false;
        Controller.actionEvent.connect(this.onActionBound);

        this.interval = Script.setInterval(function() {
            debug("TurnAround | Checking if finished",
                  this.numTimesSnapTurnPressed, this.numTimesSmoothTurnPressed);
            var FORWARD_THRESHOLD = 90;
            var REQ_NUM_TIMES_SNAP_TURN_PRESSED = 3;
            var REQ_NUM_TIMES_SMOOTH_TURN_PRESSED = 2;

            var dir = Quat.getFront(MyAvatar.orientation);
            var angle = Math.atan2(dir.z, dir.x);
            var angleDegrees = ((angle / Math.PI) * 180);

            var hasTurnedEnough = this.numTimesSnapTurnPressed >= REQ_NUM_TIMES_SNAP_TURN_PRESSED
                || this.numTimesSmoothTurnPressed >= REQ_NUM_TIMES_SMOOTH_TURN_PRESSED;
            var facingForward = Math.abs(angleDegrees) < FORWARD_THRESHOLD
            if (hasTurnedEnough && facingForward) {
                Script.clearInterval(this.interval);
                this.interval = null;
                playSuccessSound();
                onFinish();
            }
        }.bind(this), 100);
    },
    onAction: function(action, value) {
        var STEP_YAW_ACTION = 6;
        var SMOOTH_YAW_ACTION = 4;

        if (action == STEP_YAW_ACTION && value != 0) {
            debug("TurnAround | Got step yaw action");
            ++this.numTimesSnapTurnPressed;
        } else if (action == SMOOTH_YAW_ACTION) {
            debug("TurnAround | Got smooth yaw action");
            if (this.smoothTurnDown && value === 0) {
                this.smoothTurnDown = false;
                ++this.numTimesSmoothTurnPressed;
            } else if (!this.smoothTurnDown && value !== 0) {
                this.smoothTurnDown = true;
            }
        }
    },
    cleanup: function() {
        debug("TurnAround | Cleanup");
        try {
            Controller.actionEvent.disconnect(this.onActionBound);
        } catch (e) {
        }

        setControllerPartLayer('joystick', 'normal');
        setControllerPartLayer('touchpad', 'blank');
        setControllerPartLayer('tips', 'blank');

        if (this.interval) {
            Script.clearInterval(this.interval);
        }
        hideEntitiesWithTags(this.tags);
        deleteEntitiesWithTag(this.tempTag);
    }
};




///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Teleport                                                            //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepTeleport = function(tutorialManager) {
    this.name = 'teleport';

    this.tags = ["teleport", "teleport-" + tutorialManager.controllerName];
    this.tempTag = "teleport-temporary";
}
stepTeleport.prototype = {
    start: function(onFinish) {
        setControllerPartLayer('button_a', 'highlight');
        setControllerPartLayer('touchpad', 'teleport');
        setControllerPartLayer('tips', 'teleport');

        Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none');

        // Wait until touching teleport pad...
        var padProps = getEntityWithName(TELEPORT_PAD_NAME);
        var xMin = padProps.position.x - padProps.dimensions.x / 2;
        var xMax = padProps.position.x + padProps.dimensions.x / 2;
        var zMin = padProps.position.z - padProps.dimensions.z / 2;
        var zMax = padProps.position.z + padProps.dimensions.z / 2;
        function checkCollides() {
            debug("Teleport | Checking if on pad...");

            var pos = MyAvatar.position;

            debug('Teleport | x', pos.x, xMin, xMax);
            debug('Teleport | z', pos.z, zMin, zMax);

            if (pos.x > xMin && pos.x < xMax && pos.z > zMin && pos.z < zMax) {
                debug("Teleport | On teleport pad");
                Script.clearInterval(this.checkCollidesTimer);
                this.checkCollidesTimer = null;
                playSuccessSound();
                onFinish();
            }
        }
        this.checkCollidesTimer = Script.setInterval(checkCollides.bind(this), 500);

        showEntitiesWithTags(this.tags);
    },
    cleanup: function() {
        debug("Teleport | Cleanup");
        setControllerPartLayer('button_a', 'normal');
        setControllerPartLayer('touchpad', 'blank');
        setControllerPartLayer('tips', 'blank');

        if (this.checkCollidesTimer) {
            Script.clearInterval(this.checkCollidesTimer);
        }
        hideEntitiesWithTags(this.tags);
        deleteEntitiesWithTag(this.tempTag);
    }
};





///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// STEP: Finish                                                              //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
var stepFinish = function() {
    this.name = 'finish';

    this.tag = "finish";
    this.tempTag = "finish-temporary";
}
stepFinish.prototype = {
    start: function(onFinish) {
        editEntitiesWithTag('door', { visible: false, collisonless: true });
        showEntitiesWithTag(this.tag);
        Settings.setValue("tutorialComplete", true);
        onFinish();
    },
    cleanup: function() {
    }
};

var stepCleanupFinish = function() {
    this.name = 'cleanup';

    this.shouldLog = false;
}
stepCleanupFinish.prototype = {
    start: function(onFinish) {
        hideEntitiesWithTag('finish');
        onFinish();
    },
    cleanup: function() {
    }
};





TutorialManager = function() {
    var STEPS;

    var currentStepNum = -1;
    var currentStep = null;
    var startedTutorialAt = 0;
    var startedLastStepAt = 0;
    var didFinishTutorial = false;

    var wentToEntryStepNum;
    var VERSION = 2;
    var tutorialID;

    var self = this;

    // The real controller name is the actual detected controller name, or 'unknown'
    // if one is not found.
    if (HMD.isSubdeviceContainingNameAvailable("OculusTouch")) {
        this.controllerName = "touch";
        this.realControllerName = "touch";
    } else if (HMD.isHandControllerAvailable("OpenVR")) {
        this.controllerName = "vive";
        this.realControllerName = "vive";
    } else {
        info("ERROR, no known hand controller found, defaulting to Vive");
        this.controllerName = "vive";
        this.realControllerName = "unknown";
    }

    this.startTutorial = function() {
        currentStepNum = -1;
        currentStep = null;
        startedTutorialAt = Date.now();

        // Old versions of interface do not have the Script.generateUUID function.
        // If Script.generateUUID is not available, default to an empty string.
        tutorialID = Script.generateUUID ? Script.generateUUID() : "";
        STEPS = [
            new stepStart(this),
            new stepOrient(this),
            new stepFarGrab(this),
            new stepNearGrab(this),
            new stepEquip(this),
            new stepTurnAround(this),
            new stepTeleport(this),
            new stepFinish(this),
            new stepEnableControllers(this),
        ];
        wentToEntryStepNum = STEPS.length;
        for (var i = 0; i < STEPS.length; ++i) {
            STEPS[i].cleanup();
        }
        MyAvatar.shouldRenderLocally = false;
        this.startNextStep();
    }

    this.onFinish = function() {
        debug("onFinish", currentStepNum);
        if (currentStep && currentStep.shouldLog !== false) {
            self.trackStep(currentStep.name, currentStepNum);
        }

        self.startNextStep();
    }

    this.startNextStep = function() {
        if (currentStep) {
            currentStep.cleanup();
        }

        ++currentStepNum;

        // This always needs to be set because we use this value when
        // tracking that the user has gone through the entry portal. When the
        // tutorial finishes, there is a last "pseudo" step that the user
        // finishes when stepping into the portal.
        startedLastStepAt = Date.now();

        if (currentStepNum >= STEPS.length) {
            // Done
            info("DONE WITH TUTORIAL");
            currentStepNum = -1;
            currentStep = null;
            didFinishTutorial = true;
            return false;
        } else {
            info("Starting step", currentStepNum);
            currentStep = STEPS[currentStepNum];
            currentStep.start(this.onFinish);
            return true;
        }
    }.bind(this);

    this.restartStep = function() {
        if (currentStep) {
            currentStep.cleanup();
            currentStep.start(this.onFinish);
        }
    }

    this.stopTutorial = function() {
        if (currentStep) {
            currentStep.cleanup();
            HMD.requestHideHandControllers();
        }
        reenableEverything();
        currentStepNum = -1;
        currentStep = null;
    }

    this.trackStep = function(name, stepNum) {
        var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000;
        var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
        UserActivityLogger.tutorialProgress(
                name, stepNum, timeToFinishStep, tutorialTimeElapsed,
                tutorialID, VERSION, this.realControllerName);
    }

    // This is a message sent from the "entry" portal in the courtyard,
    // after the tutorial has finished.
    this.enteredEntryPortal = function() {
        info("Got enteredEntryPortal");
        if (didFinishTutorial) {
            info("Tracking wentToEntry");
            this.trackStep("wentToEntry", wentToEntryStepNum);
        }
    }
}

// To run the tutorial:
//
//var tutorialManager = new TutorialManager();
//tutorialManager.startTutorial();
//
//
//var keyReleaseHandler = function(event) {
//    if (event.isShifted && event.isAlt) {
//        print('here', event.text);
//        if (event.text == "F12") {
//            if (!tutorialManager.startNextStep()) {
//                tutorialManager.startTutorial();
//            }
//        } else if (event.text == "F11") {
//            tutorialManager.restartStep();
//        } else if (event.text == "F10") {
//            MyAvatar.shouldRenderLocally = !MyAvatar.shouldRenderLocally;
//        } else if (event.text == "r") {
//            tutorialManager.stopTutorial();
//            tutorialManager.startTutorial();
//        }
//    }
//};
//Controller.keyReleaseEvent.connect(keyReleaseHandler);