// requires AE.Translate, AE.Bridge


var AE = window.AE || {};


/**
 * @module
 */
AE.ToolbarEditor = function(self) {


// Object to hold data.
var Index = [];
var availableButtons = [];
var toolbars = [];


/**
 * Escapes a String for generating a regular Expression.
 * @param {string} string
 * @returns {string}
 * @private
 */
var escapeRegExp = function(string) {
  return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}


/**
 * jQuery on document ready
 */
$(function() {

  // Allow to remove items from sortable.
  $("#buttons-available").droppable({
    accept: ".toolbar .toolbar-button",
    drop: function( event, ui ) {
      ui.helper.remove();
      ui.draggable.remove();
    }
  });

  // Button to add a new toolbar
  $("#button-add-toolbar").on("click", function() { new Toolbar().activate() });

  var searchScheduler = new AE.Scheduler();
  // Search box // TODO: Does this sometimes not trigger in IE?
  $("#input-filter").on("keydown", function() {
    searchScheduler.replace(function() {
      var match;
      var searchWord = $("#input-filter").val();
      if (searchWord.length > 0) {
        // Hide all buttons.
        for (var i = 0; i < availableButtons.length; i++) {
          try{ availableButtons[i].hide(); } catch(e){}
        }
        var searchExp = new RegExp(escapeRegExp(searchWord), "i");
        // Show those buttons that match.
        $("#buttons-available li").each(function() {
          match = ($(this).text().search(searchExp) !== -1);
          if (match) { $(this).show(); }
        });
      }
      // If no filter is applied, show all again.
      else {
        for (var i = 0; i < availableButtons.length; i++) {
          try{ availableButtons[i].show(); } catch(e){}
        }
      }
    })
  });

  // Button to add a new custom button
  DialogEditCustomButton = new DialogEditCustomButton();
  $("#button-add-custom-button").on("click", function() { 
    DialogEditCustomButton.show(function(entry) {
      var button = new CustomButton(entry);
      // Allow to drag a copy of the button into any toolbars.
      button.jQueryElement.draggable({
        connectToSortable: ".toolbar",
        // We need to put the helper outside of the overflow:scroll/hidden element.
        helper: "clone",
        appendTo: document.body,
        scroll: false,
        revert: "invalid",
        cursor: "move", cursorAt: { top: 10, left: 20 }
      });
      // Add the button to the list.
      availableButtons.push(button.jQueryElement.get(0));
      $("#buttons-available").prepend(button.jQueryElement.get(0));
    });
  });

  // Apply button
  $("#button-apply").on("click", function() {
    var data = {}, toolbar;
    for (var i = 0; i < toolbars.length; i++) {
      toolbar = toolbars[i];
      if (toolbar.buttons.length > 0) {
        data[toolbar.id] = {"name": toolbar.name, "buttons": toolbar.buttons};
      };
    };
    AE.Bridge.callRuby("save_toolbars", data);
  });

  // Cancel button
  $("#button-cancel").on("click", function() {
    AE.Bridge.callRuby("close");
  });

  // Disable text selection on various UI elements.
  $("ul, li").disableSelection();

});


/**
 * Build a list of custom buttons.
 * @param {array<object>} array - list of objects <name,description,icon,id>
 */
self.loadCustomButtons = function(array) {
  if (!Object.prototype.toString.call(array) === '[object Array]') { return; }
  // Keep the button metadata in an index.
  Index = Index.concat(array);
  // Generate buttons.
  var buttons = [], button, entry;
  for (var i = 0; i < array.length; i++) {
    entry = array[i];
    button = new CustomButton(entry);
    if (!button.jQueryElement) { continue; }
    // Allow to drag a copy of the button into any toolbars.
    button.jQueryElement.draggable({
      connectToSortable: ".toolbar",
      // We need to put the helper outside of the overflow:scroll/hidden element.
      helper: "clone",
      appendTo: document.body,
      scroll: false,
      revert: "invalid",
      cursor: "move", cursorAt: { top: 10, left: 20 }
    });
    buttons.push(button.jQueryElement);
  }
  // Add the buttons to the list.
  $("#buttons-available").prepend(buttons);
  availableButtons = availableButtons.concat(buttons);
};


/**
 * Build a list of available buttons.
 * @param {array<object>} array - list of objects <name,description,icon,id>
 */
self.loadButtons = function(array) {
  if (!Object.prototype.toString.call(array) === '[object Array]') { return; }
  // Loading of buttons finished, now we can remove the indicator.
  $("#loading").remove();
  // Add a separator.
  array.unshift({
    id: 0,
    name: AE.Translate.get("Separator"),
    description: AE.Translate.get("Add a separator to group toolbar buttons."),
    icon: "../images/separator.png"
  });
  // Keep the button metadata in an index.
  Index = Index.concat(array);
  // Generate buttons.
  var boo = true;
  // Add all buttons asynchronously in chunks of 20.
  for (var i = 0; i < array.length; i += 20) {
    (function (i) {
      setTimeout(function(){
        // Add a chunk of 20 buttons.
        var buttons = [];
        for (var j = i; j < i+20 && j < array.length; j++) {
          // Add one button.
          var entry = array[j];
          if (!entry) { continue; }
          var button = new ToolbarButton(entry.id, entry);
          if (!button.jQueryElement) { return; }
          // Allow to drag a copy of the button into any toolbars.
          button.jQueryElement.draggable({
            connectToSortable: ".toolbar",
            // We need to put the helper outside of the overflow:scroll/hidden element.
            helper: "clone",
            appendTo: document.body,
            scroll: false,
            revert: "invalid",
            cursor: "move", cursorAt: { top: 10, left: 20 }
          });
          // Add the button to the list.
          buttons.push(button.jQueryElement);
        }
        $("#buttons-available").append(buttons);
        availableButtons = availableButtons.concat(buttons);
      }, 0);
    })(i);
  }
};


/**
 * Build a list of created toolbars.
 * @param {array<object>} array - list of objects <name,description,icon,id>
 */
self.loadToolbars = function(array) {
  if (Object.prototype.toString.call(array) !== '[object Array]') { return; }
  var entry, toolbar, buttons;
  for (var i = 0; i < array.length; i++) {
    entry = array[i];
    if (entry.id >= toolbarCounter) { toolbarCounter = entry.id + 1; }
    toolbar = new Toolbar(entry.name, entry.id);
    buttons = entry.buttons;
    for (var j = 0; j < buttons.length; j++) {
      toolbar.addButton(buttons[j]);
    }
  }
  // Create a new toolbar if none exists.
  if (array.length == 0) {
    toolbar = new Toolbar();
  }
  // Select the last toolbar.
  if (toolbars.length > 0) {
    toolbars[toolbars.length - 1].activate();
  }
};


/**
 * @class Toolbar
 * @param {string} name
 * @param {numeric} id
 */
var toolbarCounter = 0;
var Toolbar = function(name, id) {

  /** @private */
  var toolbar = this;
  var header, title, titleEdit, buttonRemove, buttonEdit, buttonWrapper, content, statusEditName = false;

  /** @public */
  toolbar.id = (typeof id === "number") ? id : (toolbarCounter += 1);
  toolbar.name = (typeof name == "string") ? name :
    AE.Translate.get("Toolbar") + " " + toolbar.id;
  toolbar.buttons = [];

  /**
   * Initializes the new toolbar and adds it to the UI.
   * @constructor
   * @private
   */
  var initialize = function() {
    // Header with buttons.
    header = $("<h3/>").
      attr({"class": "toolbar header"});
    header.get(0).toolbar = toolbar; // reference
    title = $("<span/>").text(toolbar.name);
    titleEdit = $("<input/>").
      attr({"type": "text", "value": toolbar.name}).
      hide().
      on("keydown", function(event) {
        // Enter
        if (event.which == 13) {
          toolbar.finishEditName();
          event.preventDefault();
        }
        event.stopPropagation();
      }).
      on("blur", function() {
        toolbar.finishEditName();
        statusEditName = false;
      });
    // Button to remove the toolbar.
    buttonRemove = $("<button/>").
      attr({"class": "symbolic-button", "title": AE.Translate.get("Remove this toolbar")}).
      append($("<img/>").attr("src", "../images/icon_close.png")).
      on("click", function() {
        toolbar.remove();
      });
    // Button to edit a toolbar's name.
    buttonEdit = $("<button/>").
      attr({"class": "symbolic-button", "title": AE.Translate.get("Edit the toolbar name")}).
      append($("<img/>").attr("src", "../images/icon_edit.png")).
      on("click", function() {
        !statusEditName ? toolbar.editName() : toolbar.finishEditName();
        statusEditName = !statusEditName;
      });
    buttonWrapper = $("<span/>").
      attr({"class": "right"}).
      append(buttonEdit, buttonRemove);
    header.append(title, titleEdit, buttonWrapper, $("<div/>").attr({"class": "clear"}) );

    //Content
    content = $("<ul/>").
      attr({"class": "toolbar content"});
    content.get(0).toolbar = toolbar; // reference

    // Add to toolbars list.
    $("#custom-toolbars").append(header, content);

    // Only after the content has been added to the DOM, we can make it sortable.
    content.sortable({
      connectWith: ".toolbar",
      revert: true,
      // Refresh the toolbar buttons array:
      change: function(event, ui) { update() },
      stop: function(event, ui) { update() }, // Also triggered when items on droppable.
      scroll: false,
      appendTo: document.body
      // Check that the dragged element is allowed to be dropped.
      /*beforeStop: function(event, ui) {
        // Check that it is no duplicate (except separator). When SketchUp loads toolbars, duplicates show a wrong icon.
        var id = Number(ui.item.attr("name"));
        if ( $.inArray(id, toolbar.buttons) >= 0 && id !== 0) { ui.item.remove(); }
      },*/
    });
  };

  /**
   * Refreshes the list of buttons of this toolbar.
   * @private
   */
  var update = function() {
    toolbar.buttons = content.find(".toolbar-button").map(function() {
      return Number(this.getAttribute("name"))
    }).get();
  };

  /**
   * Select the panel of this toolbar.
   * @public
   */
  toolbar.activate = function() {
    $("#custom-toolbars").parent().scrollTop(header.offset().top);
  };

  /**
   * Edit mode of this toolbar's name.
   */
  toolbar.editName = function() {
    titleEdit.show().focus();
    title.hide();
    /* Set caret to end */
    var e = titleEdit.get(0);
    var pos = e.value.length;
    /* DOM 3.0 */
    if('selectionStart' in e){
      e.selectionStart = pos;
      e.selectionEnd = pos;
    }
    /* Internet Explorer */
    else if (document.selection) {
      var r = e.createTextRange();
      r.moveEnd('textedit', -1);
      r.moveStart('character', pos);
      r.moveEnd('character', 0);
      r.select();
    }
  };

  /**
   * Finish edit mode of this toolbar's name.
   */
  toolbar.finishEditName = function() {
    if (statusEditName) {
      toolbar.name = titleEdit.hide().val();
      title.text(toolbar.name).show();
    }
  };

  /**
   * Add a new button (programmatically, not drag&drop)
   * @param {number} id
   * @public
   */
  toolbar.addButton = function(id) {
    var button = new ToolbarButton(id);
    if (!button) { return; }
    content.append(button.jQueryElement);
    toolbar.buttons.push(button.id);
  };

  /**
   * Remove this toolbar completely.
   * @public
   */
  toolbar.remove = function() {
    header.remove();
    content.remove();
    toolbars.splice($.inArray(toolbar, toolbars), 1);
    AE.Bridge.callRuby("delete_toolbar", toolbar.id)
    delete toolbar;
  };

  initialize();
  toolbars.push(toolbar);
}; // end @class Toolbar


/**
 * @class ToolbarButton
 */
var ToolbarButton = function(id, hash) {

  /** @private */
  var button = this;
  // Note: Double-checking because if hash is undefined, IE sees it as [object Object] instead of [object Undefined].
  if (hash && Object.prototype.toString.call(hash) === '[object Object]') {
    button.id = id;
    var entry = hash;
  } else {
    var entry = $.grep(Index, function(item){
      return item.id == id;
    })[0];
    if (!entry) { return; }
    button.id = entry.id;
  };
  if (!entry || typeof entry.id !== "number") { return; }

  /**
   * @constructor
   */
  var initialize = function() {
    button.jQueryElement = $("<li/>").
      attr({"id": entry.id, "name": entry.id, "title": entry.name, "class": "toolbar-button"}).
      data("entry", entry).
      append(
        $("<img/>").attr({"class": "icon", "src": entry.icon}),
        $("<span/>").attr({"class": "name"}).text(entry.name),
        $("<span/>").attr({"class": "category"}).text(entry.category),
        $("<span/>").attr({"class": "description"}).text(entry.description)
      );
  };

  initialize();
}; // end @class ToolbarButton


/**
 * @class CustomButton
 */
var CustomButton = function(entry) {

  /** @private */
  var button = this;
  button.id = entry.id;
  if (!entry || !button.id) { return; }
  var image_element, name_element, category_element, description_element;

  /**
   * @constructor
   */
  var initialize = function() {
    button.jQueryElement = $("<li/>").
      attr({"id": entry.id, "name": entry.id, "title": entry.name, "class": "toolbar-button"}).
      append(
        $("<div/>").attr("class", "controls").append(
          // Delete button
          $("<div/>").append(
            $("<button/>").attr("title", AE.Translate.get("Delete")).addClass("symbolic-button").on("click", function() {
              var index = 0; while (availableButtons[index] && availableButtons[index] != button) { index++; }
              availableButtons.splice(index, 1);
              button.jQueryElement.remove();
              AE.Bridge.callRuby("delete_custom_button", entry.id);
            }).append(
              $("<img/>").attr({"src": "../images/icon_close.png"})
            )
          ),
          // Edit button
          $("<div/>").append(
            $("<button/>").attr("title", AE.Translate.get("Edit")).addClass("symbolic-button").on("click", function() {
              DialogEditCustomButton.show(function(entry) {
                // Update the custom button.
                image_element.attr("src", entry.icon);
                name_element.text(entry.name);
                category_element.text(entry.category);
                description_element.text(entry.description);
              }, entry);
            }).append(
              $("<img/>").attr({"src": "../images/icon_edit.png"})
            )
          )
        ),
        image_element = $("<img/>").attr({"class": "icon", "src": entry.icon}),
        name_element = $("<span/>").attr({"class": "name"}).text(entry.name),
        category_element = $("<span/>").attr({"class": "category"}).text(entry.category),
        description_element = $("<span/>").attr({"class": "description"}).text(entry.description)
      );
  };


  initialize();
}; // end @class CustomButton


/**
 * @class DialogEditCustomButton
 * @singleton
 */
var DialogEditCustomButton = function() {

  /** @private */
  var element, form, entry, onDialogSubmit;

  /**
   * @constructor
   */
  var initialize = function() {
    element = $("#dialog-edit-custom-button");
    form = {
      name: $("#edit-custom-button-name"),
      category: $("#edit-custom-button-category"),
      description: $("#edit-custom-button-description"),
      icon: $("#edit-custom-button-icon"),
      icon_select: $("#edit-custom-button-icon-select"),
      icon_image: $("#edit-custom-button-image"),
      code: $("#edit-custom-button-code"),
      cancel: $("#button-edit-custom-cancel"),
      save: $("#button-edit-custom-save")
    };
    // The HTMLInputElement[type=file] does not give access to the absolute file
    // path. Because of this, we simulate it with a text input and a button using 
    // the SketchUp API method.
    form.icon_select.on("click", function() {
      AE.Bridge.callRuby("openpanel", form.icon.val(), function(filename){
        if (!filename) { return; }
        form.icon.val(filename);
        form.icon_image.attr("src", filename);
      });
    });
    form.icon.on("click", function() {
      form.icon_select.trigger("click");
    });
    // Cancel button
    form.cancel.on("click", function() {
      element.hide();
    });
    // Save button
    form.save.on("click", function() {
      if (entry) {
        entry.name = form.name.val();
        if (!entry.id) { entry.id = hashCode(entry.name + entry.description + entry.icon); }
        entry.category = form.category.val();
        entry.description = form.description.val();
        entry.icon = form.icon.val();
        entry.code = form.code.val();
        // Save entry to SketchUp
        AE.Bridge.callRuby("save_custom_button", entry.id, entry.code, 
          {name: entry.name, category: entry.category, description: entry.description, icon: entry.icon},
          function(success) {
            if (success) {
              if (typeof onDialogSubmit === "function") {
                onDialogSubmit(entry);
              }
              element.hide();
            } else {
              // Show warning about error
              form.code.css({border: "3px red solid"});
              setTimeout(function(){ form.code.css({border: ""}); }, 500);
            }
        });
      }
    });
    element.hide();
  };


  /**
   * Shows the dialog for editing a custom button.
   * @param {function(object)} callback - Called after editing an entry.
   * @param {object} old_entry
   */
  this.show = function(callback, old_entry) {
    onDialogSubmit = callback;
    // Create a new entry if no existing entry given.
    if (!old_entry) { entry = {}; }
    else { entry = old_entry; }
    // Fill in the existing data.
    form.name.val(entry.name); //.attr("disabled", !!entry.id);
    form.category.val(entry.category);
    form.description.val(entry.description);
    form.icon.val(entry.icon);
    form.icon_image.attr("src", entry.icon);
    form.code.val(entry.code);
    // Show the dialog.
    element.show();
  };


  /**
   * Hash function to get short and reproducible identifiers.
   * @param {object} object
   */
  function adler32(s, l) {
    var s = String(s),
      h = 0,
      m = 99999,
      c;
    for (var i = 0; i < s.length; i++) {
      c = s.charCodeAt(i);
      h = (128 * h + c) % m;
    }
    return h;
  }
  var hashCode = adler32;


  initialize();
}; // end @class DialogEditCustomButton


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


window.onerror = function(message, url, lineNumber){
  AE.Bridge.error(message+" \n"+url+"\n"+lineNumber);
  return true;
}
