<?php

/**
 * @file
 * Implement an amazon shopping cart with search and remote cart capabilities
 *
 */

define('AS_DEBUG', FALSE); // Turn on to execute debug code
define('AMAZON_STORE_PATH', variable_get('amazon_store_path', 'amazon_store'));
define('AMAZON_STORE_STORE_NAME', variable_get('amazon_store_store_name', "Amazon Store"));

// This oddity was required by http://drupal.org/node/1343732
// It might be a ctools or FAPI bug, but I wasn't able to figure a way to have the "offers" ctools content type
// Work correctly without a validate function "not found". Obviously could have moved the whole form back inside
// amazon_store.module, and that would have fixed it too.
module_load_include('inc', 'amazon_store', 'amazon_store.pages');

/**
 * Implements hook_init().
 */
function amazon_store_init() {
  $locale = variable_get('amazon_locale', 'US');
  //TODO: We probably don't need to load this in hook_init. Figure out where to do it
  $GLOBALS['amazon_store_search_indexes'] = new SearchIndexes($locale);
}


/**
 * Implements hook_menu().
 */
function amazon_store_menu() {
	$amazon_store_path = amazon_store_get_amazon_store_path();
	$items[$amazon_store_path] = array(
      'title' => variable_get('amazon_store_store_name', 'Amazon Store'),
      'page callback' => '_amazon_store_page',
      'file' => 'amazon_store.pages.inc',
    	'access callback' => TRUE,
      'type' => MENU_NORMAL_ITEM,
  );
  $items["$amazon_store_path/clear_cart"] = array(
      'page callback' => 'amazon_store_clear_cart',
      'file' => 'amazon_store.pages.inc',
    	'access callback' => TRUE,
      'type' => MENU_CALLBACK,
  );
  $items["$amazon_store_path/cart"] = array(
      'page callback' => 'amazon_store_show_cart',
      'file' => 'amazon_store.pages.inc',
    	'access callback' => TRUE,
      'title' => 'Your Amazon Cart',
      'type' => MENU_NORMAL_ITEM,
  );
  $items["$amazon_store_path/item/%"] = array(
      'page callback' => 'amazon_store_item_detail',
      'file' => 'amazon_store.pages.inc',
    	'page arguments' => array(2),
      'access callback' => TRUE,
      'type' => MENU_NORMAL_ITEM,
  );
  $items["$amazon_store_path/clear_cache"] = array(
      'page callback' => 'amazon_store_clear_cache',
      'access callback' => TRUE,
      'type' => MENU_CALLBACK,
  );

// TODO: Consider making amazon_store a tab on amazon settings
  $items['admin/config/services/amazon/store'] = array(
      'title' => t('Store'),
      'page callback' => 'drupal_get_form',
      'page arguments' => array('amazon_store_admin_form'),
      'file' => 'amazon_store.admin.inc',
      'access arguments' => array('access administration pages'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 50,
  );

  return $items;
}



/**
 * Implements hook_block_info.
 */
function amazon_store_block_info() {
  $blocks['amazon_store_search']['info'] = t('Amazon Store Search');
  $blocks['categories']['info'] = t('Amazon Store Categories');
  return $blocks;
}

/**
 * Implements hook_block_view.
 */
function amazon_store_block_view($delta) {
  $block = array();
  switch ($delta) {
    case 'amazon_store_search':
      $block['subject'] = t("Search");
      $block['content'] = theme('amazon_store_search_block');
      break;
    case 'categories':
      $block['subject'] = t('Search Categories');
      $block['content'] = theme('amazon_store_categories');
      break;
      return $block;
  }
  return $block;
}

/**
 * Implements hook_block_configure.
 */
function amazon_store_block_configure($delta) {
  switch ($delta) {
    case 'amazon_store_search':
      $form = array();
      $form['amazon_store_search_block_keywords_width'] = array(
            '#type' => 'textfield',
            '#title' => t('Width (in characters) of keywords element'),
            '#size' => 5,
            '#default_value' => variable_get('amazon_store_search_block_keywords_width',    15),
          );
      return $form;
    case "categories":
      $form = array();
      $form['amazon_store_categories_block_num_columns'] = array(
            '#type' => 'select',
            '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5)),
            '#title' => t('Number of columns'),
            '#default_value' => variable_get('amazon_store_categories_block_num_columns',    2),
          );
      return $form;

  }
}

/**
 * Implements hook_block_save.
 */
function amazon_store_block_save($delta, $edit) {
  switch ($delta) {
    case 'amazon_store_search':
      variable_set('amazon_store_search_block_keywords_width', $edit['amazon_store_search_block_keywords_width']);
      break;
    case "categories":
      variable_set('amazon_store_categories_block_num_columns', $edit['amazon_store_categories_block_num_columns']);
      break;
  }
  return;
}

/**
 * Implements hook_theme();().
 */
function amazon_store_theme() {
  return array(
    'amazon_store_search_results' => array(
      'variables' => array('results' => NULL),
      'template' => 'amazon_store_search_results',
    ),
    'amazon_store_cart' => array(
      'variables' => array('cart' => NULL,),
      'template' => 'amazon_store_cart',
      'file' => 'amazon_store.pages.inc',
    ),
    'amazon_store_item_detail' => array(
      'variables' => array('amazon_item' => NULL),
      'template' => 'amazon_store_item_detail',
      'file' => 'amazon_store.pages.inc',
    ),
    'amazon_store_browsenodes_panel' => array(
      'variables' => array('item' => NULL),
      'template' => 'amazon_store_browsenodes_panel',
    ),
    'amazon_store_similar_items_panel' => array(
      'variables' => array('item' => NULL),
      'template' => 'amazon_store_similar_items_panel',
    ),
    'amazon_store_details_panel' => array(
      'variables' => array('item' => NULL),
      'template' => 'amazon_store_details_panel',
    ),
    'amazon_store_item_reviews_panel' => array(
      'variables' => array('item' => NULL),
      'template' => 'amazon_store_item_reviews_panel',
    ),
    'amazon_store_search_results_manufacturer' => array(
      'variables' => array('manufacturer' => NULL),
      'template' => 'amazon_store_search_results_manufacturer',
    ),
    'amazon_store_search_block' => array(
      'template' => 'amazon_store_search_block',
    ),
    'amazon_store_item_image' => array(
      'variables' => array('amazon_item' => NULL, 'size' => NULL),
      'template' => 'amazon_store_item_image',
    ),
    'amazon_store_item_offers' => array(
      'variables' => array('amazon_item' => NULL),
      'template' => 'amazon_store_item_offers',
    ),
    'amazon_store_categories' => array(
      'template' => 'amazon_store_categories',
    ),
    'amazon_store_link_button' => array(
      'variables' => array('text' => NULL, 'url' => NULL),
      'file' => 'amazon_store.pages.inc',
    ),

  );
}

/**
 * Implements hook_cron();().
 */
function amazon_store_cron() {
  // Clear data which has expired.
  // Normally data should expire within 24 hours, as that
  // is the Amazon AAWS requirement.
  cache_clear_all(NULL, 'cache_amazon_store');
  cache_clear_all(NULL, 'cache_amazon_store_searches');
}

/**
 * Implements hook_flush_caches();().
 *
 * When global cache clean is done (as from devel module)
 * the cache_amazon_store table will be truncated as well
 */
function amazon_store_flush_caches() {
  return array('cache_amazon_store', 'cache_amazon_store_searches');
}

/**
 * Implements hook_forms().
 *
 * Since we have pages with multiple forms of the same type on the
 * search results page and also on the cart page, we have to
 * tell the system where to find the processing function
 */
function amazon_store_forms($form_id) {
  $args = func_get_args();
  $forms = array();
  if (preg_match('/^amazon_store_addcart_form/', $form_id)) {
    $forms[$form_id] = array(
        'callback' => 'amazon_store_addcart_form',
        'callback arguments' => $args[1],
        'file' => 'amazon_store.pages.inc',
    );
  }
  if (preg_match('/^_amazon_store_cart_quantity_form/', $form_id)) {
    $forms[$form_id] = array(
      'callback' => '_amazon_store_cart_quantity_form',
      'callback arguments' => $args[1],
      'file' => 'amazon_store.pages.inc',
    );
  }
  if (preg_match('/^amazon_store_buttonize_link/', $form_id)) {
    $forms[$form_id] = array(
      'callback' => 'amazon_store_buttonize_link',
      'callback arguments' => $args[1],
      'file' => 'amazon_store.pages.inc',
    );
  }
  return $forms;
}


/**
 * Implement hook_ctools_include_directory().
 *
 * Tell ctools where to find plugins
 */

function amazon_store_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools' && !empty($plugin)) {
    return "ctools_plugins/$plugin";
  }
}


