﻿// Given a number or string, converts it to a float, rounds it to 2 decimal
// places and formats it as an american price string.
window.formatPrice = function (price)
{
   var priceNumber = new Number(price);
   var priceString = priceNumber.toFixed(2);
   return "$" + priceString.addCommas() + " USD";
};

// OrderForm.js depends on the following method.
// Pretty prints numbers with commas separating thousands places.
String.prototype.addCommas = function ()
{
   var nStr = this;
   nStr += '';
   x = nStr.split('.');
   x1 = x[0];
   x2 = x.length > 1 ? '.' + x[1] : '';
   var rgx = /(\d+)(\d{3})/;
   while (rgx.test(x1))
   {
      x1 = x1.replace(rgx, '$1' + ',' + '$2');
   }
   return x1 + x2;
};

var ProductList = Class.create(
{
   initialize: function (clientID)
   {
      this.clientID = clientID;
      this.container = $(this.clientID + "_tblProductList");
      if (this.container == null)
      {
         alert("I couldn't find the products list. Fix me.");
         return;
      }
      this.products = {};
      this.discounts = {};
      this.tierProducts = {};

      this.selectedProducts = [];
      this.selectedDiscounts = [];
   },

   Verify: function ()
   {
      return this.selectedProducts.length > 0 || this.selectedDiscounts.length > 0;
   },

   AddProduct: function (id, name, price)
   {
      this.products[id + ""] = new Product(id, name, price);
   },

   AddDiscount: function (id, name, price)
   {
      this.discounts[id + ""] = new Product(id, name, price);
   },

   AddTierProduct: function (groupName, name)
   {
      this.tierProducts[groupName] = new TieredProduct(groupName, name);
   },

   AddTier: function (id, groupName, year, level, price)
   {
      var product = this.tierProducts[groupName];
      if (product == null)
         return;

      product.AddTier(new Tier(id, groupName, year, level, price));
   },

   OwnsTier: function (id, groupName, year, level, price)
   {
      var product = this.tierProducts[groupName];
      if (product == null)
         return;

      product.OwnsTier(new Tier(id, groupName, year, level, price));
   },

   Run: function ()
   {
      // Get all the elements with the classname 'product'
      var products = this.container.select(".product");

      // Look for an input tag in each table and parse it.
      for (var productIndex = 0; productIndex < products.length; productIndex++)
      {
         var productTable = products[productIndex];
         var rowID = productTable.select(".addButton input")[0].value;

         // R for regular products. T for tiered products.
         // The rowID will be something like "R:10" or "T:accessWizard" so
         // substring(2) fetches the part of the string after the colon.
         if (rowID.charAt(0) == "R")
         {
            this.SetupProduct(productTable, rowID.substring(2));
         }
         else if (rowID.charAt(0) == "T")
         {
            this.SetupTieredProduct(productTable, rowID.substring(2));
         }
      }

      var discounts = this.container.select(".discount");

      for (var discountIndex = 0; discountIndex < discounts.length; discountIndex++)
      {
         var discountTable = discounts[discountIndex];
         var rowID = discountTable.select(".addButton input")[0].value;

         this.SetupDiscount(discountTable, rowID.substring(2));
      }
   },

   // id is expected to be in the form "R:10" or "T:accessWizard:11" where the
   // number after the second colon is the tier id. This will simulate the user
   // click on a product to purchase it.
   PreselectProduct: function (id)
   {
      var idParts = id.split(":");
      var productTable = null;
      var productTables;
      var productTablesCount;
      var productID;
      var searchID;

      // If the id isn't right, don't bother.
      if (idParts.length != 2 && idParts.length != 3)
         return;

      searchID = idParts[0] + ":" + idParts[1];

      productTables = this.container.select(".product");
      productTablesCount = productTables.length;

      for (var i = 0; i < productTablesCount; ++i)
      {
         productID = productTables[i].select(".addButton input")[0].value;
         if (productID == searchID)
         {
            productTable = productTables[i];
            break;
         }
      }

      // Quit if we can find an appropriate product table.
      if (productTable == null)
         return;

      if (idParts.length == 2 && idParts[0] == "R")
      {
         // Preselect a regular product.
         var eventFunction = this.OnAddProduct(productTable, idParts[1]).bindAsEventListener(this);
         eventFunction(null);
      }
      else if (idParts.length == 3 && idParts[0] == "T")
      {
         // Preselect a tiered product.
         var product = this.tierProducts[idParts[1]];
         var tier = product.tiers[idParts[2]];

         // Simulate setting the dropdowns to the right values;
         var cboYear = productTable.select(".options select.year");
         var cboLevel = productTable.select(".options select.level");

         if (cboYear.length == 1)
            cboYear[0].value = tier.year;

         if (cboLevel.length == 1)
            cboLevel[0].value = tier.level;

         // Make sure the price has a chance to update
         var eventFunction = this.OnChangeTier(productTable, idParts[1]).bind(this);
         eventFunction(null);

         eventFunction = this.OnAddTieredProduct(productTable, idParts[1]).bindAsEventListener(this);
         eventFunction(null);
      }
   },

   SetupProduct: function (product, id)
   {
      var addLink = product.select("a")[0];
      if (addLink == null)
         return;

      Event.observe(addLink, "click", this.OnAddProduct(product, id).bindAsEventListener(this));
   },

   SetupTieredProduct: function (product, name)
   {
      var addLink = product.select("a")[0];
      if (addLink == null)
         return;

      Event.observe(addLink, "click", this.OnAddTieredProduct(product, name).bindAsEventListener(this));

      var dropdowns = product.select(".options select");
      for (var dropdownIndex = 0; dropdownIndex < dropdowns.length; dropdownIndex++)
      {
         Event.observe(dropdowns[dropdownIndex], "change", this.OnChangeTier(product, name).bindAsEventListener(this));
      }
   },

   SetupDiscount: function (discountTable, id)
   {
      var addLink = discountTable.select("a")[0];
      if (addLink == null)
         return;

      Event.observe(addLink, "click", this.OnAddDiscount(discountTable, id).bindAsEventListener(this));
   },

   OnAddProduct: function (productTable, id)
   {
      // The returned function is the actual event handler but by wrapping it
      // like this, we're able to sneak in some additional parameters to the
      // click event, like the table containing the button that was clicked
      // and the product id.
      return function (event)
      {
         if (event && event.stop)
            event.stop();

         var product = this.products[id];

         // Add another row for this product so users can buy two licenses
         var newProductTable = product.CreateTable();
         productTable.insert({ after: newProductTable });
         this.SetupProduct(newProductTable, id);

         // Mark this row purchased
         productTable.addClassName("purchased");

         // Add this to the selected products and notify about the price change
         this.selectedProducts.push(id);
         this.OnPriceChanged(product.price);

         // And allow this row to be removed now.
         this.MakeProductRemoveable(productTable, id, this.OnRemovePurchase);
      }
   },

   OnAddTieredProduct: function (productTable, name)
   {
      return function (event)
      {
         if (event && event.stop)
            event.stop();

         var product = this.tierProducts[name];

         // Add another row for this product so users can buy two licenses          
         var newProductTable = product.CreateTable();
         productTable.insert({ after: newProductTable });
         this.SetupTieredProduct(newProductTable, name);

         // Mark this row purchased
         productTable.addClassName("purchased");

         // Add this to the selected products and notify about the price change
         var tier = product.GetTierByProductTable(productTable);
         this.selectedProducts.push(tier.id);
         this.OnPriceChanged(tier.price);

         // And allow this row to be removed now.
         // To make finding this tier easier later, we need the product name
         // and the tier id.
         this.MakeProductRemoveable(productTable, [name, tier.id], this.OnRemoveTierPurchase);
      }
   },

   OnAddDiscount: function (discountTable, id)
   {
      // The returned function is the actual event handler but by wrapping it
      // like this, we're able to sneak in some additional parameters to the
      // click event, like the table containing the button that was clicked
      // and the product id.
      return function (event)
      {
         if (event && event.stop)
            event.stop();

         var discount = this.discounts[id];

         // Mark this row purchased
         discountTable.addClassName("purchased");

         // Add this to the selected products and notify about the price change
         this.selectedDiscounts.push(id);
         this.OnPriceChanged(discount.price);

         // And allow this row to be removed now.
         this.MakeProductRemoveable(discountTable, [id], this.OnRemoveDiscount);
      }
   },

   OnPriceChanged: function (amount)
   {
      // This is a slightly hacky way of allowing this object to communicate
      // with anyone who wants to list.
      // Check to see if a certain global function has been defined and call
      // it if it has.
      if (window.ProductList_PriceChanged)
         window.ProductList_PriceChanged(amount);
   },

   MakeProductRemoveable: function (productTable, identifier, removeRowCallback)
   {
      // Add button stops adding new rows
      var addLink = productTable.select("a")[0];
      Event.stopObserving(addLink, "click");

      // Add button image becomes remove image
      var removeImage = new Element("img", { "src": "images/button_remove.gif" });
      addLink.update(removeImage);

      // Add a check box to the left of the product name
      var check = new Element("img", { "src": "images/accept.png" });
      var nameCell = productTable.select(".name")[0];
      nameCell.insert({ top: check });

      // And don't let the user change the dropdowns
      var selects = productTable.select(".options select");
      var optionString = "&nbsp;";
      for (var selectIndex = 0; selectIndex < selects.length; selectIndex++)
      {
         optionString += selects[selectIndex].value + " ";
      }
      productTable.select(".name")[0].insert({ bottom: optionString });
      var options = productTable.select(".options")
      if (options.length > 0)
         options[0].update("");

      // Setup the remove button
      Event.observe(addLink, "click", removeRowCallback(productTable, identifier).bindAsEventListener(this));
   },

   OnRemovePurchase: function (productTable, id)
   {
      return function (event)
      {
         event.stop();

         productTable.remove();
         this.RemoveProductID(id);

         var product = this.products[id];
         this.OnPriceChanged(-product.price);
      }
   },

   // id needs to be an array with the tiered product name and the tier id,
   // in that order.
   OnRemoveTierPurchase: function (productTable, id)
   {
      return function (event)
      {
         event.stop();

         if (id.length != 2)
            return;

         var name = id[0];
         var tierID = id[1];

         productTable.remove();
         this.RemoveProductID(tierID);

         var product = this.tierProducts[name];
         var tier = product.tiers[tierID];
         this.OnPriceChanged(-tier.price);
      }
   },

   OnRemoveDiscount: function (discountTable, id)
   {
      return function (event)
      {
         event.stop();

         // Unmark this row purchased
         discountTable.removeClassName("purchased");

         var checkmark = discountTable.select(".name img")[0];
         checkmark.remove();

         // Each discount can only be added once, so we can get away with
         // using the without function here.
         this.selectedDiscounts = this.selectedDiscounts.without(id);

         // Remove button goes back to being add button
         var addLink = discountTable.select("a")[0];
         Event.stopObserving(addLink, "click");
         this.SetupDiscount(discountTable, id);

         // Add button image becomes remove image
         var addImage = new Element("img", { "src": "images/button_add.gif" });
         addLink.update(addImage);

         var discount = this.discounts[id];
         this.OnPriceChanged(-discount.price);
      }
   },

   // This method searches the list of selected products and removes the id
   // specified if it exists. This method is necessary because a) javascript
   // doesn't natively provide a method that removes elements of an array and
   // b) if the array contains the same id twice, prototype's 'without' 
   // function removes both and we only want to take out one at a time.
   RemoveProductID: function (id)
   {
      var timesFound = 0;
      var totalProductsCount = this.selectedProducts.length;

      // See how many times this id is in the array.
      for (var i = 0; i < totalProductsCount; ++i)
      {
         if (this.selectedProducts[i] == id)
            timesFound++;
      }

      // Removes all instances of that id.
      this.selectedProducts = this.selectedProducts.without(id);

      // Add that id back into the array, but add one less than there was before.
      timesFound--;

      for (var i = 0; i < timesFound; ++i)
      {
         this.selectedProducts.push(id);
      }
   },

   OnChangeTier: function (productTable, name)
   {
      return function (event)
      {
         var product = this.tierProducts[name];
         var priceCell = productTable.select(".price")[0];
         var tier = product.GetTierByProductTable(productTable);

         priceCell.update(formatPrice(tier.price));
      }
   }
});

