/**
 * @license Licensed under the Apache License, Version 2.0 (the "License"):
 *          http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * @fileoverview Instances input field.
 */
'use strict';

goog.provide('Blockly.FieldInstance');

goog.require('Blockly.FieldDropdown');
goog.require('Blockly.Instances');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('goog.string');


/**
 * Class for a specific type of instances' dropdown field.
 * @param {?string} instanceName The default name for the instance. If null,
 *     a unique instance name will be generated.
 * @param {!string} instanceType The type of instances for the dropdown.
 * @param {boolean} uniqueName
 * @param {boolean=} opt_lockNew Indicates a special case in which this
 *     dropdown can only rename the current name and each new block will always
 *     have a unique name.
 * @param {boolean=} opt_lockRename
 * @param {Function=} opt_validator A function that is executed when a new
 *     option is selected.  Its sole argument is the new option value.
 * @extends {Blockly.FieldDropdown}
 * @constructor
 */
Blockly.FieldInstance = function(
    instanceType, instanceName, uniqueName, opt_lockNew, opt_lockRename,
    opt_editDropdownData, opt_validator) {
  Blockly.FieldInstance.superClass_.constructor.call(this,
      this.dropdownCreate, opt_validator);

  this.instanceType_ = instanceType;
  this.setValue(instanceName);
  this.uniqueName_ = (uniqueName === true);
  this.lockNew_ = (opt_lockNew === true);
  this.lockRename_ = (opt_lockRename === true);
  this.editDropdownData = (opt_editDropdownData instanceof Function) ?
      opt_editDropdownData : null;
};
goog.inherits(Blockly.FieldInstance, Blockly.FieldDropdown);

/**
 * Sets a new change handler for instance field.
 * @param {Function} handler New change handler, or null.
 */
Blockly.FieldInstance.prototype.setValidator = function(handler) {
  var wrappedHandler;
  if (handler) {
    // Wrap the user's change handler together with the instance rename handler.
    wrappedHandler = function(value) {
      var v1 = handler.call(this, value);
      if (v1 === null) {
        var v2 = v1;
      } else {
        if (v1 === undefined) {
          v1 = value;
        }
        var v2 = Blockly.FieldInstance.dropdownChange.call(this, v1);
        if (v2 === undefined) {
          v2 = v1;
        }
      }
      return v2 === value ? undefined : v2;
    };
  } else {
    wrappedHandler = Blockly.FieldInstance.dropdownChange;
  }
  Blockly.FieldInstance.superClass_.setValidator.call(this, wrappedHandler);
};

/**
 * Install this dropdown on a block.
 */
Blockly.FieldInstance.prototype.init = function() {
  // Nothing to do if the dropdown has already been initialised once.
  if (this.fieldGroup_) return;

  Blockly.FieldInstance.superClass_.init.call(this);

  var workspace = this.sourceBlock_.isInFlyout ?
      this.sourceBlock_.workspace.targetWorkspace : this.sourceBlock_.workspace;

  if (!this.getValue()) {
    // Instances without names get uniquely named for this workspace.
    this.setValue(Blockly.Instances.generateUniqueName(workspace));
  } else {
      if (this.uniqueName_) {
        // Ensure the given name is unique in the workspace, but not in flyout
        if (!this.sourceBlock_.isInFlyout) {
          this.setValue(
              Blockly.Instances.convertToUniqueNameBlock(
                  this.getValue(), this.sourceBlock_));
        }
      } else {
        // Pick an existing name from the workspace if any exists
        var existingName =
            Blockly.Instances.getAnyInstanceOf(this.instanceType_ , workspace);
        if (existingName) this.setValue(existingName);
      }
  }
};

/**
 * Get the instance's name (use a variableDB to convert into a real name).
 * Unlike a regular dropdown, instances are literal and have no neutral value.
 * @return {string} Current text.
 */
Blockly.FieldInstance.prototype.getValue = function() {
  return this.getText();
};

Blockly.FieldInstance.prototype.getInstanceTypeValue = function(instanceType) {
  return (instanceType === this.instanceType_) ? this.getText() : undefined;
};

/**
 * Set the instance name.
 * @param {string} newValue New text.
 */
Blockly.FieldInstance.prototype.setValue = function(newValue) {
  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
    Blockly.Events.fire(new Blockly.Events.Change(
        this.sourceBlock_, 'field', this.name, this.value_, newValue));
  }
  this.value_ = newValue;
  this.setText(newValue);
};

/**
 * Return a sorted list of instance names for instance dropdown menus.
 * If editDropdownData has been defined it passes this list to the 
 * @return {!Array.<string>} Array of instance names.
 */
Blockly.FieldInstance.prototype.dropdownCreate = function() {
  if (this.sourceBlock_ && this.sourceBlock_.workspace) {
    var instanceList =
        Blockly.Instances.allInstancesOf(this.instanceType_,
                                         this.sourceBlock_.workspace);
  } else {
    var instanceList = [];
  }
  // Ensure that the currently selected instance is an option.
  var name = this.getText();
  if (name && instanceList.indexOf(name) == -1) {
    instanceList.push(name);
  }
  instanceList.sort(goog.string.caseInsensitiveCompare);
  if (!this.lockRename_) {
    instanceList.push(Blockly.Msg.RENAME_INSTANCE);
  }
  if (!this.lockNew_) {
    instanceList.push(Blockly.Msg.NEW_INSTANCE);
  }

  // If configured, pass data to external handler for additional processing
  var options = this.editDropdownData ? this.editDropdownData(options) : [];

  // Instances are not language specific, so use for display and internal name
  for (var i = 0; i < instanceList.length; i++) {
    options[i] = [instanceList[i], instanceList[i]];
  }

  return options;
};

/**
 * Event handler for a change in instance name.
 * Special case the 'New instance...' and 'Rename instance...' options.
 * In both of these special cases, prompt the user for a new name.
 * @param {string} text The selected dropdown menu option.
 * @return {null|undefined|string} An acceptable new instance name, or null if
 *     change is to be either aborted (cancel button) or has been already
 *     handled (rename), or undefined if an existing instance was chosen.
 * @this {!Blockly.FieldInstance}
 */
Blockly.FieldInstance.dropdownChange = function(text) {
  function promptName(promptText, defaultText, callback) {
    Blockly.hideChaff();
    var newVar = Blockly.prompt(promptText, defaultText, function(newVar) {
      // Merge runs of whitespace.  Strip leading and trailing whitespace.
      // Beyond this, all names are legal.
      if (newVar) {
        newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
        if (newVar == Blockly.Msg.RENAME_INSTANCE ||
            newVar == Blockly.Msg.NEW_INSTANCE) {
          newVar = null;  // Ok, not ALL names are legal...
        }
      }
      callback(newVar);
    });
  }
  var workspace = this.sourceBlock_.workspace;
  if (text == Blockly.Msg.RENAME_INSTANCE) {
    var oldInstance = this.getText();
    var thisFieldInstance = this;
    var callbackRename = function(text) {
      if (text) {
        Blockly.Instances.renameInstance(
            oldInstance, text, thisFieldInstance.instanceType_, workspace);
      }
    };
    promptName(Blockly.Msg.RENAME_INSTANCE_TITLE.replace('%1', oldInstance),
               oldInstance, callbackRename);
    return null;
  } else if (text == Blockly.Msg.NEW_INSTANCE) {
    //TODO: this return needs to be replaced with an asynchronous callback
    return Blockly.Instances.generateUniqueName(workspace);
  }
  return undefined;
};