// TODO: Update to ctools.
/**
 * Implements hook_ctools_plugin_api().
 */
function amazon_store_ctools_plugin_api($module, $api) {
  if ($module == 'panels_mini' && $api == 'panels_default') {
    return array('version' => 1);
  }
  if ($module == 'page_manager' && $api == 'pages_default') {
    return array('version' => 1);
  }
}

// Template functions for theming

/**
 * This one overrides the amazon module's theming for amazon_item so that
 * the detail page url is local, using amazon_store instead of linking to amazon.com
 *
 * @param $variables
 * @return unknown_type
 */
function amazon_store_preprocess_amazon_item(&$variables) {
  $variables['detailpageurl'] = url(AMAZON_STORE_PATH . '/item/' . $variables['asin'], array('absolute' => TRUE));
}

function template_preprocess_amazon_store_all(&$variables) {
}

/**
 * Cache an amazon item - pass in SimpleXML item.
 *
 * Timeout is controlled by the module's admin settings.
 *
 * @param (SimpleXML) $xml
 *   A simplexml item.
 */
function amazon_store_cache_item($xml) {

  $cache_timeout = variable_get('amazon_store_refresh_schedule', 43200);
  $locale = variable_get('amazon_locale', 'US');

  // If you cache the object itself it can't be deserialized. http://drupal.org/node/199337
  // So we cache the XML instead
  cache_set("ASIN-{$xml->ASIN}-{$locale}", $xml->asXML(), 'cache_amazon_store', REQUEST_TIME + $cache_timeout);
}


/**
 * Try to get a product-description XML from the cache.
 * If it's not there, get it from Amazon
 *
 * @param $asin
 * 	Amazon.com ASIN identifier
 * @return
 * 	Item as SimpleXML object
 */
function amazon_store_retrieve_item($asin) {
  $locale = variable_get('amazon_locale', 'US');

  $item = cache_get("ASIN-{$asin}-{$locale}", 'cache_amazon_store');
  if ($item && $item->data) {
    $xml = new SimpleXMLElement($item->data);
  }
  else { // Item was not in cache
    $xml = amazon_store_get_item_data($asin);
  }
  return $xml;
}

/**
 * Cache an amazon search; alphabetize the params and use that
 * as key.
 *
 * @param  $parameters
 *  Parameters as available per Amazon docs
 * @param  $results
 * 	SimpleXML object with the search
 */
