/***
 * Code for TheCardBox.co.uk
 * (c) 2005 David Thomas
 ***/

  /* CONSTANTS */
  /*************/
  
  // Used by the menu
  NORMAL = 0;
  SELECTED = 1;
  HIDDEN = 2;
  HOVER = 3;
  
  // Used to limit number of search results
  MAX_SEARCH_RESULTS=60;
  
  // Postage categories
  FIRST_CLASS = 0;
  COURIER = 1;
  
  // Price of postage insurance in pounds
  INSURANCE_PRICE = 0.75;
  
  // Different types of product
  CARD = 0;
  BOX = 1;
  ALBUM = 2;
  WOOD = 3;  // wooden spoons and rolling pins
  GLASS = 4;
  GLASSBOX = 5;
  arrTYPES = [CARD,BOX,ALBUM,WOOD,GLASS,GLASSBOX];
  
  // Mapping of types to available postage options
  arrTYPE_POSTAGE_MAP = new Array();
  arrTYPE_POSTAGE_MAP[CARD] = [FIRST_CLASS];
  arrTYPE_POSTAGE_MAP[BOX] = [FIRST_CLASS,COURIER];
  arrTYPE_POSTAGE_MAP[ALBUM] = [FIRST_CLASS];
  arrTYPE_POSTAGE_MAP[WOOD] = [FIRST_CLASS];
  arrTYPE_POSTAGE_MAP[GLASS] = [FIRST_CLASS,COURIER];
  arrTYPE_POSTAGE_MAP[GLASSBOX] = [FIRST_CLASS,COURIER];

  strNextPage = "";

  /* MenuItem class - represents an item in a menu */
  /*************************************************/
  function MenuItem(objMenu,strId,strName,objParent,numStatus,strFilepath)
  {
    /* PROPERTIES */
    this.container=objMenu;    // reference to the containing menu
    this.id=strId;             // the id suffix for this menu item in the HTML
    this.name=strName;         // the text to be displayed for this menu item
    this.parent=objParent;     // the parent of this menu item - null for root items
    this.children=new Array(); // holds the MenuItems which are on this item's sub-menu
    if (this.parent!=null)
    {
      this.depth = this.parent.depth+1; // the depth, used for indenting
      this.parent.children = this.parent.children.concat(this); // register with parent
    }
    else
    {
      this.depth=0;            // depth 0 for root elements
    }
    this.status=numStatus;     // the current status of this item
    this.originalStatus=numStatus; // used for resetting the menu
    this.filepath = strFilepath==null ? "" : strFilepath; // the file to load when this item is selected
    /* METHODS */
    // toString() - Write the row for this menu item
    this.toString = function()
    {
      var arrOut = new Array();
      arrOut[arrOut.length] = '<tr id="MENU_TR_' + this.id + 
             '" class="' + this.container.baseStyle + (this.status==HIDDEN ? 'Hidden' : '') + '">';
      if (this.container.indented)
      {
        for (var j=0; j < this.container.maxDepth*2+1; j++)
        {
          if (j==this.depth)
          {
            arrOut[arrOut.length] = '<td colspan="' + (this.container.maxDepth+1) +
                    '" id="MENU_TD_' + this.id +
                    '" class="' + this.container.baseStyle + (this.status==SELECTED ? 'Selected' : '') +
                    '" onclick="javascript:menu.clickedAt(' + this.id +
                    ')" onmouseover="javascript:this.className+=\'Hover\'' +
                    '" onmouseout="javascript:this.className=this.className.replace(/Hover/,\'\')' +
                    '">' + this.name + '</td>';
            j+=this.container.maxDepth;
          }
          else
          {
            arrOut[arrOut.length] = '<td width="'+this.container.indent+'">&nbsp;</td>';
          }
        }
      }
      else
      {
        arrOut[arrOut.length] = '<td id="MENU_TD_' + this.id +
                '" class="' + this.container.baseStyle + (this.status==SELECTED ? 'Selected' : '') +
                    '" onclick="javascript:menu.clickedAt(' + this.id +
                    ')" onmouseover="javascript:this.className+=\'Hover\'' +
                    '" onmouseout="javascript:this.className=this.className.replace(/Hover/,\'\')' +
                    '">' + this.name + '</td>';
      }
      return arrOut.join("") + "</tr>";
    }
    // setStatus(numNewStatus) - change the status and apply the corresponding style
    // This method does not work until the menu has been written into the page
    this.setStatus = function(numNewStatus)
    {
      if (numNewStatus==null)
      {
        this.status = this.originalStatus;
      }
      else
      {
        this.status = numNewStatus;
      }
      this.applyStyle(this.status);
    }
    // applyStyle(numStatus)
    this.applyStyle = function(numStatus)
    {
      var domTR = document.getElementById("MENU_TR_"+this.id);
      var domTD = document.getElementById("MENU_TD_"+this.id);
      var arrTRStyle = ["","","Hidden"];
      var arrTDStyle = ["","Selected",""];
      domTR.className = this.container.baseStyle + arrTRStyle[numStatus];
      domTD.className = this.container.baseStyle + arrTDStyle[numStatus];
    }
  }

  function menuClick(strPath,objItem) {
    numPage = 1;
    strCatId = objItem.id
    strNextPage = "browse.htm";
    navigate();
  }

  /* Menu class - represents an HTML menu */
  /****************************************/
  function Menu(strStyle,numIndent,numWidth,funClickAction)
  {
    /* PROPERTIES */
    this.indented = numIndent==0 ? false : true;    // whether this menu uses indentation
    this.indent = numIndent==null ? 20 : numIndent; // the size of the indent
    this.width = numWidth==null ? 160 : numWidth;   // the total width of the menu
    this.baseStyle = strStyle;                      // the styles for the different stati
    this.maxDepth = 0;                              // the maximum depth of items
    this.menuItems = new Array();                   // all the MenuItems in this menu
    this.rootItems = new Array();                   // just the root MenuItems
    // addItem(strName,objParent,strFilepath) - add an item to the menu
    // Specify null as the parent to add a root-level item
    this.addItem = function(strName,objParent,strFilepath)
    {
      var numInitStatus = objParent==null ? NORMAL : HIDDEN;
      var objNewItem = new MenuItem(this,this.menuItems.length.toString(),strName,objParent,numInitStatus,strFilepath);
      this.menuItems[this.menuItems.length] = objNewItem;
      this.maxDepth = Math.max(this.maxDepth,objNewItem.depth);
      if (objParent==null)
      {
        this.rootItems[this.rootItems.length] = objNewItem;
      }
      return this.menuItems[this.menuItems.length-1];
    }
    // toString() - returns the HTML for the menu
    this.toString = function()
    {
      var arrOut = ['<table class="' + this.baseStyle + '" cellSpacing="0" cellPadding="0">'];
      for (var i=0; i < this.menuItems.length; i++)
      {
        arrOut[arrOut.length] = this.menuItems[i].toString();
      }
      // the next part forces the menu cell widths to stay fixed
      arrOut[arrOut.length] = '<tr height="1">';
      for (i=0; i < this.maxDepth*2+1; i++)
      {
        arrOut[arrOut.length] = i==this.maxDepth ? '<td width="' + (this.width-(this.maxDepth*2+1)*this.indent) + '"><img src="graphics/blank.gif" /></td>' : '<td width="' + this.indent + '"><img src="graphics/blank.gif" /></td>';
      }
      return arrOut.join("") + "</tr></table>";
    }
    // clickAction(objClicked,numItem) - called when an active item is clicked
    this.clickAction = funClickAction ? funClickAction : menuClick;
    // reset() - Set each item back to its initial display status
    this.reset = function()
    {
      for (var i=0; i < this.menuItems.length; this.menuItems[i++].setStatus()) { }
    }
    // select(objSelected) - select the given MenuItem
    this.select = function(objSelected)
    {
      //loop - set parent to selected and siblings to normal
      var objLastSelected=null;
      while (objSelected!=null)
      {
        for (var i=0; i < objSelected.children.length; i++)
        {
          if (objSelected.children[i]!=objLastSelected)
          {
              objSelected.children[i].setStatus(NORMAL);
          }
        }
        objSelected.setStatus(SELECTED);
        objLastSelected = objSelected;
        objSelected = objSelected.parent;
      }
    }
    // selectCategory(strId) - select the MenuItem with the given index
    this.selectCategory = function(strId) {
      if (strId.length > 0) {
        var objItem = this.menuItems[parseInt(strId)];
        this.select(objItem);
        arrProdIds = objItem.filepath.split(",");
      }
    }
    // clickedAt(numId) - called when a MenuItem is clicked
    this.clickedAt = function(numId)
    {
      var objClicked = this.menuItems[numId];
      if (objClicked.filepath != "")
      {
        this.clickAction(objClicked.filepath,objClicked);
      }
      else
      {
        // This part could be more intelligent - expand or contract the immediate children
        this.reset();
        this.select(objClicked);
      }
    }
  }

