/*
AE JavaScript Library for SketchUp WebDialogs (extract)
Version:      1.1.5
Date:         08.05.2014
Summary:
module AE
  module Bridge                    // Communication with Ruby. Requires AE::Dialog
    .callRuby(name, *arguments [,callbackFunction]) // Executes a Ruby action_callback with any amount of arguments.
    .nextMessage()                 // Called from Ruby to send the next message from the queue.
    .callbackJS(id [,data])        // Called from a Ruby action_callback to run another JavaScript callback.
    .initialize()                  // Tell SketchUp that the dialog is ready.

  class Scheduler(delay)
    .queue(function)
    .replace(function)
    .wait(criterium_or_delay, function)
*/


/**
 * @namespace
 */
var AE = function(AE) {


/**
 * Debugging mode
 */
AE.debug = false;


/**
 * Whether debugging is done in an external browser.
 * Disables the use of the skp protocol and logs to the browser's console.
 */
AE.debug_no_skp = false;


/**
 * @const PLATFORM
 */
AE.PLATFORM = (navigator.appVersion.indexOf("Win") != -1)? "WIN" : ((navigator.appVersion.indexOf("OS X")!=-1)? "OSX" : "other");
AE.RUBY_VERSION = 0;     // Needs to be set from SketchUp.
AE.SKETCHUP_VERSION = 0; // Needs to be set from SketchUp.


/**
 * @module Translate
 * Stub module for the case that no translation has been loaded.
 */
if (!AE.Translate) { AE.Translate = { get: function(s) { return s; } }; }


/**
 * @module Bridge:
 * Methods to interact with SketchUp/Ruby.
 */
AE.Bridge = function(self) {


var messageID = 0;

// Private object to hold callback functions.
var callbacks = {};

// SketchUp/OSX/Safari skips skp urls if they happen in a too short time interval.
// We pass all skp urls through a queue that makes sure that a message is only
// sent after the SketchUp side has received the previous message.
var messageQueue = [];
var messageField = null;

// Whether the queue is ready to send another messge.
var ready = true;


/**
 * Calls a SketchUp Ruby callback.
 * @param {string} name - The name of the Ruby action_callback.
 * @param {...Object} argument - Any amount of arguments of JSON type.
 * @param {function} callback - A JavaScript function to execute after the action_callback.
 * @returns {number} - The id of the sent message.
 */
self.callRuby = function(name, argument, callback) {
  var args = Array.prototype.slice.call(arguments).slice(1),
    callback = (typeof(args[args.length-1]) === 'function') ? args.pop() : null,
    data = {id: messageID, name: name, arguments: args};
  // In case of debugging, just log the arguments to the console.
  if (AE.debug_no_skp) {
    console.log(name, args);
  } else {
    data = (AE.RUBY_VERSION > 1.9) ? JSON.stringify(data) : JSONstringify(data);
    // We assign an id to this message so we can identify a callback (if there is one).
    if (callback) { callbacks[messageID] = callback; }
    var url = "skp:AE.Bridge.receive_message";
    messageQueue.push([url, data]);
    // If the queue is not running, start it. If the message queue contains many urls, then it is already running.
    if (ready) { self.nextMessage(); }
  }
  // Increase the id for the next message.
  messageID++;
  // Return the id of this message.
  return messageID - 1;
};


/**
 * Tells the bridge that the next message can be sent.
 * @private (only for use by this file or Ruby AE::Dialog)
 */
self.nextMessage = function() {
  var message = messageQueue.shift();
  if (!message) { ready = true; return; }
  // Lock the status variable before sending the message.
  // (because window.location is synchronous in IE and finishes before this
  // function finishes.)
  ready = false;
  // Prepare the message field, so that SketchUp can obtain the message data from here.
  var url = message[0],
      data = message[1];
  fillMessageField(data);
  // Give enough time to refresh the DOM, so that SketchUp will be able to
  // access the latest values of input elements.
  window.setTimeout(function(){
    window.location.href = url;
  }, 0);
};


/**
 * Calls a JavaScript callback from a SketchUp Ruby action_callback.
 * @private (only for use by Ruby AE::Dialog)
 * @param {number} id - The id of the JavaScript-to-Ruby message.
 * @param {...argument} argument - Any amount of arguments.
 */
self.callbackJS = function(id, argument) {
  var args = Array.prototype.slice.call(arguments).slice(1);
  // If there is a callback, execute it.
  if (id && callbacks[id]) {
    try {
      callbacks[id].apply(window, args)
    } catch (e) {
      self.error("AE.Bridge.callbackJS: Error when executing callback #"+id+": "+e);
    }
    delete callbacks[id];
  }
};


/**
 * Tells SketchUp that the dialog is loaded.
 * AE::Dialog will run on_show callbacks.
 */
self.initialize = function() {
  self.callRuby("AE.Bridge.initialize");
};


/* For debugging. */
self.puts = function(text) {
  self.callRuby("puts",  text);
};


/* For debugging. */
self.error = function(text) {
  self.callRuby("error",  text);
};


/* For debugging. If no browser console defined, link to the Ruby Console. */
window.console = window.console || {
  log: function(text) { self.puts(text); }
};


/** private */


function fillMessageField(value) {
  if (!messageField) { createMessageField(); }
  messageField.value = value;
};


function createMessageField() {
  messageField = document.createElement('input');
  messageField.setAttribute('type', 'hidden');
  messageField.setAttribute('style', 'display: none');
  messageField.setAttribute('id', 'AE.Bridge.messageField');
  document.getElementsByTagName("body")[0].appendChild(messageField);
};


/**
 * Fallback JSON.stringify method that does not encode unicode characters.
 * Important for older Ruby versions that don't understand unicode escaping (\u00f6)
 * but treat unicode multibyte characters as multiple single byte characters.
 * @param {object} object
 * @returns {string}
 */
function JSONstringify(object) {
  var jsonString = '';
  // null
  if (object === null || typeof object === 'undefined') {
    jsonString = 'null';
  // array
  } else if (Object.prototype.toString.call(object) === '[object Array]') {
    var a = [];
    for (var i = 0; i < object.length; i++) {
      var v = JSONstringify(object[i]);
      if (!!v) { a.push(v) }
    }
    jsonString = '[' + a.join(', ') + ']';
  // object literal
  } else if (Object.prototype.toString.call(object) === '[object Object]') {
    var o = [];
    for (var e in object) {
      if (e === object) { continue; } // ignore circle references. Should never occur!
      // if (Object.prototype.hasOwnProperty.call(object, e)) { // causes "out of memory" ?
      var k = JSONstringify(e);
      var v = JSONstringify(object[e]);
      if (!!k && !!v) { o.push(k + ': ' + v) }
      // }
    }
    jsonString = '{' + o.join(', ') + '}';
  // string
  } else if (typeof object === 'string') {
    // Escaping according to JSON spec
    jsonString = '"' + object.replace(/\\/g, '\\\\').replace(/\//g, '\\/')
      .replace(/\"/g, '\\\"').replace(/\f/g, '\\f')
      .replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t')
      // Escaping to prevent Ruby string interpolation.
      .replace(/#/g, '\\#') + '"';
  // number
  } else if (typeof object === 'number') {
    if (isNaN(object)) { jsonString = '0.0/0.0'; }
    else if (!isFinite(object)) { jsonString = (object > 0) ? '1.0/0.0' : '-1.0/0.0'; }
    else { jsonString = String(object); }
  } else if (typeof object === 'boolean') {
    jsonString = String(object);
  } // else: undefined
  return jsonString;
}


return self;
}(AE.Bridge || {}); // end @module Bridge


/**
 * @class
 * This class (to be instanced) makes sure given functions are not called more
 * frequently than a certain time limit. Note that the first function is run
 * immediately and synchronously.
 * @param {number} dt - Minimum time interval in milliseconds between subsequent
 *   function calls.
 */
AE.Scheduler = function(dt) {
  var that = this;
  var scheduled = []; // Array of scheduled functions.
  var t = 0; // Tracks the time of the last function call.
  var interval = null;
  dt = (dt) ? Number(dt) : 250;


  /**
   * Adds a new function. All collected functions will be executed one by one
   * with time interval inbetween.
   * @param {function} fn
   */
  this.queue = function(fn) {
    scheduled.push(fn);
    check();
  };


  /**
   * Adds a new function to be executed instead of the last function. This allows
   * to update actions, ie. with more up-to-date data or redrawing something etc.
   * @param {function} fn
   */
  this.replace = function(fn) {
    if (scheduled.length > 0) { scheduled.pop(); }
    scheduled.push(fn);
    check();
  };


  var run = function() {
    var toRun = scheduled.shift();
    // Function
    if (typeof toRun === "function") { toRun(); }
    // Array of functions
    else if (Object.prototype.toString.call(toRun) === '[object Array]') {
      for (var i=0; i<toRun.length; i++) {
        toRun[i]();
      }
    }
  };


  /**
   * Lets the scheduler wait until the condition of function 1 is true, optionally
   * adds function 2 to the queue.
   * @param {number, function->boolean} time to wait or criterium to become true
   * @param {function} fn
   */
  this.wait = function(criterium, fn) {
    if (criterium === "number") {
      t += criterium;
      check();
    } else if (typeof criterium == "function") {
      t = Infinity;
      interval = window.setInterval( function(){
        if ( criterium() ) {
          window.clearInterval(interval);
          t = Number(new Date().getTime());
          check();
        }
      }, dt);
    }
    if (typeof fn === "function") { that.add(fn); }
  };


  /**
   * Stops the criterium of wait() and executes the next function from the queue.
   * @param {number, function} criterium
   * @param {function} fn
   */
  this.continu = function() {
    window.clearInterval(interval);
    t = Number(new Date().getTime());
    check();
  };


  var check = function() {
    var c = Number(new Date().getTime());
    // Last function call is long enough ago (or first time), execute given function immediately.
    if (c >= t && scheduled.length > 0) {
      run();
      // Set timer for next possible function call.
      t = c + dt;
      window.setTimeout(check, dt);
    }
  };


}; // end @class Scheduler


return AE;
}(AE || {}); // end @module AE