var Product = Class.create({
   initialize: function (id, name, price)
   {
      this.id = id;
      this.name = name;
      this.price = price;
   },

   CreateTable: function ()
   {
      var productTable = new Element("table", { "class": "product" });
      var productBody = new Element("tbody");
      var productRow = new Element("tr");

      var nameCell = new Element("td", { "class": "name" });
      var optionsCell = new Element("td", { "class": "options" });
      var priceCell = new Element("td", { "class": "price" });
      var addCell = new Element("td", { "class": "addButton" });

      productTable.insert(productBody);
      productBody.insert(productRow);

      productRow.insert(nameCell);
      productRow.insert(optionsCell);
      productRow.insert(priceCell);
      productRow.insert(addCell);

      nameCell.update(this.name);
      optionsCell.update("&nbsp;");
      priceCell.update(formatPrice(this.price));

      var addLink = new Element("a", { "href": "#" });
      var addImage = new Element("img", { "src": "images/button_add.gif" });
      var hiddenID = new Element("input", { "type": "hidden", "value": "R:" + this.id });

      addLink.update(addImage);

      addCell.insert(addLink);
      addCell.insert(hiddenID);

      return productTable;
   }
});

var TieredProduct = Class.create({
   initialize: function (groupName, name)
   {
      this.groupName = groupName;
      this.name = name;
      this.tiers = {};
      this.distinctYears = [];
      this.distinctLevels = [];
      this.ownsTier = false;
      this.ownedTiers = [];
   },

   AddTier: function (tier)
   {
      this.tiers[tier.id + ""] = tier;

      // Add each tier's year and level to arrays if those arrays don't
      // already contain the values.
      if (this.distinctYears.indexOf(tier.year) == -1)
         this.distinctYears.push(tier.year);

      if (this.distinctLevels.indexOf(tier.level) == -1)
         this.distinctLevels.push(tier.level);
   },

   OwnsTier: function (tier)
   {
      this.ownsTier = true;
      this.ownedTiers.push(tier);
   },

   // Given a product table, this method will try to find the correct product
   // tier based on the values of the dropdowns and return it.
   GetTierByProductTable: function (productTable)
   {
      var cboYear = productTable.select(".options select.year");
      var cboLevel = productTable.select(".options select.level");
      var year;
      var level;

      if (cboYear.length > 0)
         year = cboYear[0].value;
      else
         year = "_";

      if (cboLevel.length > 0)
         level = cboLevel[0].value;
      else
         level = "_";

      return this.GetTierByYearAndLevel(year, level);
   },

   GetTierByYearAndLevel: function (year, level)
   {
      for (var tierName in this.tiers)
      {
         var tier = this.tiers[tierName];
         if (tier.year == year && tier.level == level)
            return tier;
      }

      return null;
   },

   CreateTable: function ()
   {
      var productTable = new Element("table", { "class": "product" });
      var productBody = new Element("tbody");
      var productRow = new Element("tr");

      var nameCell = new Element("td", { "class": "name" });
      var optionsCell = new Element("td", { "class": "options" });
      var priceCell = new Element("td", { "class": "price" });
      var addCell = new Element("td", { "class": "addButton" });

      var cboYears = new Element("select", { "class": "year" });
      var cboLevels = new Element("select", { "class": "level" });

      productTable.insert(productBody);
      productBody.insert(productRow);

      productRow.insert(nameCell);
      productRow.insert(optionsCell);
      productRow.insert(priceCell);
      productRow.insert(addCell);

      nameCell.update(this.name);

      this.distinctYears.sort();

      // If this product has no years, it'll have one value: "_"
      if (!(this.distinctYears.length == 1 && this.distinctYears[0] == "_"))
      {
         for (var i = 0; i < this.distinctYears.length; i++)
            cboYears.options[i] = new Option(this.distinctYears[i], this.distinctYears[i]);

         // Select the last item by default
         cboYears.options[cboYears.options.length - 1].selected = true;
         optionsCell.insert(cboYears);
      }
      else
         cboYears.options[0] = new Option("_", "_");


      this.distinctLevels.sort();

      // If this product has no levels, it'll have one value: "_"
      if (!(this.distinctLevels.length == 1 && this.distinctLevels[0] == "_"))
      {
         for (var i = 0; i < this.distinctLevels.length; i++)
            cboLevels.options[i] = new Option(this.distinctLevels[i], this.distinctLevels[i]);

         // Select the last item by default
         cboLevels.options[cboLevels.options.length - 1].selected = true;
         optionsCell.insert(cboLevels);
      }
      else
         cboLevels.options[0] = new Option("_", "_");

      var initialPrice = this.GetTierByYearAndLevel(cboYears.value, cboLevels.value).price;
      priceCell.update(formatPrice(initialPrice));

      var addLink = new Element("a", { "href": "#" });
      var addImage = new Element("img", { "src": "images/button_add.gif" });
      var hiddenID = new Element("input", { "type": "hidden", "value": "R:" + this.id });

      addLink.update(addImage);

      addCell.insert(addLink);
      addCell.insert(hiddenID);

      return productTable;
   }
});

var Tier = Class.create({
   initialize: function (id, groupName, year, level, price)
   {
      this.id = id;
      this.groupName = groupName;
      this.year = year;
      this.level = level;
      this.price = price;
   }
});