function amazon_store_cache_search($parameters, $results) {
  $cache_timeout = variable_get('amazon_store_refresh_schedule', 43200);
  $locale = variable_get('amazon_locale', 'US');

  ksort($parameters);
  $key = "Search-$locale-" . implode('/', array_keys($parameters)) . implode('/', $parameters);
  cache_set($key, $results->asXML(), 'cache_amazon_store_searches', REQUEST_TIME + $cache_timeout);
}

/**
 * Get a cached Amazon search and instantiate as SimpleXML obj
 *
 * @param  $parameters
 * @return
 * 	SimpleXML object or NULL if not found in cache
 */
function amazon_store_retrieve_cached_search($parameters) {
  $locale = variable_get('amazon_locale', 'US');

  $xml = NULL;
  ksort($parameters);
  $key = "Search-$locale-" . implode('/', array_keys($parameters)) . implode('/', $parameters);

  $item = cache_get($key, 'cache_amazon_store_searches');
  if ($item && $item->data) {
    $xml = new SimpleXMLElement($item->data);
  }
  return $xml;
}


function parametersForKeywordSearch($keywords) {
  return array(
  'SearchIndex' => 'All',
  'Keywords' => htmlentities("\"$keywords\""),
  'Operation' => 'ItemSearch',
  );
}



/**
 * Add an item to cart by ASIN (default) or OfferListingId (much better)
 * @param $item_id  The item ID - either an ASIN or and OfferListingId
 * @param $id_type  Type: Either "ASIN" or "OfferListingId"
 * @param $quantity - Number to add
 * @param $norecurse - If TRUE, do not call this again
 * @return TRUE on success
 */
function _amazon_store_add_to_cart($asin, $offerListingId = NULL, $quantity = 1, $norecurse = FALSE) {

  $cart_creds = amazon_store_get_cart_creds();
  $add_items = array(
    'Item.1.Quantity' => $quantity,
  );
  if (!empty($offerListingId)) {
    $add_items['Item.1.OfferListingId'] = $offerListingId;
  }
  else {
    $add_items['Item.1.ASIN'] = $asin;
  }

  $items = array();
  if ($cart_creds) { // If we already have a cart, try this.
    $results = amazon_store_http_request('CartAdd', $cart_creds + $add_items);
    if (empty($results->Cart->Request->Errors)) {
      return TRUE;
    }
    switch ((string) $results->Cart->Request->Errors->Error->Code) {
      case 'AWS.ECommerceService.ItemAlreadyInCart':
        $cart_item_id = $results->Cart->CartItems->CartItem[0]->CartItemId;
        $requested_quantity = (int)$results->Cart->CartItems->CartItem[0]->Quantity;
        amazon_store_update_cart_quantity((string)$cart_item_id, $requested_quantity);
        return TRUE;
      case 'AWS.ECommerceService.CartInfoMismatch':
        // Probably the sponsored cause, and thus the associate id, has changed
        watchdog('amazon', "Failed adding item to cart but will now create new cart - code=%code", array('%code' => (string) $results->Request->Errors->Error->Code));
        // Fall through and create new cart
        break;
      default:
        drupal_set_message(t('Failed adding item to cart.'), 'error');
        watchdog('amazon', "Failed adding item to cart - code=%code", array('%code' => (string) $results->Cart->Request->Errors->Error->Code));
        return FALSE;
    }
  }

  // If CartAdd fails and falls through from above
  //    Do CartCreate with cart_id
  //    Get new cart_id and update user with it
  // In all cases, log resultsresults->
  // In Initial stages, we should probably show the cart after doing an add
  $results = amazon_store_http_request('CartCreate', $add_items);
  if ($results->error) {
    drupal_set_message(t('Failed to create cart.'), 'error');
    watchdog("amazon", "Failed to create cart");
  }
  elseif ($results->Cart->Request->IsValid == 'True') {
    amazon_store_set_cart_creds((string) $results->Cart->CartId, (string) $results->Cart->HMAC);
    return TRUE;
  }

  return FALSE;
}

function amazon_store_get_current_cart_quantity($cartItemId) {
  if ($cart_creds = amazon_store_get_cart_creds()) {;
    $cart_items = array(
      'Item.1.CartItemId' => $cartItemId,
    );
    $results = amazon_store_http_request('CartGet', $cart_creds + $cart_items);
    $current_quantity = (int) $results->Cart->CartItems->CartItem[0]->Quantity;
    return $current_quantity;
  }
  return FALSE;
}

/**
 * Update quantity on a cart item
 *
 * @param  $cartItemId
 * 	CartItemId
 * @param  $quantity
 * @param  $item_number
 * @return
 * 	TRUE on success
 */
function amazon_store_update_cart_quantity($cartItemId, $quantity) {
  if ($cart_creds = amazon_store_get_cart_creds()) {
    $cart_items = array(
      'Item.1.CartItemId' => $cartItemId,
      'Item.1.Quantity' => $quantity,
    );

    $results = amazon_store_http_request('CartModify', $cart_creds + $cart_items);

    if (empty($results->error)) {
    	// watchdog(dprint_r($results);
      if (isset($results->Cart->CartItems)) {
  	    foreach ($results->Cart->CartItems->CartItem as $cartitem) {
  	      if ($cartitem->CartItemId == $cartItemId) {
  	        $actual_quantity = $quantity > 0 ? (int)$cartitem->Quantity : $quantity;
  	      }
  	    }
      }
      if ($quantity <= 0) {
        drupal_set_message(t('Item removed from cart'));
      }
      else {
        drupal_set_message(t("Cart quantity updated to @quantity", array('@quantity' => $actual_quantity)));
      }
      return TRUE;
    }
    else {
      drupal_set_message(t('Failed to update cart quantity'), 'error');
      amazon_store_report_error('Failed to update cart quantity.', $results->Cart->Request->Errors);
      return FALSE;
    }
  }
  return FALSE;
}