/* Code related to products */

function funProdImageToString() {
  return '<img src="' + this.path +
         (this.width==-1 ? '"' : '" width="' + this.width + '" height="' + this.height + '"') +
         ' />';
}

function ProductImage(strPath,numWidth,numHeight) {
  this.path = strPath;
  if (numWidth==null)
  {
    numWidth = -1;
    numHeight = -1;
  }
  this.width = numWidth;
  this.height = numHeight;
  this.toString = funProdImageToString;
}

function ProductOptionValue(strName,arrImages) {
  this.value = strName;
  if (arrImages && arrImages.length > 0) {
    this.images = arrImages;
  }
  else {
    this.images = new Array();
  }
}

DEFAULT_THUMBNAIL = new ProductImage("graphics/missingpicture.jpg","75","100");
MAX_THUMBNAIL_SIZE = 150;
MAX_IMAGE_SIZE = 400;

function funAddImage(strPath,numWidth,numHeight) {
  // Adjust width and height so as not to exceed maximum
  var numScale = Math.min(MAX_IMAGE_SIZE/Math.max(numWidth,numHeight),1);
  var objImage = new ProductImage(strPath,numWidth*numScale,numHeight*numScale);
  this.images[this.images.length] = objImage;
  return objImage;
}

function funSetThumbnail(strPath,numWidth,numHeight) {
  // Adjust width and height so as not to exceed maximum
  var numScale = Math.min(MAX_THUMBNAIL_SIZE/Math.max(numWidth,numHeight),1);
  this.thumbnail = new ProductImage(strPath,numWidth*numScale,numHeight*numScale);
}

