/**
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

/**
 * DependentOptionMenus class client-side definition encapsulates option menus
 * who values are dynamically filled in as selections are made from previous
 * menus.  Also includes messaging for SKU availability and/or price.
 *
 * @param {array} aOptionTypes array of MLOptionType objects
 * @param {array} aOptionSkus array of MLOptionsSku objects
 * @param {boolean} bDoMsgAvailNoSku no sku messaging flag
 * @param {boolean} bDoMsgAvailInStock in-stock messaging flag
 * @param {boolean} bDoMsgAvailBackOrdered back-ordered messaging flag
 * @param {boolean} bDoMsgAvailOutOfStock out-of-stock messaging flag
 * @param {boolean} bDoMsgPrice price messaging flag
 * @param {boolean} bDoMsgAvailInMenu availability in menu or outside flag
 * @param {boolean} bDoMsgPriceInMenu price in menu or outside flag
 * @param {String} sMessageNoSku no sku message
 * @param {String} sMessageInStock in-stock message
 * @param {String} sMessageBackOrdered back-ordered message (includes {0} date replacement token)
 * @param {String} sMessageOutOfStock out-of-stock message
 * @param {String} sOptionsSeparator separator character for intra-option value/messaging/price
 */
function DependentOptionMenus(iProductPk,
                              aOptionTypes,
                              aOptionSkus,
                              bDoMsgAvailNoSku,
                              bDoMsgAvailInStock,
                              bDoMsgAvailBackOrdered,
                              bDoMsgAvailOutOfStock,
                              bDoMsgPrice,
                              bDoMsgAvailInMenu,
                              bDoMsgPriceInMenu,
                              sMessageNoSku,
                              sMessageInStock,
                              sMessageBackOrdered,
                              sMessageOutOfStock,
                              sOptionsSeparator) {

  this.iProductPk             = iProductPk;
  this.aOptionTypes           = aOptionTypes;
  this.aOptionSkus            = aOptionSkus;
  this.bDoMsgAvailNoSku       = bDoMsgAvailNoSku;
  this.bDoMsgAvailInStock     = bDoMsgAvailInStock;
  this.bDoMsgAvailBackOrdered = bDoMsgAvailBackOrdered;
  this.bDoMsgAvailOutOfStock  = bDoMsgAvailOutOfStock;
  this.bDoMsgPrice            = bDoMsgPrice;
  this.bDoMsgAvailInMenu      = bDoMsgAvailInMenu;
  this.bDoMsgPriceInMenu      = bDoMsgPriceInMenu;
  this.sMessageNoSku          = sMessageNoSku;
  this.sMessageInStock        = sMessageInStock;
  this.sMessageBackOrdered    = sMessageBackOrdered;
  this.sMessageOutOfStock     = sMessageOutOfStock;
  this.sOptionsSeparator      = sOptionsSeparator;

  // Maintains option type and option value requested
  // to sync menus with (ie. from swatch click).
  this.requestChangeOptionTypePk = -1;
  this.requestChangeOptionPk = -1;

  /**
   * Returns the option type in our array of option types.
   * @param {int} iOptionTypePk option type PK
   * @return {object} OptionType object
   */
  DependentOptionMenus.prototype.getOptionType = function(iOptionTypePk) {
    for (var i = 0; i < this.aOptionTypes.length; i++) {
      if (this.aOptionTypes[i].iPk == iOptionTypePk) {
        return this.aOptionTypes[i];
      }
    }

    // Not found.
    return null;
  }

  /**
   * Returns the index of the option type in our array
   * of option types.
   * @param {int} iOptionTypePk option type PK
   * @return {int} index >= 0 if found, else -1
   */
  DependentOptionMenus.prototype.getOptionTypeIndex = function(iOptionTypePk) {
    for (var i = 0; i < this.aOptionTypes.length; i++) {
      if (this.aOptionTypes[i].iPk == iOptionTypePk) {
        return i;
      }
    }

    // Not found.
    return -1;
  }

  /**
   * Returns the select menu object corresponding to an option type.
   * @param {int} iOptionTypePk option type PK
   * @return {object} DOM select object
   */
  DependentOptionMenus.prototype.getSelectObj = function(iOptionTypePk) {
    return document.getElementById('options_' + this.iProductPk + '_' + iOptionTypePk);
  }

  /**
   * Return one sku which matches the options
   * @param {array} aOptions array of Options objects to match against sku options
   * @return {object} OptionsSku object if found, else null
   */
  DependentOptionMenus.prototype.findSku = function(aOptions) {

    // Special case to return the single SKU when no options
    // passed in and we only have 1 SKU.
    if (aOptions.length == 0 && this.aOptionSkus.length == 1) {
      return this.aOptionSkus[0];
    }

    // Go through each of the SKUs
    // TODO: Use Binary search.
    for (var i = 0; i < this.aOptionSkus.length; i++) {

      // Assume matches until proven otherwise.
      var skuMatched = true;

      // Compare the sku options with the array of options
      // passed in.
      var skuOptions = this.aOptionSkus[i].aOptions;

      // All of the options must be specified in the input
      // aOptions parameter.
      if (skuOptions.length != aOptions.length) {
        continue;
      }

      for (var j = 0; j < skuOptions.length; j++) {
        if (skuOptions[j].iPk != aOptions[j].iPk) {
          skuMatched = false;
          break;
        }
      }

      // If sku options matched, return it.
      if (skuMatched) {
        return this.aOptionSkus[i];
      }
    }

    return null;
  }

  /**
   * Get the availability messaging (in-stock, back-ordered, out of stock)
   * The constructed message is based on the configuration settings
   * controlling which types of availability messages to present.
   * @param {object} oSku OptionsSku sku object
   * @return {String} availability messaging text
   */
  DependentOptionMenus.prototype.getAvailabilityMessage = function(oSku) {
    var sMessage = '';

    if (!oSku) {
      if (this.bDoMsgAvailNoSku) {
        sMessage = this.sMessageNoSku;
      }
    }
    else if (oSku.bInStock) {
      if (this.bDoMsgAvailInStock) {
        sMessage = this.sMessageInStock;
      }
    }
    else if (oSku.sReorderDate.length > 0) {
      if (this.bDoMsgAvailBackOrdered) {
        var sBackOrder = this.sMessageBackOrdered;
        sBackOrder = sBackOrder.replace('{0}', oSku.sReorderDate);
        sMessage = sBackOrder;
      }
    }
    else {
      if (this.bDoMsgAvailOutOfStock) {
        sMessage = this.sMessageOutOfStock;
      }
    }

    return sMessage;
  }

  /**
   * Get the availability messaging and option price messaging for a
   * sku which matches the chosen options.
   * @param {array} aChosenOptions array of Option objects
   * @bForMenu {boolean} bForMenu true if want messaging for inside a menu, false for outside.
   * @return {String} messaging text
   */
  DependentOptionMenus.prototype.getMessaging = function(aChosenOptions, bForMenu) {
    var sMessage = '';

    // Find the skus that match those options.
    var oFoundSku = this.findSku(aChosenOptions);

    if ((bForMenu && this.bDoMsgAvailInMenu) ||
        (!bForMenu && !this.bDoMsgAvailInMenu)) {

      sMessage = this.getAvailabilityMessage(oFoundSku);
    }

    if ((bForMenu && this.bDoMsgPriceInMenu) ||
        (!bForMenu && !this.bDoMsgPriceInMenu)) {

      if (this.bDoMsgPrice && oFoundSku) {
        if (sMessage.length > 0) {
          sMessage += this.sOptionsSeparator;
        }
        sMessage += oFoundSku.sPrice;
      }
    }

    return sMessage;
  }

  /**
   * Build an array of Option objects representing the choices made in
   * select option menus.
   * @return {array} array of Option objects
   */
  DependentOptionMenus.prototype.buildChosenOptions = function() {
    var aChosenOptions = new Array();

    // Go through options that are selected and
    // build array of options that were chosen in those menus.
    //
    for (var i = 0; i < this.aOptionTypes.length; i++) {
      var oSelected = this.getSelectObj(this.aOptionTypes[i].iPk);

      // If <select> has a valid value, push it.
      if (oSelected.value > 0) {
        aChosenOptions.push(this.aOptionTypes[i].getOptionByPk(oSelected.value));
      }
      // Else, no more valid selections, done.
      else {
        break;
      }
    }

    return aChosenOptions;
  }

  /**
   * Clear the dynamic entries in a select option menu.  This means
   * all values except the static text option in the first index.
   * @param {object} oSelect DOM select object
   */
  DependentOptionMenus.prototype.clearOptionMenu = function(oSelect) {
    // Delete current options except for the first static item
    // Note: Delete in reverse order.
    for (var i = oSelect.options.length - 1; i > 0; i--) {
      oSelect.options[i] = null;
    }

    // This hack is needed to convince IE to recalc the
    // width of the select element after we remove all the
    // items.
    // TODO: Is there an easier elem.recalc() or something method?
    var oOptionElem = document.createElement("OPTION");
    oOptionElem.value = 0;
    oOptionElem.text = "";
    
    oSelect.options[oSelect.length] = oOptionElem;
    oSelect.options[1] = null;

    // Select the first static item.
    oSelect.value = 0;
  }

  /**
   * Dynamically load a select option menu with values corresponding
   * to each option value in the option type for that menu.  If the
   * menu is the final select menu, messaging can be added to the
   * menu value text.
   * @param {object} oSelect DOM select object
   * @param {object} oOptionType OptionType object
   * @param {int} iOptionTypeIndex index of the option menu being loaded (zero-based)
   */
  DependentOptionMenus.prototype.loadOptionMenu = function(oSelect, oOptionType, iOptionTypeIndex) {
    // Save off current menu value so we can restore.
    var sOrigValue = oSelect.value;

    this.clearOptionMenu(oSelect);

    // Get the options values for this option type to load into menu.
    var aOptions = oOptionType.aOptions;

    // Assume not loading last menu, so no messaging.
    var bAddMessaging = false;
    var aChosenOptions = null;

    // If this is the last menu and we're showing messaging
    // in the menu, build up the array of options chosen in
    // the preceding menus as we will need them to add
    // messaging in the last menu's option value strings.
    //
    if (iOptionTypeIndex == this.aOptionTypes.length - 1 &&
         (this.bDoMsgAvailInMenu || this.bDoMsgPriceInMenu)) {

      bAddMessaging = true;

      // Build array of current <select> chosen values.
      aChosenOptions = this.buildChosenOptions();
    }

		//added for making menus dependent
		//including the bFound boolean
		var aOptionsNew =new Array();
		var j=0;
		var bFound=0;
    // Add option menu for each option.
    for (var i = 0; i < aOptions.length; i++) {

      bFound=0;		
      var iNewOptionPk = aOptions[i].iPk;
      var sNewOptionText = aOptions[i].sName;

      if (bAddMessaging) {
        // Temporarily push on this option value as chosen just
        // to get messaging.
        aChosenOptions.push(oOptionType.getOptionByPk(iNewOptionPk));

        // Add messaging text for these options.
        var sMsg = this.getMessaging(aChosenOptions, true);

        if (sMsg.length > 0 && sMsg != 'Unavailable' && sMsg != 'Out of Stock') {
          //sNewOptionText += this.sOptionsSeparator;
          //sNewOptionText += sMsg;
					//Added for dependent menus
          aOptionsNew[j]=aOptions[i];
          j++;
          bFound=1;
        }

        // Remove temporary option choice.
        aChosenOptions.pop();
      }

			//Altered to make the menus dependent
			if(iOptionTypeIndex==0 || bFound==1){
      var oOptionElem = document.createElement("OPTION");

      oOptionElem.value = iNewOptionPk;
      oOptionElem.text = sNewOptionText;

      oSelect.options[oSelect.length] = oOptionElem;
      // Restore selected item.
      oSelect.value = sOrigValue;
    }
  	}
		//Added for making menus dependent
    aOptions=aOptionsNew;
  }

  /**
   * Update state of menu(s) after a change to an option menu.  This may
   * include clearing or loading subsequent option menus.   *
   * @param {object} oSelect DOM select object
   * @param {int} iOptionTypePk pk of the option type of the option menu
   * @param {int} iOptionTypeIndex index of the option menu (zero-based)
   */
  DependentOptionMenus.prototype.optionChanged = function(oSelect, iOptionTypePk, iOptionTypeIndex) {

    // Updates the hidden optionTypeValues_${optionTypePk} field.
    optionTypeValues(oSelect);

    // Get value that select was just changed to.
    var iPrevMenuValue = oSelect.value;

    // Assume all menus have values.
    var bAllOptionsSet = iPrevMenuValue > 0;

    // Go through subsequent menus.
    for( var i = iOptionTypeIndex + 1; i < this.aOptionTypes.length; i++ ) {

      // Get the next option type.
      var oOptionType = this.aOptionTypes[i];

      // Get corresponding <select> element.
      var oNextSelect = this.getSelectObj(oOptionType.iPk);

      // If the menu preceding this select is in its default value,
      // clear this menu.
      if (iPrevMenuValue == 0) {
        this.clearOptionMenu(oNextSelect);

        // Try and sync swatches with this change.
        this.syncSwatches(oOptionType.iPk, oNextSelect.value);

        // Updates the hidden optionTypeValues_${optionTypePk} field.
        optionTypeValues(oNextSelect);
      }
      // Else, a valid menu selection was made.
      else {
        // Only load the menu if not already loaded or if it is
        // the last menu and we're showing messaging in the menu.
        if (oNextSelect.options.length == 1 ||
            (i == this.aOptionTypes.length - 1 && (this.bDoMsgAvailInMenu || this.bDoMsgPriceInMenu))) {

          this.loadOptionMenu(oNextSelect, oOptionType, i);

          // If this option type was requested to be set to a specific
          // value (ie. from swatch click), set it now after the load.
          //
          if (oOptionType.iPk == this.requestChangeOptionTypePk) {

            oNextSelect.value = this.requestChangeOptionPk;

            // Clear the set request.
            this.requestChangeOptionTypePk = -1;
            this.requestChangeOptionPk = -1;
          }

          // Try and sync swatches with this change.
          this.syncSwatches(oOptionType.iPk, oNextSelect.value);

          // Updates the hidden optionTypeValues_${optionTypePk} field.
          optionTypeValues(oNextSelect);
        }
      }

      if (oNextSelect.value < 1) {
        bAllOptionsSet = false;
      }

      iPrevMenuValue = oNextSelect.value;
    }

    // If not doing availability or messaging inside the menu,
    // update messaging text.
    if (!this.bDoMsgAvailInMenu || !this.bDoMsgPriceInMenu) {

      var oMessagingText = document.getElementById('messagingText_' + this.iProductPk);

      if (oMessagingText) {
        if (bAllOptionsSet) {
          // Return messaging for these options.
          var sMessaging = this.getMessaging(this.buildChosenOptions(), false);
          oMessagingText.innerHTML = sMessaging;
          oMessagingText.style.display="inline";
        }
        else {
          oMessagingText.innerHTML = '';
          oMessagingText.style.display="none";
        }
      }
    }
  }

  /**
   * Try and sync the swatches tile with a change to our menus.  If the option type PK
   * corresponds to the swatch object, it will be found and sent the synchronize method
   * passing in the new value that the swatch should adjust to.
   *
   * @param {int} iOptionTypePk pk of the option type of the option menu
   * @param {String} sSelectValue new value of the option menu
   */
  DependentOptionMenus.prototype.syncSwatches = function(iOptionTypePk, sSelectValue) {
    // Try and find swatch object corresponding to this
    // option type by matching goSwatches_ + product pk + option type pk.
    //
    try {
      var oSwatches = eval('goSwatches_' + this.iProductPk + '_' + iOptionTypePk);

      oSwatches.synchronize(sSelectValue);
    }
    catch (e) {
    }
  }

  /**
   * Event handler for select option menu changed value.  Updates
   * state of menu(s) and optionally synchronizes related swatch object.
   * @param {object} oSelect DOM select object
   * @param {int} iOptionTypePk pk of the option type of the option menu
   * @param {int} iOptionTypeIndex index of the option menu (zero-based)
   */
  DependentOptionMenus.prototype.selectMenuChanged = function(oSelect, iOptionTypePk, iOptionTypeIndex) {

    // Perform update after a menu changed.
    this.optionChanged(oSelect, iOptionTypePk, iOptionTypeIndex);

    // Try and sync swatches with this change.
    this.syncSwatches(iOptionTypePk, oSelect.value);
  }

  /**
   * Try and sychronize our menu with an option value
   * selected via another UI element (ie. swatch clicked).  If
   * the menu cannot be synchronized at this time because prior
   * dependent menus have not been loaded, we record the request
   * and apply it when the menu is loaded.
   * @param {int} iOptionTypePk pk of the option type to sync
   * @param {int} iOptionPk pk of the option value fo select in the option type menu
   */
  DependentOptionMenus.prototype.synchronize = function(iOptionTypePk, iOptionPk) {
    // Get corresponding <select> menu object.
    var oSelect = this.getSelectObj(iOptionTypePk);

    if (oSelect != null) {

      // If the select has been loaded, we can set its new value.
      if (oSelect.options.length > 1) {

        if (oSelect.value != iOptionPk) {
          // Update value of select menu.
          oSelect.value = iOptionPk;

          // Notify of option change similar to when user clicks menu.
          this.optionChanged(oSelect, iOptionTypePk, this.getOptionTypeIndex(iOptionTypePk));
        }
      }
      // Else, save off request and set when possible.
      else {
        this.requestChangeOptionTypePk = iOptionTypePk;
        this.requestChangeOptionPk = iOptionPk;
      }
    }
  }

  //
  // INITIALIZATION
  //

  // If there are any options, initialize the first menu.
  //
  if (this.aOptionTypes.length > 0) {

    // Get the first option type.
    var oOptionType = this.aOptionTypes[0];

    // Get the select element corresponding to the first option type.
    var oSelect = this.getSelectObj(oOptionType.iPk);

    this.loadOptionMenu(oSelect, oOptionType, 0);
  }
  // Else, if showing availability status, update text.
  else {
    var oMessagingText = document.getElementById('messagingText_' + this.iProductPk);

    if (oMessagingText) {
      oMessagingText.innerHTML = this.getAvailabilityMessage(this.findSku([]));
      oMessagingText.style.display="inline";
    }
  }
}