// Utility functions   ///////

/**
 * Return the cart ID and HMAC from session variable
 *
 * @param $cart_id
 * @param $cart_HMAC
 * @deprecated
 */
function amazon_store_get_cart_info(&$cart_id, &$cart_HMAC) {
	if ($cart_creds = amazon_store_get_cart_creds()) {
	  $cart_id = $cart_creds['CartId'];
	  $cart_HMAC = $cart_creds['HMAC'];
	}
}

/**
 * Return cart credentials (cart ID and HMAC) from the session variable
 *
 * @return
 *   Array containing the cart_id and the HMAC, if set. Otherwise FALSE.
 */
function amazon_store_get_cart_creds() {
  if (!empty($_SESSION['cart_id']) && !empty($_SESSION['HMAC'])) {
    return array(
      'CartId' => $_SESSION['cart_id'],
      'HMAC' => $_SESSION['HMAC'],
    );
  }
  return FALSE;
}

/**
 * Set cart credentials (cart ID and HMAC) in the session variable
 *
 * @param $cart_id
 * @param $cart_HMAC
 * @deprecated
 */
function amazon_store_set_cart_info($cart_id, $cart_HMAC) {
  amazon_store_set_cart_creds($cart_id, $cart_HMAC);
}

/**
 * Set cart credentials (cart ID and HMAC) in the session variable
 *
 * @param $cart_id
 * @param $cart_HMAC
 */
// TODO: Isolate amazon info in sessions
function amazon_store_set_cart_creds($cart_id, $cart_HMAC) {
  $_SESSION['cart_id'] = $cart_id;
  $_SESSION['HMAC'] = $cart_HMAC;
}

/**
 * Utility function for reporting errors as reported in the
 * errors portion of SimpleXML Amazon item object.
 *
 * @param $message Custom error message
 * @param $errors SimpleXML object containing errors
 */
function amazon_store_report_error($message = 'Amazon request failed.', $errors = NULL) {
  if ($errors) {
    $message .= " Amazon's response was: <br /><ul>";
    $msglines = array();
    $i = 0;
    foreach ($errors->Error as $error) {
      $i++;
      $message .= "<li>%msg$i</li>";
      $msglines["%msg$i"] = "[{$error->Code}]: {$error->Message}";
    }
    $message .= '</ul>';
  }
  watchdog('amazon_store', $message, $msglines);
}

/**
 * Basic Amazon search form.
 */
function amazon_store_search_form($form, &$form_state, $keyword_width = 15) {

  $moduledir = drupal_get_path('module', 'amazon_store');
  $form['#attached']['css'] = array("$moduledir/amazon_store.css");
  $form['#attached']['js'] = array("$moduledir/amazon_store.js");

  if (variable_get('amazon_store_show_category_select', TRUE)) {
    $form['SearchIndex'] = array(
        '#title' => t('Category'),
        '#type' => 'select',
        '#options' => $GLOBALS['amazon_store_search_indexes']->getSearchIndexPulldown(),
        '#default_value' => !empty($_GET['SearchIndex']) ? $_GET['SearchIndex'] : variable_get('amazon_store_search_index_choice', 'All'),
    );
  }
  else {
    $form['SearchIndex'] = array(
      '#type' => 'value',
      '#value' => variable_get('amazon_store_search_index_choice', 'All'),
    );
  }
  $form['Keywords'] = array(
      '#title' => t('Search For'),
      '#type' => 'textfield',
      '#size' => $keyword_width,
      '#default_value' => isset($_GET['Keywords']) ? $_GET['Keywords'] : "",
  );
  $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Search'),
  );
  $form['clear'] = array(
    '#type' => 'markup',
    '#markup' => "<div class='search-form-clear'></div>",
  );
  return $form;

}

function amazon_store_search_form_submit($form, &$form_state) {
  $form_state['redirect'] = array(AMAZON_STORE_PATH, array('query' => array('Keywords' => $form_state['values']['Keywords'], 'SearchIndex' => $form_state['values']['SearchIndex'])));
}

/**
 * Do an xpath search on passed-in SimpleXML object, returning the results
 * This takes care of namespace issues
 *
 * The query does have to have the "a" prefix. Example query:
 *   $errors = amazon_store_xpath($someSimpleXML,'//a:Error');

 *
 * @param  $xml
 * 	SimpleXML object on which to perform search
 * @param  $query
 * 	Xpath query
 * @return results of query as Xpath object
 */
function amazon_store_xpath($xml, $query) {
  // Amazon uses default namespace, so need to fiddle with xpath.
  // Description of this comes from http://us2.php.net/manual/en/function.simplexml-element-xpath.php#87141
  $namespaces = $xml->getNamespaces(true);
  if (isset($namespaces[""])) {
    $xml->registerXPathNamespace("a", $namespaces[""]);
  }
  $result = $xml->xpath($query);
  return $result;
}

 /**
 * Determine whether to report an Amazon API "Error" or not.
 *
 * Not all errors should actually be reported. Some are not real failures.
 *
 * @param $code
 *   (string) The code to be checked.
 */
