var IMAGE_SIZE = 120;  // default image size
var MAX_TITLE_CHARS = 100; // Maximum length of a title
var RESULTS_LIMIT = 20;
// DS 09/26 - targets of href display have been changed to _parent to load in parent of Iframe 
/**
 * Escapes the given string, so it is HTML safe.
 * @param {String} html The input HTML to escape.
 * @return {String} The escaped HTML.
 */
function escapeHtml(html) {
  var s = '' + html;
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

/**
 * Escapes the given URL.
 * @param {String} url The URL to escape.
 * @return {String} The escaped URL.
 */
function escapeUrl(url) {
  return url.replace(/</g, '%3C').replace(/>/g, '%3E').replace(/"/g, '%22');
}

/**
 * Escape a string, so that we can embed the string in the js code inside
 * another string.
 * @param {String} str The string to escape.
 * @return {String} The escaped string.
 */
function escapeJsString(str) {
  return str.replace(/\\/g, '\\\\').replace(/\"/g, '\\\"')
      .replace(/\'/g, '\\\'');
}

/**
 * Adds ellipses to long strings.
 * @param {Number} nChars Maximum number of characters in result.
 * @param {String} str String to possibly add ellipses to.
 * @return {String} a string no longer than nChars.
 */
function truncateAndAddEllipses(nChars, str) {
  return str.length > nChars ? str.substring(0, nChars - 0) + '...' : str;

}

/**
 * Strips off all HTML tags.
 * @param {String} html The input HTML.
 * @return {String} The stripped version of HTML.
 */
function stripOffHtmlTags(html) {
  var s = '' + html;
  return s.replace(/\<[^\>]+\>/g, '');
}

/**
 * Escapes characters that must be escaped for the shopping API.
 * @param {String} name Name to be escaped.
 * @return {String} The escaped name.
 */
function escapeShoppingApiString(name) {
  return name.replace(/([,/=:|()])/g, '\\$1');
}

/**
 * Returns the HTML for a single item in list mode.
 * @param {String} thumb The optional url of the thumbnail.
 * @param {String} title The name of the item.
 * @param {String} href The url of the item page.
 * @param {String} description The item's description.
 * @param {String} docid The item's doc ID.
 * @param {String} price The price of the item.
 * @return {String} The html to render.
 */
function getHTMLOneEntryList(thumb, title, href, description, docid, price) {
  var html = [];
  html.push('<tr class="cse-commerce-result">');
  html.push('<td>');
  if (thumb) {
    html.push('<a class="url" target="_parent" href="');
    html.push(href);
    html.push('">');
    html.push('<img src="');
    html.push(thumb);
    html.push('" border="0">');
    html.push('</a>');
  }
  html.push('</td>');
  html.push('<td>');
  html.push('<div>');
  html.push('<a class="url" target="_parent" href="');
  html.push(href);
  html.push('"><strong>');
  html.push(title);
  html.push('</strong></a>');
  html.push('<div>');
  html.push(description);
  html.push('</div>');
  html.push('<div>Google ID: ');
  html.push(docid);
  html.push('</div>');
  html.push('</div>');
  html.push('</td>');
  html.push('<td class="cse-commerce-price-col">');
  html.push(price);
  html.push('</td>');
  html.push('</tr>');
  return html.join("");
}

/**
 * Returns the HTML for a single item in grid mode.
 * @param {String} thumb The optional url of the thumbnail.
 * @param {String} title The name of the item.
 * @param {String} href The url of the item page.
 * @param {String} price The price of the item.
 * @return {String} The html to render.
 */
function getHTMLOneEntryGrid(thumb, title, href, price) {
  var s = '<li class="result-grid"><div class="result-cont-grid">' +
      (thumb ? '<p class="result-image-grid"><a class="url" target="_parent" href="' +
       href + '"><img border="0" src="' + thumb + '"></a></p>' : '');

  if (title.length > MAX_TITLE_CHARS + 3) {
    title = title.substring(0, MAX_TITLE_CHARS) + '...';
  }

  return s + '<h3 class="result-h3-grid"><a wrap=soft target="_parent" href="' +
       href + '">' + title + '</a></h3><b>' + price + '</b></div>';
}

/**
 * Returns the html for a single item.
 * @param {ProductObject} product Product to generate data for.
 * @param {String} view 'list' or 'grid'.
 * @return {String} The html for this item.
 */
function getHTMLProduct(product, view) {
  var description = product.description;
  if (description.length > 200) {
    description = description.substr(0, 200) + '...';
  }
  var thumb = product.images ? product.images[0].thumbnails[0].link : undefined;
  var title = escapeHtml(product.title);
  var parkhref = escapeUrl(product.link);
  var href = parkhref.replace("theparkcatalog.com", "cigaretteurns.com");
  var price = formatPrice(product.inventories[0].price,
                          product.inventories[0].currency);
  var docid = product.googleId;
  return view == 'grid' ?
      getHTMLOneEntryGrid(thumb, title, href, price) :
      getHTMLOneEntryList(thumb, title, href, description, docid, price);
 }

/**
 * Returns the HTML for an array of items.
 * @param {ItemObject[]} items Items to generate HTML for.
 * @param {String} view 'list' or 'grid'.
 * @return {String} The HTML for these items.
 */
function getHTMLItems(items, view) {
  var html = [];
  if (items) {
    for (var i = 0, len = items.length; i < len; ++i) {
      html[i] = getHTMLProduct(items[i].product, view);
    }
  }
  return html.join('');
}

function getHTMLFormattedPromotion(name, link, description, image_link) {
  var str = '<div class="cse-commerce-promotion">';
  str += '<table><tr>';
  if (image_link) {
    str +=
        '<td class="cse-commerce-promotion-image" width="' + IMAGE_SIZE + '">';
    str += '<a href="' + escapeUrl(link) + '" target="_parent">';
    str += '<img src="' + escapeUrl(image_link);
    str += '" height="' + IMAGE_SIZE + '" onload="imgOnLoad(this)"></img>';
    str += '</a>';
    str += '</td>';
  }
  str += '<td class="cse-commerce-promotion-title">' +
      '<strong><a href="' + escapeUrl(link) + '\" target="_parent">' +
      escapeHtml(stripOffHtmlTags(name)) + '</a></strong>';
  if (description) {
    str += '<br/><span>' + escapeHtml(description) + '</span>';
  }
  str += '</div></td></tr></table></div>';
  return str;
}

/**
 * Returns the html for a single standard promotion.
 * @param {PromotionObject} promotion Promotion to generate data for.
 * @return {String} The html for this promotion.
 */
function getHTMLStandardPromotion(promotion) {
  return getHTMLFormattedPromotion(promotion.name, promotion.destLink,
                                   promotion.description, promotion.imageLink);
}

/**
 * Returns the html for a single product promotion.
 * @param {PromotionObject} promotion Promotion to generate data for.
 * @return {String} The html for this promotion.
 */
function getHTMLProductPromotion(promotion) {
  var product = promotion.product;
  var image = null;
  if (product.images) {
    image = product.images[0].link;
  }
  return getHTMLFormattedPromotion(product.title, product.link,
                                   product.description, image);
}

/**
 * Returns the html for a single custom promotion.
 * @param {PromotionObject} promotion Promotion to generate data for.
 * @return {String} The html for this promotion.
 */
function getHTMLCustomPromotion(promotion) {
  // Normally this would simply return the promotion.customHtml data, but
  // because there may be references to a customer's site in the HTML that may
  // not work in the preview page we don't want to use it directly in the page.
  var str = '<div class="cse-commerce-promotion">';
  str += 'Custom Promotion triggered:';
  str += '<div class="cse-commerce-promotion-customhtml">';
  str += escapeHtml(promotion.customHtml);
  str += '</div>';
  str += '</div>';
  return str;
}

/**
 * Returns the html for a single promotion.
 * @param {PromotionObject} promotion Promotion to generate data for.
 * @return {String} The html for this promotion.
 */
function getHTMLPromotion(promotion) {
  switch (promotion.type) {
    case 'standard':
      return getHTMLStandardPromotion(promotion);
    case 'product':
      return getHTMLProductPromotion(promotion);
    case 'custom':
      return getHTMLCustomPromotion(promotion);
    default:
      return '';
  }
}

/**
 * Returns the HTML for an array of promotions.
 * @param {PromotionObject[]} promotions Promotions to generate HTML for.
 * @return {String} The HTML for these promotions.
 */
function getHTMLPromotions(promotions) {
  var html = [];
  if (promotions) {
    for (var i = 0, len = promotions.length; i < len; ++i) {
      html[i] = getHTMLPromotion(promotions[i]);
    }
  }
  return html.join('');
}

/**
 * Returns the html for a single redirect.
 * @param {RedirectObject} redirect The redirect to generate HTML for.
 * @return {String} The html for this redirect.
 */
function getHTMLRedirect(redirect) {
  var str = '<div class="cse-commerce-redirect">';
  str += 'Redirect triggered for ';
  str += '<a href="' + escapeUrl(redirect) + '\">' + escapeHtml(redirect) +
      '</a>';
  str += '</div>';
  return str;
}

/**
 * Returns the HTML for an array of redirects.
 * @param {RedirectsObject[]} redirects Redirects to generate HTML for.
 * @return {String} The HTML for these redirects.
 */
function getHTMLRedirects(redirects) {
  var html = [];
  if (redirects) {
    for (var i = 0, len = redirects.length; i < len; ++i) {
      html[i] = getHTMLRedirect(redirects[i]);
    }
  }
  return html.join('');
}

/**
 * Returns the HTML for the promotions and items for either the list or grid
 * view.
 * @param {ItemObject[]} items The items to display.
 * @param {PromotionObject[]} promotions The promotions to display.
 * @param {RedirectObject[]} redirects The redirects to display.
 * @param {String} view 'grid' or 'list'.
 * @return {String} The HTML to render.
 */
function getHTMLSearchResults(items, promotions, redirects, view) {
  var s = getHTMLRedirects(redirects);
  s += getHTMLPromotions(promotions);
  s += view == 'grid' ? '<div id="cse-main-div">' : '<table><colgroup>' +
               '<col id="cse-commerce-image">' +
               '<col id="cse-commerce-description">' +
               '<col id="cse-commerce-price"></colgroup><tbody>';
  s += getHTMLItems(items, view);
  s += view == 'grid' ? '' : '</tbody></table>';
  return s;
}

/**
 * Callback when an image is loaded to scale it to fit.
 * @param {ImageObject} img The freshly loaded image.
 */
function imgOnLoad(img) {
  var w = img.width;
  var h = img.height;  
  if (w > h) {
    img.width = IMAGE_SIZE;
    img.height = IMAGE_SIZE * h / w;
  } else {
    img.height = IMAGE_SIZE;
    img.width = IMAGE_SIZE * w / h;
  }
}

/**
 * Sets the inner html in the given html element or hides the element if the
 * supplied html is empty.
 * @param {object} panel Html element to add html to.
 * @param {String} html Html to add to the panel.
 */
function setInnerHtmlOrHide(panel, html) {
  if (html) {
    panel.innerHTML = html;
    panel.style.display = '';
  } else {
    panel.style.display = 'none';
  }
}

/**
 * Returns the name to display for a given facet.
 * @param {FacetObject} facet The facet from which to extract the name.
 * @return {String} The name to be displayed for the facet.
 */
function getFacetDisplayName(facet) {
  return facet.displayName;
}

/**
 * Returns the key for a given facet.  For attributes this simply returns the
 * name, while properties have their name returned in all caps.  This is to
 * prevent collisions between attributes and properties of the same name.
 * @param {FacetObject} facet The facet from which to extract the key.
 * @return {String} The key for the facet.
 */
function getFacetKey(facet) {
  return facet.property ? facet.property.toUpperCase() : facet.name;
}

/**
 * Returns a restrict string for the given bucket and facet that can be used
 * with restrictBy=.
 * @param {BucketObject} bucket Bucket for the restrict.
 * @param {FacetObject} facet Facet for the restrict.
 * @return {String} The restrict string.
 */
function getRestrict(bucket, facet) {
  var restrict;
  if (facet.property) {
    restrict = facet.property;
  }
  else {
    restrict = escapeShoppingApiString(facet.name) + '(' + facet.type + ')';
    if (facet.unit) {
      restrict = restrict + '/' + facet.unit;
    }
  }
  restrict += '=';
  if ('value' in bucket) {
    return restrict + escapeShoppingApiString(bucket.value.toString());
  }
  restrict += bucket.minExclusive ? '(' : '[';
  restrict += bucket.min || '*';
  restrict += ',';
  restrict += bucket.max || '*';
  restrict += bucket.maxExclusive ? ')' : ']';
  return restrict;
}

/**
 * Returns the bucket count in the facet that corresponds to the given restrict
 * @param {FacetObject} facet Facet object in which to find bucket.
 * @param {String} restrict Restrict string to match.
 * @return {Number} The count in the matched bucket or 0 if no bucket was
 *    matched.
 */
function getBucketCountForRestrict(facet, restrict) {
  for (var j = 0, bucket; bucket = facet.buckets[j]; ++j) {
    if (getRestrict(bucket, facet) == restrict) {
      return bucket.count;
    }
  }
  return 0;
}

/**
 * Formats a price for display in the given currency.
 * @param {Number} priceValue Value to format.
 * @param {String} currency The currency code.
 * @return {String} Formatted price.
 */
function formatPrice(priceValue, currency) {
  if (currency == 'AUD') {
    return '$' + priceValue.toFixed(2);
  } else if (currency == 'EUR') {
    return '\u20ac' + priceValue.toFixed(2);
  } else if (currency == 'GBP') {
    return '£' + priceValue.toFixed(2);
  } else if (currency == 'JPY') {
    return '¥' + priceValue.toFixed(0);
  } else if (currency == 'USD') {
    return '$' + priceValue.toFixed(2);
  } else {
    return priceValue.toFixed(2) + ' ' + currency;
  }
}

/*
 * Formats the value in a bucket of a facet.
 * @param {Number} value Value to format.
 * @param {FacetObject} facet Facet this value is part of.
 * @return {String} The formatted value
 */
function formatBucketValue(value, facet) {
  if (facet.name == 'price' && facet.type == 'float') {
    return formatPrice(value, facet.unit);
  } else {
    return value.toString();
  }
}

/**
 * Returns a title to display for the given bucket and facet.
 * @param {BucketObject} bucket Bucket to get title for.
 * @param {FacetObject} facet Facet that the bucket belongs to.
 * @return {String} Title to display for the bucket.
 */
function getBucketTitle(bucket, facet) {
  if ('value' in bucket) {
    return bucket.value.toString();
  }
  var delta = facet.type == 'int' ? 1 : 0.01;
  if ('min' in bucket && 'max' in bucket) {
    var min = bucket.minExclusive ? bucket.min + delta : bucket.min;
    var max = bucket.maxExclusive ? bucket.max - delta : bucket.max;
    return min == max ? min.toString() :
        formatBucketValue(min, facet) + ' - ' + formatBucketValue(max, facet);
  } else if ('max' in bucket) {
    return 'Under ' + formatBucketValue(
        bucket.maxExclusive ? bucket.max : bucket.max + delta, facet);
  } else {
    return 'Over ' + formatBucketValue(
        bucket.minExclusive ? bucket.min : bucket.min - delta, facet);
  }
}

/**
 * Ensures the appropriate images are displayed for the given view
 * and that both the list and grid images are visible.
 * @param {ImageObject} listImage The list image button.
 * @param {ImageObject} gridImage The grid image button.
 * @param {String} view 'list' or 'grid'.
 */
function ensureImagesVisible(listImage, gridImage, view) {
  var imagePrefix = 'http://www.google.com/cse/images/commerce/';
  if (view == 'grid') {
    gridImage.src = imagePrefix + 'grid_view_selected.png';
    listImage.src = imagePrefix + 'list_view_unselected.png';
  } else {
    gridImage.src = imagePrefix + 'grid_view_unselected.png';
    listImage.src = imagePrefix + 'list_view_selected.png';
  }
  gridImage.style.display = '';
  listImage.style.display = '';
}

/**
 * Returns the HTML text for the found text message.
 * @param {String} query The term queried for.
 * @param {Number} currentPage The current page.
 * @param {Number} totalResults The total number of results.
 * @return {String} A text string containing the number found.
 */
function printFoundText(query, currentPage, totalResults) {
  query = escapeHtml(query);
  if (totalResults > 0) {
    var endIndex = Math.min(currentPage * RESULTS_LIMIT, totalResults);
    var startIndex = (currentPage - 1) * RESULTS_LIMIT + 1;
    return ['Results ',
            startIndex.toString(),
            ' - ',
            endIndex.toString(),
            ' of about ',
            totalResults.toString(),
            ' for <string>',
            query,
            '</string>'].join('');
  } else {
    return '0 results for <strong>' + query + '</strong>';
  }
}

/*
 * Returns the HTML text for a link to one of the pages
 * @param {String} text Text of link.
 * @param {Number} page Page number.
 * @param {boolean} link Whether to add a link.
 * @param {boolean} last Whether this is the last page.
 */
function printPageLink(text, page, link, last) {
  var html = '';
  if (link) {
    html += '<a href="javascript:void(0);" onclick="pageSubmit(' + page +
        ');">';
  } else {
    html += '<strong>';
  }
  html += text;
  html += link ? '</a>' : '</strong>';
  if (!last) {
    html += '&nbsp;|&nbsp;';
  }
  return html;
}

/**
 * Returns the HTML text for the paging bar.
 * @param {Number} currentPage Current page number.
 * @param {Number} totalPages Total number of pages.
 * @return {String} The HTML to display.
 */
function printPageBar(currentPage, totalPages) {
  var PAGES_ONSCREEN = 10;  // We display 10 page links at once.

  if (totalPages <= 0) {
    return '';
  }

  var pagesStart = currentPage - Math.floor((PAGES_ONSCREEN - 1) / 2);
  var pagesEnd = currentPage + Math.floor(PAGES_ONSCREEN / 2);
  if (pagesStart < 1) {
    var delta = 1 - pagesStart;
    pagesStart = 1;
    pagesEnd += delta;
    if (pagesEnd > totalPages) {
      pagesEnd = totalPages;
    }
  } else if (pagesEnd > totalPages) {
    var delta = pagesEnd - totalPages;
    pagesEnd = totalPages;
    pagesStart -= delta;
    if (pagesStart < 1) {
      pagesStart = 1;
    }
  }

  var str = [];
  if (currentPage > 1) {
    str.push(printPageLink('&lsaquo; Previous', currentPage - 1, true, false));
  }
  for (var i = pagesStart; i <= pagesEnd; ++i) {
    str.push(printPageLink(i, i, i != currentPage,
                           i == totalPages && currentPage == totalPages));
  }
  if (currentPage < totalPages) {
    str.push(printPageLink('Next &rsaquo;', currentPage + 1, true, true));
  }
  return str.join('');
}

/**
 * Initializes Commerce Search preview code.
 *
 * Note: This script requires googleapis.min.js:
 * <script src="https://ajax.googleapis.com/ajax/libs/googleapis/<version>/googleapis.min.js"></script>
 * where <version> is the current/latest version of the googleapis version.
 *
 * @param {String} cx commerce search engine identifier.
 * @param {object} resultsPanel Html element in which to display search results.
 * @param {object} unfilteredFacetsPanel Html element in which to display
 *     unfiltered facets.
 * @param {object} filteredFacetsPanel Html element in which to display filtered
 *    facets.
 * @param {String} country 2 letter country code.
 * @param {object} doneCallback Function to call after retrieving search
 *    results.
 * @param {object} errorHandler Function to call on an error.
 * @param {String} language BCP-47 language code.
 * @param {String} opt_currency Optional currency code.
 * @param {object} gridImage Html image object in which to display the image for
 *     grid view.
 * @param {object} listImage Html image object in which to display the image for
 *     list view.
 * @param {String} opt_api_developer_key Developer API key.
 * @param {String} useCase CommerceSearchUseCase or ProductSearchUseCase.
 * @param {function} opt_onReady Function to call when the shopping API is
 *     ready.
 */
function initializeCommerceSearch(cx,
                                  resultsPanel,
                                  unfilteredFacetsPanel,
                                  filteredFacetsPanel,
                                  country,
                                  doneCallback,
                                  errorHandler,
                                  language,
                                  opt_currency,
                                  gridImage,
                                  listImage,
                                  opt_api_developer_key,
                                  useCase,
                                  opt_onReady) {
  // Global refinement vars.
  var sortOrder = 'match';
  var currentPage = 1;
  var queryString = '';
  var allItems;
  var allFacets;
  var allPromotions;
  var allRedirects;
  var viewPref = 'list';
  var restrictsMap = {};  // Map which maps an attribute to a restrict.

  country = country.toLowerCase();
  country = country == 'uk' ? 'gb' : country;

  googleapis.load('shopping', 'v1', function() {
    if (opt_onReady) {
      opt_onReady();
    }
  });

  var handleShoppingApiResponse = function(response) {
    if (response.error) {
      errorHandler(response.error.message);
      return;
    }
    allItems = response.items;
    allPromotions = response.promotions;
    allRedirects = response.redirects;
    var spellingSuggestion = response.spelling ?
        response.spelling.suggestion : undefined;
    var totalPages = Math.ceil(response.totalItems / RESULTS_LIMIT);
    doneCallback(response.totalItems, spellingSuggestion, currentPage,
                 totalPages);
    ensureImagesVisible(listImage, gridImage, viewPref);
    response.facets && handleFacets(response.facets);
    resultsPanel.innerHTML =
        getHTMLSearchResults(response.items, response.promotions,
                             response.redirects, viewPref);
  };

  var handleFacets = function(facets) {
    allFacets = facets;
    var filteredFacets = '';
    var unfilteredFacets = '';
    for (var i = 0, facet; facet = facets[i]; ++i) {
      var facetName = getFacetDisplayName(facet);
      var facetKey = getFacetKey(facet);
      //DS 09/26 - don't display strange facets 
      if(facetName.indexOf('_') != -1 || facetName.indexOf('quantity') != -1){
          //Don't display	
      }else{
      if (restrictsMap[facetKey]) {
	filteredFacets += '<div><h4>' + facetName +
	  '</h4><div class="cse-commerce-facet">';
	filteredFacets += '<ul><li><b>' + truncateAndAddEllipses(20,
	  escapeHtml(restrictsMap[facetKey]['display']));

	var count = getBucketCountForRestrict(
	  facet, restrictsMap[facetKey]['value']);
	if (count > 0) {
	filteredFacets += ' (' + count + ')';
	}

	filteredFacets += '</b></li>';
	filteredFacets += '<li><a href="javascript:void(0)" ' +
	  'onClick="javascript:applyFilter(\'' + escapeJsString(facetKey) +
	  '\', null, null)">View all</a></li>';
	filteredFacets += '</ul></div></div>';
	} else {
	unfilteredFacets += '<div id=attr' + i + '>';
	unfilteredFacets += showBuckets(i, false);
	unfilteredFacets += '</div>';
	}
	}
	setInnerHtmlOrHide(filteredFacetsPanel, filteredFacets);
	setInnerHtmlOrHide(unfilteredFacetsPanel, unfilteredFacets);
    }
      
  }

  /**
   * Shows the buckets for an unfiltered facet. If all is set to
   * true all facets are shown with a "less" option if there are more than 5
   * facets. If all is set to false only 5 facets are shown with a "more..."
   * option.
   * @param {Number} i Index of facet to show.
   * @param {boolean} all Whether to show all the buckets.
   * @return {String} The html for the buckets.
   */
  var showBuckets = function(i, all) {
    var facet = allFacets[i];
    var facetName = getFacetDisplayName(facet);
    var facetKey = getFacetKey(facet);
    var str = '<h4>' + facetName +
    	  	'</h4><div class="cse-commerce-facet"><ul>';
    var maxToShow = 5;

    // explicitly included facets that have zero count
    if (!facet.buckets) {
      return;
    }
    for (var j = 0, bucket; bucket = facet.buckets[j]; j++) {
      var displayTitle = getBucketTitle(bucket, facet);
      str += '<li><a href="javascript:void(0)" ' +
          'onClick="javascript:applyFilter(\'' + escapeJsString(facetKey) +
          '\', \'' + escapeJsString(getRestrict(bucket, facet)) + '\', \'' +
          escapeJsString(displayTitle) + '\')">' +
          truncateAndAddEllipses(20, escapeHtml(displayTitle)) +
          ' (' + bucket.count + ')</a></li>';
      if (j == (maxToShow - 1) && !all) {
         str += '<li><a href="javascript:void(0)" ' +
             'onClick="javascript:showBucket(' + i + ',true)">More...</a></li>';
         break;
      }
    }
    if (facet.buckets.length > maxToShow && all) {
      str += '<li><a href="javascript:void(0)" ' +
          'onClick="javascript:showBucket(' + i + ',false)">Less...</a></li>';
    }
    str += '</ul></div>';
    return str;
  }

  var showBucketsDiv = function(index, all) {
    var div = document.getElementById('attr' + index);
    if (div) {
      div.innerHTML = showBuckets(index, all);
    }
  }

  var clearResults = function() {
    gridImage.style.display = 'none';
    listImage.style.display = 'none';
    resultsPanel.innerHTML = '';
  }

  var getMyFeed = function(q) {
    queryString = q.replace(/#/g, '%23');

    var params = {
      rankBy: sortOrder == 'price_low' ? 'price:ascending' :
          (sortOrder == 'price_high' ? 'price:descending' : 'relevancy'),
      startIndex: (currentPage - 1) * RESULTS_LIMIT + 1,
      maxResults: RESULTS_LIMIT,
      q: queryString,
      source: 'cx:' + cx,
      country: country,
      language: language,
      spelling: {'enabled': true},
      promotions: {enabled: true, useGcsConfig: true},
      thumbnails: '*:' + (viewPref == 'grid' ? '120' : '90'),
      facets: {enabled: true, useGcsConfig: true, discover: '20:30'},
      redirects: {enabled: true, useGcsConfig: true},
      useCase: useCase
    };
    if (opt_currency) {
      params['currency'] = opt_currency;
    }

    var restricts = [];
    for (var key in restrictsMap) {
      restricts.push(restrictsMap[key]['value']);
    }
    if (restricts.length > 0) {
      params['restrictBy'] = restricts.join(',');
    }

    googleapis.setDeveloperKey(opt_api_developer_key);
    shopping.products.list(params).execute(handleShoppingApiResponse);
  };

  // Called when we submit a new search term.
  var formSubmit = function(query, order) {
    if (!opt_api_developer_key) {
      errorHandler('Please obtain a Search API for Shopping developer key ' +
          'and enter it in the Commerce Settings panel.');
      return;
    }

    currentPage = 1;

    if (order) {
      sortOrder = order;
    } else {
      sortOrder = 'match';
      restrictsMap = {};
    }

    clearResults();
    getMyFeed(query);
  };

  // Called when we submit a new spelling correction.
  var spellSubmit = function(query) {
    currentPage = 1;
    clearResults();
    getMyFeed(query);
  }

  // Called when we navigate to the given page.
  var pageSubmit = function(page) {
    currentPage = page;
    clearResults();
    getMyFeed(queryString);
  }

  // Called when we view to change the view
  var changeView = function(view, resultsPanel) {
    viewPref = view;
    resultsPanel.innerHTML = getHTMLSearchResults(allItems, allPromotions,
                                                  allRedirects, view, gridImage,
                                                  listImage);
  }

  // Called when a filter is applied.
  var applyFilter = function(query, key, value, display) {
    currentPage = 1;
    if (value) {
      restrictsMap[key] = {'value': value, 'display': display};
    } else {
      delete restrictsMap[key];
    }
    clearResults();
    getMyFeed(query);
  }

  return {
    'cx' : cx,
    'country': country,
    'language': language,
    'useCase': useCase,
    'opt_currency': opt_currency,
    'opt_api_developer_key': opt_api_developer_key,
    'formSubmit': formSubmit,
    'spellSubmit': spellSubmit,
    'pageSubmit': pageSubmit,
    'changeView': changeView,
    'applyFilter': applyFilter,
    'showBucketsDiv': showBucketsDiv };
}