function funSetDescription(strDescription) {
  this.description = strDescription;
}

function funSetType(numType) {
  this.productType = numType;
  this.postageOptions = arrTYPE_POSTAGE_MAP[numType];
}

function funAddOption(strName,strValue,arrImages) {
  // sanitise the name
  var strInternalName = strName.replace(/[ ;:{}\[\]\(\)]/g,"_")
  // create it if this is the first time we've seen it
  if (!this.options[strInternalName]) {
    this.optionNames[strInternalName] = strName;
    this.options[strInternalName] = new Array();
    this.optionCount++;
  }
  // append the given value and images to the list of options
  this.options[strInternalName][this.options[strInternalName].length] = new ProductOptionValue(strValue,arrImages);
}

// Return true if the given item is in the given array
function funContains(objItem,arrList) {
  blnFound = false;
  if (arrList)
  {
    for (var i=0; i<arrList.length && !blnFound; i++) {
      blnFound = blnFound || (arrList[i] == objItem);
    }
  }
  return blnFound;
}

arrImages = new Array();

// Returns true only if the intersection is returned
function funImagesForOptions(arrOptions) {
  // Fallback - use the whole lot
  arrImages = this.images;
  blnOut = false;
  
  // Compute the intersection of the arrays of images
  var arrIntersection = new Array();
  for (var i=0; i<this.images.length; i++) {
    blnIn = true;
    for (var j in arrOptions) {
      blnIn = blnIn && funContains(this.images[i], arrOptions[j].images);
    }
    if (blnIn) {
      arrIntersection[arrIntersection.length] = this.images[i];
    }
  }
  
  if (arrIntersection.length > 0) {
    arrImages = arrIntersection;
    blnOut = true;
  }
  else {
    // Compute the union
    var arrUnion = new Array();
    for (var i in arrOptions) {
      for (var j=0; j<arrOptions[i].images.length; j++) {
        if (!funContains(arrOptions[i].images[j], arrUnion)) {
          arrUnion[arrUnion.length] = arrOptions[i].images[j];
        }
      }
    }
    if (arrUnion.length > 0) {
      arrImages = arrUnion;
    }
  }
  return blnOut;
}

// Can be used as a method or as a standalone function
function funFormatPrice(numPrice) {
  if(!numPrice) {
    numPrice = this.price;
  }
  var strPence = "" + Math.round(numPrice*100);
  if (strPence.length==1) {
    return "0.0" + strPence;
  } else if (strPence.length==2) {
    return "0." + strPence;
  } else {
    return strPence.slice(0,-2) + "." + strPence.slice(-2);
  }
}