function amazon_store_error_is_reportable($code) {
  $non_reportable = array('AWS.ECommerceService.ItemAlreadyInCart');
  if (in_array($code, $non_reportable)) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Wrapper on amazon_http_request.
 * Checks for errors on all requests and logs them.
 *
 * @param $op
 * 	Operation to perform: ItemSearch, CartAdd, etc.
 * @param $parameters
 * 	Array of parameters for the request
 * @return SimpleXML object (or NULL) and  $result->error set if there was an error detecdted
 */
function amazon_store_http_request($op, $parameters) {
  if (AS_DEBUG) {
    drupal_set_message("Request: op=$op, Parameters=" . print_r($parameters, TRUE));
  }
  $results = amazon_http_request($op, $parameters);
  if (empty($results)) {
    watchdog('amazon_store', 'Failed call to amazon_http_request; op=%op, parameters = %parameters', array('%op' => $op, '%parameters' => print_r($parameters, TRUE)));
    drupal_set_message(t("amazon_http_request failed; op=@op, parameters=@parameters.", array('@op' => $op, '@parameters' => print_r($parameters, TRUE))), 'error');
    return NULL;
  }

  $errors = amazon_store_xpath($results, '//a:Error');
  foreach ($errors as $error) {
    if (amazon_store_error_is_reportable((string)$error->Code)) {
      $msg = "Amazon error returned. Code=$error->Code}, Message={$error->Message} //";
      $error_set = TRUE;
    }
  }

  if (isset($error_set)) {
    $errors = "";
    watchdog('amazon', 'There was an error accessing amazon. Message=@msg, results=@results', array('@msg' => $msg, '@results' => print_r($results, TRUE)));
    $results->error = $msg;
  }
  return ($results);
}

/**
 * Perform the search on AAWS
 *
 * @param  $parameters
 * 	Array of parameters, as defined by AAWS
 * @return
 * 	SimpleXML result with search results
 */
function _amazon_store_search($parameters = array()) {
  $locale = variable_get('amazon_locale', 'US');
  $response_groups = "Variations,Images,ItemAttributes,OfferFull";
  if ($parameters['SearchIndex'] != 'Apparel') {
    $response_groups .= ',EditorialReview,Similarities,AlternateVersions,Large';
  }
  if ($locale == 'US') { // SearchBins not currently supported outside US
    $response_groups .= ',SearchBins';
  }
  $parameters['ResponseGroup'] = $response_groups;

  // MerchantId is now either Amazon or nothing; 10/2011.
  if (empty($parameters['MerchantId']) && (variable_get('amazon_store_merchant_id', 'Amazon') == 'Amazon')) {
    $parameters['MerchantId'] = 'Amazon';
  }
  $parameters['Condition'] = 'New';

  if (!($results = amazon_store_retrieve_cached_search($parameters))) {
    $results = amazon_store_http_request('ItemSearch', $parameters);
    if (empty($results->error) && !empty($results->Items->Item)) {
      amazon_store_cache_search($parameters, $results);
      foreach ($results->Items->Item as $item) {
        amazon_store_cache_item($item);
      }
    }
  }

  return $results->Items;
}

/**
 * Just like search, but takes a comma-separated item list of ASINs instead
 * @param $itemlist
 *   Parameters to pass. Normal would be array('ItemId' => 'asin1,asin2,...')
 * @return unknown_type
 */
function _amazon_store_itemlist($parameters = array()) {
  $parameters['ResponseGroup'] = 'Variations,ItemAttributes,Images,EditorialReview,OfferFull,Reviews,Similarities,AlternateVersions,Large';

  if (! ($results = amazon_store_retrieve_cached_search($parameters))) {
    $results = amazon_store_http_request('ItemLookup', $parameters);
    if (empty($results->error) && !empty($results->Items->Item)) {
      amazon_store_cache_search($parameters, $results);
      foreach ($results->Items->Item as $item) {
        amazon_store_cache_item($item);
      }
    }
  }

  return $results->Items;
}

/**
 * Request info about a single item from amazon
 *
 * @param unknown_type $asin
 * @return unknown
 */
function amazon_store_get_item_data($asin) {
  $parameters = array(
      'ResponseGroup' => 'Variations,Images,ItemAttributes,OfferFull,Large,EditorialReview',
  'ItemId' => $asin,
  );

  $items = array();
  $results = amazon_store_http_request('ItemLookup', $parameters);
  $amazon_item = $results->Items->Item[0];
  if (!empty($amazon_item)) {
    amazon_store_cache_item($amazon_item);
  }
  return $amazon_item;
}

/**
 * Clear a single cached item from the cache, identified by ASIN
 *
 * @param unknown_type $asin
 */
function amazon_store_clear_cache($asin) {
  if (!empty($asin)) {
    cache_clear_all("ASIN-" . $asin, 'cache_amazon_store');
  }
  drupal_goto();
}

/**
 * Map product group to SearchIndex so we can browse a SearchIndex based on product group
 *
 * @param unknown_type $ProductGroup
 * @return SearchIndex
 * 	Returns the most likely SearchIndex for this productGroup
 */
function ProductGroup2SearchIndex($ProductGroup) {
  /**
   * This array was made by a download of product group to SearchIndex membership
   * on browsenode.com. Then I made arbitrary decisions about the single
   * primary SearchIndex that a ProductGroup would imply, since the mapping
   * was many to many.
   */

  static $map = array(
    "Amazon Devices" => "Electronics",
    "Apparel" => "Apparel",
    "Audible" => "Books",
    "Automotive Parts and Accessories" => "Automotive",
    "Automotive Parts and Accessories" => "Tools",
    "Baby Product" => "Baby",
    "Beauty" => "Beauty",
    "BISS" => "All",
    "Book" => "Books",
    "CE" => "Electronics",
    "Classical" => "Classical",
    "Digital Music Album" => "MP3Downloads",
    "Digital Music Artist" => "MP3Downloads",
    "Digital Music Track" => "MP3Downloads",
    "Digital Text Feeds" => "Books",
    "DVD" => "DVD",
    "eBooks" => "Books",
    "Furniture" => "OfficeProducts",
    "Gourmet" => "GourmetFood",
    "Grocery" => "Grocery",
    "Health and Beauty" => "HealthPersonalCare",
    "Home" => "Tools",
    "Home Improvement" => "HomeGarden",
    "Jewelry" => "Jewelry",
    "Kitchen" => "Kitchen",
    "Lawn & Patio" => "HomeGarden",
    "Loose Diamonds" => "Jewelry",
    "Magazine" => "Magazines",
    "Movie" => "UnboxVideo",
    "Music" => "Music",
    "Musical Instruments" => "MusicalInstruments",
    "Office Product" => "OfficeProducts",
    "Personal Computer" => "PCHardware",
    "Pet Products" => "PetSupplies",
    "Photography" => "Photo",
    "Shoes" => "Shoes",
    "Single Detail Page Misc" => "All",
    "Software" => "Software",
    "Sports" => "SportingGoods",
    "Toy" => "Toys",
    "TV Series Episode Video on Demand" => "UnboxVideo",
    "TV Series Video on Demand" => "UnboxVideo",
    "Video" => "DVD",
    "Video Games" => "VideoGames",
    "Watch" => "Jewelry",
    "Wireless" => "Wireless",
  );
  $SearchIndex = $map[$ProductGroup];
  if (!empty($SearchIndex)) {
    return $SearchIndex;
  }
  return "All";

}

/**
 * Update the query parameters based on passed-in parameters.
 *
 * @param $newstuff
 *   Associative Array of Amazon parameters in URL to be changed
 *
 * @return new array of query params
 */
function _amazon_store_revise_query_parameters($updated_params = array()) {
  $newget = $_GET;
  unset($newget['page'], $newget['q']); // If we're rewriting the query, the page is not going to be relevant
  if (empty($_GET['SearchIndex'])) {
    $newget['SearchIndex'] = variable_get('amazon_store_default_search_index', 'All');
  }
  foreach ($updated_params as $key => $value) {
    $newget[$key] = $value;
  }
  return $newget;
}

/**
 * Create a directed graph (tree) of all the possible variations
 * For example, for a pants entry with size,color, it might have
 * 	30x29
 *     -> gray
 * 			-> asin1
 * 			-> asin2
 * 		-> red
 * 			-> asin3
 *  30x30
 * 		-> gray
 * 			-> asin4
 * 		-> green
 * 			-> asin5
 *
 * @param $item
 * 		Amazon Product, in simplexml object
 * @return
 * 		Array representing all possible attribute paths
 */
function _amazon_store_process_variations($item) {
  static $savedItem;
  static $itemsByAttribute = array();
  if ($savedItem == (string) $item->ASIN && !empty($itemsByAttribute)) {
    return $itemsByAttribute;
  }
  $savedItem = (string) $item->ASIN;
  $itemsByAttribute = array();

  $dimensions = _amazon_store_get_variation_dimensions($item);
  if (!empty($item->Variations) && !empty($item->Variations->Item)) {
    // Iterate the listed dimensions for the product
    // In each dimension, iterate through the Items (sub-asins) listed in this parent asin
    foreach ($item->Variations->Item as $variation) {
      // For each sub-asin, record the values of its dimensions
      $curPtr = & $itemsByAttribute;
      foreach ($dimensions as $otherDimension) {
	foreach ($variation->VariationAttributes->VariationAttribute as $attribute) {
	  if ($attribute->Name == $otherDimension) {
	    $attr = (string) $attribute->Value;
	  }
	}
        if (empty($curPtr[$attr])) {
          $curPtr[$attr] = array();
        }
        $curPtr = & $curPtr[$attr];
      }
      _amazon_store_iterate_offers($variation->Offers, $variation, $curPtr);
    }
  }
  else { // We didn't have variations, so just do the offers
    $curPtr = & $itemsByAttribute;

    _amazon_store_iterate_offers($item->Offers, $item, $curPtr);
  }
  return $itemsByAttribute;
}

/**
 * Populate offers into the passed-in variations structure
 *
 * @param unknown_type $offers
 * @param unknown_type $itemPtr
 * @param unknown_type $curPtr
 */
function _amazon_store_iterate_offers($offers, $itemPtr, &$curPtr) {
  // Return if we have no offers
  if (!is_object($offers) || count((array) $offers->Offer) < 1) {
    return;
  }
  foreach ($offers->Offer as $offer) {
    $asin = (string) $itemPtr->ASIN;
    $offerListingId = (string) $offer->OfferListing->OfferListingId;
    $curPtr[$asin][$offerListingId]['availability'] = (string) $offer->OfferListing->Availability;
    $curPtr[$asin][$offerListingId]['price'] = (string) $offer->OfferListing->Price->FormattedPrice;
    if (!empty($offer->OfferListing->SalePrice)) {
      $curPtr[$asin][$offerListingId]['price'] = (string) $offer->OfferListing->SalePrice->FormattedPrice;
    }
    if (!empty($offer->Merchant)) {
      $curPtr[$asin][$offerListingId]['merchantname'] = (string) $offer->Merchant->Name;

    }
    else {
      $curPtr[$asin][$offerListingId]['merchantname'] = (string) $offer->Seller->Nickname;

    }

    // Now for some debugging info
    if (AS_DEBUG) {
      if ((string) $offer->Merchant->MerchantId == "ATVPDKIKX0DER") {
        $GLOBALS['amazon_offers'][(string) $itemPtr->ItemAttributes->ClothingSize][(string) $itemPtr->ItemAttributes->Color]['offer'][] = $offerListingId;
        $GLOBALS['amazon_offers'][(string) $itemPtr->ItemAttributes->ClothingSize][(string) $itemPtr->ItemAttributes->Color]['ptr'][] = $offer;
      }
    }
  }
}

/**
 * Return an array of dimensions (such as ClothingSize, Color)
 * based on metadata in the amazon item
 *
 * @param unknown_type $item
 * 	Amazon product information as SimpleXML object
 * @return
 * 	Simple array of dimension names
 */
function _amazon_store_get_variation_dimensions($item) {
  $dimensions = array();
  if (!empty($item->Variations->VariationDimensions)) {
    foreach ($item->Variations->VariationDimensions->VariationDimension as $dimension) {
      $dimensions[] = (string) $dimension;
    }
  }
  return $dimensions;
}

/**
 * AJAX callback handler for the add-to-cart update.
 *
 * This one handles the replacement of the select
 * widgets only, not the submission.
 *
 */
function _amazon_store_addcart_callback($form, &$form_state) {
  $commands[] = ajax_command_replace(NULL, drupal_render($form));
  $commands[] = ajax_command_append(NULL, theme('status_messages'));

  return array('#type' => 'ajax', '#commands' => $commands);
}


/**
 * Return the SearchIndex as entered in the URL of this page
 *
 * @return
 * 	Currently-selected search index
 */
function _amazon_store_get_search_index() {
  return empty($_GET['SearchIndex']) ? "All" : $_GET['SearchIndex'];
  return $searchIndex;
}

/**
 * Return the SortOrder as in the URL of this page,
 * or NULL if none.
 *
 * @return unknown
 */
function _amazon_store_get_sort() {
  $sort = NULL;
  if (!empty($_GET['Sort'])) {
    $sort = $_GET['Sort'];
  }
  return $sort;
}


/**
 * Change sort order for search results page
 *
 * @param unknown_type $form_state
 * @return
 * 	Form for changing sort order
 */
function amazon_store_sort_form($form, &$form_state) {

  $form['#tree'] = TRUE;
  $moduledir = drupal_get_path('module', 'amazon_store');
  $form['#attached']['css'] = array("$moduledir/amazon_store.css");
  $form['#attached']['js'] = array("$moduledir/amazon_store.js");

  $searchIndex = _amazon_store_get_search_index();
  if ($searchIndex == "All") {
    return NULL; // No sorting for "All"
  }
  $options = $GLOBALS['amazon_store_search_indexes']->getSortPossiblities($searchIndex);

  $form['sortby'] = array(
    '#title' => t('Sort Results By'),
    '#type' => 'select',
    '#options' => $options,
    '#attributes' => array('class' => array('amazon-store-autosubmit')),
    '#default_value' =>  _amazon_store_get_sort(),
    // '#attributes' => array('onchange' => 'dest=this.options[this.options.selectedIndex].value; window.location = dest;'),
  );
  $form['sortby-submit'] = array(
    '#type' => 'submit',
    '#value' => t('Sort'),
    '#name' => t('Sort'),
    '#attributes' => array('class' => array('amazon-store-no-js')),
  );
  $form['clear'] = array(
    '#type' => 'markup',
    '#markup' => "<div class='search-form-clear'></div>",
  );
  return $form;
}

function amazon_store_sort_form_submit($form, &$form_state) {
  $new_params = _amazon_store_revise_query_parameters(array('Sort' => $form_state['values']['sortby']));
  $form_state['redirect'] = array(AMAZON_STORE_PATH, array('query' => $new_params));
}

/**
 * SearchBinSets selector form, for search results page
 *
 * @param  $form_state
 * @param  $searchBinSets SimpleXML object from search results
 * @return
 * 	a form
 */
function amazon_store_searchbin_sets_form($form, &$form_state, $searchBinSets) {
  $form = array(
    '#tree' => TRUE,
    '#title' => t('Narrow your search'),
  );
  $moduledir = drupal_get_path('module', 'amazon_store');
  $form['#attached']['css'] = array("$moduledir/amazon_store.css");
  $form['#attached']['js'] = array("$moduledir/amazon_store.js");

  $form['searchbins'] = array(
    '#type' => 'fieldset',
    '#title' => t('Narrow your search'),
  );

  // For each search bin set, create a select control and a submit button
  foreach ($searchBinSets->SearchBinSet as $binset) {
    $narrowBy = (string) $binset->attributes()->NarrowBy;

    $form['searchbins'][$narrowBy]['select'] = array(
      '#type' => 'select',
      '#attributes' => array('class' => array('amazon-store-autosubmit')),
      '#options' => array('' => t('Narrow by') . ' ' . $narrowBy),
    );
    $form['searchbins'][$narrowBy]['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Narrow by') . $narrowBy,
      '#name' => $narrowBy,
      '#submit' => array('amazon_store_searchbin_sets_form_submit'),
      '#attributes' => array('class' => array('amazon-store-no-js')),
    );

    // For each "bin", which is like a price category or brand,
    // Add an option to the select control.
    // The option key is constructed as 'key=value;key2=value2;key3=value3';
    // In the case of the price ranges it can have more than one key.
    foreach ($binset->Bin as $bin) {
      $parameter_string = "";
      foreach ($bin->BinParameter as $parameter) {
        $parameter_string .= $parameter->Name . '=' . (string)$parameter->Value . ';';
      }
      $form['searchbins'][$narrowBy]['select']['#options'][$parameter_string] = (string)$bin->BinName;
    }
  }
  $form['clear'] = array(
    '#type' => 'markup',
    '#markup' => "<div class='search-form-clear'></div>",
  );

  return $form;
}

