﻿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.
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;
    }
});