function funSearchScore(arrPattern) {
  var numOut = 0;
  for (var i=0; i<arrPattern.length; i++) {
    if (this.name.search(arrPattern[i])>-1) {
      numOut += this.name.match(arrPattern[i]).length * 3;
    }
    if (this.description.search(arrPattern[i])>-1) {
      numOut += this.description.match(arrPattern[i]).length;
    }
  }
  return numOut;
}

function funMarkAsNew() {
  this.isNew = true;
}

function funMarkAsOutOfStock() {
  this.isOutOfStock = true;
}

function Product(numId,strName,numPrice)
{
  this.id = numId;
  this.name = strName;
  this.price = numPrice;
  this.thumbnail = DEFAULT_THUMBNAIL;
  this.description = "";
  this.images = new Array();
  this.options = new Array();
  this.optionNames = new Array();
  this.optionCount = 0;
  this.productType = CARD;
  this.postageOptions = arrTYPE_POSTAGE_MAP[CARD];
  this.isNew = false;
  this.isOutOfStock = false;
  this.addImage = funAddImage;
  this.addOption = funAddOption;
  this.setThumbnail = funSetThumbnail;
  this.setDescription = funSetDescription;
  this.setType = funSetType;
  this.imagesForOptions = funImagesForOptions;
  this.markAsNew = funMarkAsNew;
  this.markAsOutOfStock = funMarkAsOutOfStock;
  this.formatPrice = funFormatPrice;
  this.searchScore = funSearchScore;
}

/* Code for the basket */

function BasketItem(strId,strQuantity) {
  this.productId = strId;
  this.quantity = strQuantity;
}

function itemCost(objItem) {
  return arrCatalog[objItem.productId].price * objItem.quantity;
}

function itemAsString(objItem) {
  var arrOut = new Array();
  for (var strItem in objItem) {
    arrOut[arrOut.length] = strItem + ':"' + objItem[strItem] + '"';
  }
  return "{" + arrOut.join(",") + "}";
}

function basketAsString() {
  var arrOut = new Array();
  for (var i=0; i<arrBasket.length; i++) {
    arrOut[arrOut.length] = itemAsString(arrBasket[i]);
  }
  return '[' + arrOut.join(',') + ']';
}

function showBasket() {
  var domBasket = document.getElementById("BASKET");
  if(domBasket && arrBasket.length > 0) {
    var numTotal = 0;
    var numQuantity = 0;
    for (var i=0; i<arrBasket.length; i++) {
      numTotal += itemCost(arrBasket[i]);
      numQuantity += parseInt(arrBasket[i].quantity);
    }
    domBasket.innerText = numQuantity + " item" + (numQuantity>1 ? "s" : "") + ", £" + funFormatPrice(numTotal) + " + p&p";
    domBasket.className = "basket";
  }
}

function basketCost() {
  if (numPostageOption==-1) {
    // we shouldn't be here
    return 0;
  }
  var numOut = 0;
  for (var i=0; i<arrBasket.length; i++) {
    numOut += itemCost(arrBasket[i]);
  }
  if (blnInsurance) {
    numOut += INSURANCE_PRICE;
  }
  numOut += arrPostageCostFunctions[numPostageOption](arrBasket)
  return funFormatPrice(numOut);
}

function basket() {
  numPage = 1;
  strCatId = "";
  strProdId = "";
  strNextPage = "basket.htm";
  navigate();
}

/* Code for the postage */

// List of the options
arrPostageOptions = [FIRST_CLASS,COURIER];

// Names that are used to display the options
arrPostageNames = new Array();
arrPostageNames[FIRST_CLASS] = "Royal Mail First Class";
arrPostageNames[COURIER] = "Courier";