/**
 * Submit the searchbinsets_form.
 *
 * This actually just redirects to a URL that describes the search.
 */
function amazon_store_searchbin_sets_form_submit($form, &$form_state) {
  foreach(element_children($form['searchbins']) as $child) {
    // Break up our key, which is in the form 'key1=value1;key2=value2;'.
    if (!empty($form_state['values']['searchbins'][$child]['select'])) {
      $new_strings = explode(';', $form_state['values']['searchbins'][$child]['select']);
      foreach ($new_strings as $string) {
        if (empty($string)) {
          continue;
        }
        $additions = explode('=', $string);
        $changes[$additions[0]] = $additions[1];
      }
    }
  }
  $new_params = _amazon_store_revise_query_parameters($changes);
  $form_state['redirect'] = array(AMAZON_STORE_PATH, array('query' => $new_params));
}

/**
 * Determine if a given parameter is allowed for given SearchIndex.
 *
 * @param $SearchIndex
 *   The SearchIndex to test. Example: Apparel or Books
 * @param $parameter_name
 *   Parameter like 'Author' or  'Manufacturer'
 */
function amazon_store_allowed_parameter($SearchIndex, $parameter_name) {
  global $amazon_store_search_indexes;
  $parametersAllowed = $amazon_store_search_indexes->getParametersAllowed();
  return in_array($parameter_name, $parametersAllowed[$SearchIndex]);
}



