// Copyright (C) 2013 Massachusetts Institute of Technology
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

// Scratch HTML5 Player
// Runtime.js
// Tim Mickel, July 2011

// Runtime takes care of the rendering and stepping logic.

'use strict';

var t = new Timer();

var Runtime = function() {
    this.scene = null;
    this.sprites = [];
    this.reporters = [];
    this.keysDown = {};
    this.mouseDown = false;
    this.mousePos = [0, 0];
    this.audioContext = null;
    this.audioGain = null;
    this.audioPlaying = [];
    this.notesPlaying = [];
    this.projectLoaded = false;
};

// Initializer for the drawing and audio contexts.
Runtime.prototype.init = function() {
    this.scene = $('#container');
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    this.audioContext = new AudioContext();
		try{
	    this.audioGain = this.audioContext.createGain();
		}catch(err){
			this.audioGain = this.audioContext.createGainNode();
		}
    this.audioGain.connect(runtime.audioContext.destination);
};

// Load start waits for the stage and the sprites to be loaded, without
// hanging the browser.  When the loading is finished, we begin the step
// and animate methods.
Runtime.prototype.loadStart = function() {
    if (!runtime.stage.isLoaded()) {
        setTimeout(function(runtime) { runtime.loadStart(); }, 50, this);
        return;
    }
    for (var obj = 0; obj < runtime.sprites.length; obj++) {
        if (typeof(runtime.sprites[obj]) == 'object' && runtime.sprites[obj].constructor == Sprite) {
            if (!runtime.sprites[obj].isLoaded()) {
                setTimeout(function(runtime) { runtime.loadStart(); }, 50, this);
                return;
            }
        }
    }
    if (Instr.wavsLoaded != Instr.wavCount) {
        setTimeout(function(runtime) { runtime.loadStart(); }, 50, this);
        return;
    }
    $('#preloader').css('display', 'none');
    setInterval(this.step, 33);
    this.projectLoaded = true;
};

Runtime.prototype.greenFlag = function() {
    if (this.projectLoaded) {
        interp.activeThread = new Thread(null);
        interp.threads = [];
        interp.primitiveTable.timerReset();
        this.startGreenFlags();
    }
};

Runtime.prototype.stopAll = function() {
    interp.activeThread = new Thread(null);
    interp.threads = [];
    stopAllSounds();
    // Hide reporters
    for (var s = 0; s < runtime.sprites.length; s++) {
        if (typeof runtime.sprites[s].hideBubble == 'function') {
            runtime.sprites[s].hideBubble();
        }
    }
    // Reset graphic effects
    runtime.stage.resetFilters();
    for (var s = 0; s < runtime.sprites.length; s++) {
        runtime.sprites[s].resetFilters();
    }
};

// Step method for execution - called every 33 milliseconds
Runtime.prototype.step = function() {
    interp.stepThreads();
    for (var r = 0; r < runtime.reporters.length; r++) {
        runtime.reporters[r].update();
    }
};

// Stack functions -- push and remove stacks
// to be run by the interpreter as threads.
Runtime.prototype.allStacksDo = function(f) {
    var stage = runtime.stage;
    var stack;
    for (var i = runtime.sprites.length-1; i >= 0; i--) {
        var o = runtime.sprites[i];
        if (typeof(o) == 'object' && o.constructor == Sprite) {
            $.each(o.stacks, function(index, stack) {
                f(stack, o);
            });
        }
    }
    $.each(stage.stacks, function(index, stack) {
       f(stack, stage);
    });
};

// Hat triggers
Runtime.prototype.startGreenFlags = function() {
    function startIfGreenFlag(stack, target) {
        if (stack.op == 'whenGreenFlag') interp.toggleThread(stack, target);
    }
    this.allStacksDo(startIfGreenFlag);
};

Runtime.prototype.startKeyHats = function(ch) {
    var keyName = null;
    if (('A'.charCodeAt(0) <= ch) && (ch <= 'Z'.charCodeAt(0)) ||
        ('a'.charCodeAt(0) <= ch) && (ch <= 'z'.charCodeAt(0)))
        keyName = String.fromCharCode(ch).toLowerCase();
    if (('0'.charCodeAt(0) <= ch) && (ch <= '9'.charCodeAt(0)))
        keyName = String.fromCharCode(ch);

    if (ch == 37) keyName = "left arrow";
    if (ch == 39) keyName = "right arrow";
    if (ch == 38) keyName = "up arrow";
    if (ch == 40) keyName = "down arrow";
    if (ch == 32) keyName = "space";

    if (keyName == null) return;
    var startMatchingKeyHats = function(stack, target) {
        if ((stack.op == "whenKeyPressed") && (stack.args[0] == keyName)) {
            // Only start the stack if it is not already running
            if (!interp.isRunning(stack)) {
                interp.toggleThread(stack, target);
            }
        }
    }
    runtime.allStacksDo(startMatchingKeyHats);
};

Runtime.prototype.startClickedHats = function(sprite) {
    function startIfClicked(stack, target) {
        if (target == sprite && stack.op == "whenClicked" && !interp.isRunning(stack)) {
            interp.toggleThread(stack, target);
        }
    }
    runtime.allStacksDo(startIfClicked);
};

// Returns true if a key is pressed.
Runtime.prototype.keyIsDown = function(ch) {
    return this.keysDown[ch] || false;
};

// Sprite named -- returns one of the sprites on the stage.
Runtime.prototype.spriteNamed = function(n) {
    if (n == 'Stage') return this.stage;
    var selected_sprite = null;
    $.each(this.sprites, function(index, s) {
        if (s.objName == n) {
            selected_sprite = s;
            return false;
        }
    });
    return selected_sprite;
};

Runtime.prototype.getTimeString = function(which) {
    // Return local time properties.
    var now = new Date();
    switch (which) {
        case 'hour': return now.getHours();
        case 'minute': return now.getMinutes();
        case 'second': return now.getSeconds();
        case 'year': return now.getFullYear(); // four digit year (e.g. 2012)
        case 'month': return now.getMonth() + 1; // 1-12
        case 'date': return now.getDate(); // 1-31
        case 'day of week': return now.getDay() + 1; // 1-7, where 1 is Sunday
    }
    return ''; // shouldn't happen
};

// Reassigns z-indices for layer functions
Runtime.prototype.reassignZ = function(target, move) {
    var sprites = this.sprites;
    var oldIndex = -1;
    $.each(this.sprites, function(index, sprite) {
        if (sprite == target) {
            // Splice out the sprite from its old position
            oldIndex = index;
            sprites.splice(index, 1);
        }
    });

    if (move == null) {
        // Move to the front
        this.sprites.splice(this.sprites.length, 0, target);
    } else if (oldIndex - move >= 0 && oldIndex - move < this.sprites.length + 1) {
        // Move to the new position
        this.sprites.splice(oldIndex - move, 0, target);
    } else {
        // No change is required
        this.sprites.splice(oldIndex, 0, target);
    }

    // Renumber the z-indices
    var newZ = 1;
    $.each(this.sprites, function(index, sprite) {
        sprite.z = newZ;
        sprite.updateLayer();
        newZ++;
    });
};