// Functions that are used to calculate the total cost
function funFirstClassCost(arrBasket) {
  
  var numOut = 0;  // variable to hold the outcome
  
  // count the number of each type of item in the basket
  var arrCounter = new Array();
  for (var i=0; i<arrTYPES.length; i++) {
    arrCounter[arrTYPES[i]] = 0;
  }
  for (var i=0; i<arrBasket.length; i++) {
    arrCounter[arrCatalog[arrBasket[i].productId].productType] += parseInt(arrBasket[i].quantity);
  }
  // Special calculations for each type
  
  //   prices for posting 0-10 cards
  var arrCardPrices = [0,0.3,0.64,1.07,1.07,1.07,2.8,2.8,2.8,2.8,2.8];
  if (arrCounter[CARD]<=10) {
    numOut += arrCardPrices[arrCounter[CARD]];
  } else {
  //   price for posting more than 10 cards
    numOut += 3.64;
  }
  
  //   price per box
  numOut += arrCounter[BOX] * 5.97;
  
  //   price per album
  numOut += arrCounter[ALBUM] * 5.97;
  
  //   price per rolling pin/wooden spoon
  numOut += arrCounter[WOOD] * 3.6;
  
  //   price per champagne flute
  numOut += arrCounter[GLASS] * 5.97;
  
  // price per boxed flute
  numOut += arrCounter[GLASSBOX] * 8.2;
  
  return numOut;
}

function funCourierCost(arrBasket) {
  return 10;
}

arrPostageCostFunctions = new Array();
arrPostageCostFunctions[FIRST_CLASS] = funFirstClassCost;
arrPostageCostFunctions[COURIER] = funCourierCost;

function validOptions() {
  var arrTemp = new Array();
  for (var i=0; i<arrBasket.length; i++) {
    var arrOptionsForItem = arrCatalog[arrBasket[i].productId].postageOptions;
    for (var j=0; j<arrOptionsForItem.length; j++) {
      arrTemp[arrOptionsForItem[j]] = true;
    }
  }
  var arrOut = new Array();
  for (var i in arrTemp) {
    arrOut[arrOut.length] = i;
  }
  return arrOut;
}

/* Code for the search */

function SearchResult(objProduct,numScore) {
  this.product = objProduct;
  this.score = numScore;
}

function resultSorter(objA,objB) {
  return objB.score-objA.score;
}

function search(strSearch) {
  // Convert the search string into a series of regular expressions
  // Allow letters, numbers, hyphens, spaces and single and double quotes
  strSearch = strSearch.replace(/[^A-Za-z0-9 '"\-]/g,"");
  var arrSearch = strSearch.split(/ +/g);
  for (var i=0; i<arrSearch.length; i++) {
    // arrSearch[i] = new RegExp(arrSearch[i],"ig");
    arrSearch[i] = new RegExp("\\b" + arrSearch[i],"ig");  // match start of words only
  }

  // Run through the catalogue getting the score for each product
  // and adding any that score more than 0 to the search results
  var arrResults = new Array();
  for (var i in arrCatalog) {
    var numScore = arrCatalog[i].searchScore(arrSearch);
    if (numScore > 0) {
      arrResults[arrResults.length] = new SearchResult(arrCatalog[i],numScore);
    }
  }
  
  // Don't go to another page if no results were found
  if (arrResults.length == 0) {
    alert('No results matching "' + strSearch + '" were found.');
    return; 
  }

  // Sort the search results in descending order of score
  arrResults.sort(resultSorter);

  arrProdIds = new Array();
  for (i=0; i<arrResults.length && i<MAX_SEARCH_RESULTS; i++) {
    arrProdIds[i] = arrResults[i].product.id;
  }

  numPage = 1;
  strCatId = "";
  strNextPage = "browse.htm";
  navigate();
}

/* Code for navigation */

function navigate() {
  document.getElementById("NUM_PAGE").value = numPage;
  document.getElementById("ARR_BASKET").value = basketAsString();
  document.getElementById("STR_CAT_ID").value = '"' + strCatId + '"';
  document.getElementById("STR_PROD_ID").value = '"' + strProdId + '"';
  document.getElementById("ARR_PROD_IDS").value = arrProdIds.length == 0 ? '[]' : '["' + arrProdIds.join('","') + '"]';
  document.getElementById("STR_NEXT_PAGE").value = strNextPage;
  document.getElementById("NUM_POSTAGE_OPTION").value = numPostageOption;
  document.getElementById("BLN_INSURANCE").value = blnInsurance;
  if (strNextPage=="detail.htm" && numBasketIndex>=0) {
    document.getElementById("NUM_BASKET_INDEX").value = numBasketIndex;
  }
  document.getElementById("FRM_NAVIGATE").submit();
}

function goHome() {
  numPage = 1;
  strCatId = "";
  strProdId = "";
  arrProdIds = [];
  strNextPage = "index.htm";
  navigate();
}