/**
 * Compute pager options and invoke theme pager.
 *
 * @param int $searchResults
 *   The actual Amazon search results, as simpleXML object.
 * @param int $pager_element
 *   An optional integer to distinguish between multiple pagers on one page.
 *
 * @return string
 *   The pager as HTML.
 */
function amazon_store_search_results_pager($searchResults, $pager_element = 0) {
  global $pager_page_array, $pager_total_items, $pager_total;

  $pager_page_array = array(0);
  if (!empty($_GET['page'])) {
    $pager_page_array = explode(',', $_GET['page']);
  }
  $results_per_page = 10; // Amazon standard
  $total_results = $searchResults->TotalResults;
  $pager_total_items[$pager_element] = $total_results;
  $pager_total[$pager_element] = ceil($total_results / $results_per_page);
  $pager_page_array[$pager_element] = max(0, min((int)$pager_page_array[$pager_element], ((int)$pager_total[$pager_element]) - 1));

  // 10 items per page is wired in from Amazon API.
  return theme('pager', array('tags' => array(), 'element' => $pager_element));
}

/**
 * Helper functions for amazon_store_details_panel.php.
 */
function amazon_store_manufacturer_format($attributeType, $attributeValue) {
  return theme('amazon_store_search_results_manufacturer', array('manufacturer' => (string)$attributeValue));
}

/**
 * Format a participant detail.
 *
 * This works for author, artist, composer. It guesses the SearchIndex from
 * the ProductGroup. There may be more than one author, so it returns an
 * unordered list in that case.
 *
 * @param $attributeType
 *   'Author' or 'Composer', etc.
 * @param $attributeValue
 *   An array of author/composer/artist names
 * @param $allAttributes
 *   The Attributes section from the original XML.
 */
function amazon_store_participant_format($attributeType, $attributeValue, $allAttributes) {
  $search_index = ProductGroup2SearchIndex((string)$allAttributes->ProductGroup);
  $output = "";
  $multi = FALSE;
  if (count($attributeValue) > 1) {
    $multi = TRUE;
  }

  foreach ($attributeValue as $value) {
    $link = l((string)$value, AMAZON_STORE_PATH, array('attributes' => array('rel' => 'nofollow'), 'query'=> array((string)$attributeType => (string)$value, 'SearchIndex' => $search_index)));
    if ($multi) {
      $link = "<li>$link</li>";
    }
    $output .= $link;
  }
  if ($multi) {
    $output = '<ul>' . $output . '</ul>';
  }
  return $output;
}

function amazon_store_feature_format($attributeType, $attributeValue, $allAttributes) {
  $output = "<ul>";
  foreach ($attributeValue as $feature) {
    // Strip items that contain links, which will be unuseful.
    if (!preg_match("/href=/i", $feature)) {
      $output .= "<li>" . check_plain($feature) . "</li>";
    }
  }
  $output .= "</ul>";
  return $output;
}


function amazon_store_binding_format($attributeType, $attributeValue, $allAttributes) {
  $output = (string)$attributeValue;
  if (!empty($allAttributes->NumberOfPages)) {
    $output .= ", {$allAttributes->NumberOfPages} pages";
  }
  return $output;
}

function amazon_store_dimensions_format($attributeType, $attributeValue, $allAttributes) {
  $output = "<ul>";
  if ($attributeValue->Height) {
    $output .= "<li>Dimensions: {$attributeValue->Length}L x {$attributeValue->Width}W x {$attributeValue->Height}H</li>";
  }
  if ($attributeValue->Weight) {
    $output .= "<li>Weight: $attributeValue->Weight</li>";
  }
  $output .= "</ul>";
  return $output;
}

/**
 * Format an attribute based on various handlers.
 *
 * @param $attribute_value
 * @param $handler
 * @param $allAttributes
 */
function amazon_store_format_attribute($attributeType, $attribute_value, $handler, $allAttributes) {
  $output = "{$handler['name']}: ";
  if (!empty($handler['outputElement'])) {
    $output .= $attribute_value->{$handler['outputElement']};
  }
  elseif (!empty($handler['handler'])) {
    $output .= $handler['handler']($attributeType, $attribute_value, $allAttributes);
  }
  else {
    $output .= (string)$attribute_value;
  }
  return $output;
}

/**
 * Return the store path when other modules need to know it.
 */
function amazon_store_get_amazon_store_path() {
  return variable_get('amazon_store_path', 'amazon_store');
}
