/**
 * Called if the Titles and/or ImageURLs of any sale should be
 * updated. e.g. directly after the page has been loaded or
 * after a vote was finished.  
 */ 
function setAllSaleTitlesAndImages(pSalesAsJSON)
{
  // validity check:
  if (!pSalesAsJSON)
  {
    $('debug_div').innerHTML += "100: "+pSalesAsJSON+"<br>";
    return;
  }
  var hash = $H(pSalesAsJSON);
  if (!hash)
  {
    $('debug_div').innerHTML += "101: "+pSalesAsJSON+"<br>"; 
    return;
  }
  
  // iterate over all provided ToplevelKategories:
  hash.each(function(pair)
  {
    var saleId = pair.key;
    var values = pair.value;
    var tlc = values.TLC;
    //$('debug_div').innerHTML += "tlc: "+tlc+"; tlcValues: "+tlcValues.Title+"<br>";
    
    // Title:
    $(tlc+"_2_title").innerHTML = values.Title;
    $(tlc+"_3_title").innerHTML = values.Title;
    $(tlc+"_4_title").innerHTML = values.Title;
    
    // SaleID:
    $(tlc+"_2_sale_id").innerHTML = saleId;
    $(tlc+"_3_sale_id").innerHTML = saleId;
    $(tlc+"_4_sale_id").innerHTML = saleId;
    
    // Subtitle:
    $(tlc+"_2_subtitle").innerHTML = values.SubTitle;
    $(tlc+"_3_subtitle").innerHTML = values.SubTitle;
    $(tlc+"_4_subtitle").innerHTML = values.SubTitle;
    
    // Short-Description:
    $(tlc+"_2_description").innerHTML = values.DescriptionText;
    $(tlc+"_3_description").innerHTML = values.DescriptionText;
    
    // Image-URLs:
    $(tlc+"_2_img").src = values.ImageUrl;
    $(tlc+"_3_img").src = values.ImageUrl;
    $(tlc+"_4_img").src = values.ImageUrl;
    
    // UVPs:
    $(tlc+"_2_uvp").innerHTML = values.UVP;
    
    // BuyNow-Price:
    $(tlc+"_2_buy_now_price").innerHTML = values.BuyNowPrice;
    
    // MinPrices:
    $(tlc+"_2_min_price").innerHTML = values.CooniMinPrice;
    $(tlc+"_3_min_price").innerHTML = values.CooniMinPrice;
    $(tlc+"_4_min_price").innerHTML = values.CooniMinPrice;
    
    // StartPrices
    //$(tlc+"_2_start_price").innerHTML = values.StartPrice;
    //$(tlc+"_3_start_price").innerHTML = values.StartPrice;
    //$(tlc+"_4_start_price").innerHTML = values.StartPrice;

    // display stopper-image (if existent):
    if (values.StopperImageURL.length>0)
    {
      $(tlc+"_2_stopper_img").src = values.StopperImageURL;
      $(tlc+"_3_stopper_img").src = values.StopperImageURL;
      $(tlc+"_2_stopper_img_parent").show();
      $(tlc+"_3_stopper_img_parent").show();
    }
    
    // Delivery-cost:
    $(tlc+"_2_delivery_cost").innerHTML = values.DeliveryCost;
    $(tlc+"_3_delivery_cost").innerHTML = values.DeliveryCost;
    $(tlc+"_4_delivery_cost").innerHTML = values.DeliveryCost;
    
    // partner-text and partner-images:
    if (values.PartnerText.length>=3 && values.PartnerText.substr(0,3)=="www")
    {
      $(tlc+"_2_partner_txt").innerHTML = "<a href='http://"+values.PartnerText+"' target='_new'>"+values.PartnerText+"</a>"; // misuse of PartnerText als Link-URL
      $(tlc+"_3_partner_txt").innerHTML = "<a href='http://"+values.PartnerText+"' target='_new'>"+values.PartnerText+"</a>"; // misuse of PartnerText als Link-URL
      $(tlc+"_4_partner_txt").innerHTML = "<a href='http://"+values.PartnerText+"' target='_new'>"+values.PartnerText+"</a>"; // misuse of PartnerText als Link-URL
    }
    else
    {
      $(tlc+"_2_partner_txt").innerHTML = values.PartnerText;
      $(tlc+"_3_partner_txt").innerHTML = values.PartnerText;
      $(tlc+"_4_partner_txt").innerHTML = values.PartnerText;
    }
    $(tlc+"_2_partner_img").src = values.PartnerImageUrl;
    $(tlc+"_3_partner_img").src = values.PartnerImageUrl;
    $(tlc+"_4_partner_img").src = values.PartnerImageUrl;
    
    // set dynamic email link: 
    //var subject = values.Title;
    //subject = subject.replace(/&uuml;/g, "ue");
    //subject = subject.replace(/&ouml;/g, "oe");
    //subject = subject.replace(/&auml;/g, "ae");
    //subject = subject.replace(/&Uuml;/g, "Ue");
    //subject = subject.replace(/&Ouml;/g, "Oe");
    //subject = subject.replace(/&Auml;/g, "Ae");
    //$(tlc+"_2_email").href = 'mailto:?subject='+encodeURI(subject)+'&body='+encodeURI('Hallo,\n\nschau mal, dieses Produkt habe ich gerade auf <a href="http://www.cooni.de/t/'+tlc+'/">www.cooni.de</a> gefunden.\n\nViele Gruesse');
    //$(tlc+"_3_email").href = 'mailto:?subject='+encodeURI(subject)+'&body='+encodeURI('Hallo,\n\nschau mal, dieses Produkt habe ich gerade auf <a href="http://www.cooni.de/t/'+tlc+'/">www.cooni.de</a> gefunden.\n\nViele Gruesse');
  });
}



/**
 * Methods to reset the charts:
 */  
function setAllChartTitlesAndImages(pProductsAsJSON)
{
  // validity check:
  if (!pProductsAsJSON)
  {
    $('debug_div').innerHTML += "200: "+pProductsAsJSON+"<br>";
    return;
  }
  var hash = $H(pProductsAsJSON);
  if (!hash)
  {
    $('debug_div').innerHTML += "201: "+pProductsAsJSON+"<br>"; 
    return;
  }

  // update internal representation of products is extended:
  mStoredProductsOnceDetail = new Object();
  if (mStoredProducts==null) return;
  hash.each(function(pair) {
    var productId = pair.key;
    var values = pair.value;
    
    mStoredProductsOnceDetail[productId] = {
      Title: values.Title,
      SubTitle: values.SubTitle,
      ImageUrl: values.ImageUrl,
      UVP: values.UVP,
      DeliveryCost: values.DeliveryCost,
      CooniMinPrice: values.CooniMinPrice
    }
  });
    
  reorderProductsAndDisplay();
}
function reorderProductsAndDisplay()
{
  mNextProjectionHandler = null;
  if (!mVotingAllowed) return; 
  
  // let the projection appear:
  $('product_charts_projection').fade( {duration: 0.5} );
  
  // make the loader invisible:
  $('product_charts_loader').fade( {duration: 1.0} );

  // reduce voting area in height
  new Effect.Morph('product_charts_content', {
    style: 'height:0px;',   // from 130
    duration: 1.0
  });

  window.setTimeout("_reorderProductsAndDisplay()", 1000);  
}
function _reorderProductsAndDisplay()
{
  // iterate over all provided products and build an ordered array:
  var productsHash = $H(mStoredProducts);
  var orderedProductArray = new Array();
  var numberProducts = 0;
  productsHash.each(function(pair) {
    numberProducts++;
  });
  
  for (var index=0; index<numberProducts; index++)
  {
    var maxEntry = {
      Votes:0,
      ID:0
    }
    productsHash.each(function(pair) {
      var productId = pair.key;
      var numVotes = pair.value.NumberVotes;
      var productId = pair.value.ProductId;
  
      // is the current product already sorted:
      var productNotYetOrdered = true;
      for (var i=0; i<orderedProductArray.length; i++) if (orderedProductArray[i]==productId) productNotYetOrdered = false;
      
      // else we look for the next highest ranking product:
      if (productNotYetOrdered && (numVotes>maxEntry.Votes || (numVotes==maxEntry.Votes && productId>maxEntry.ID)))
      {
        maxEntry.Votes = numVotes;
        maxEntry.ID = productId;
      }
    });
    orderedProductArray[index] = maxEntry.ID;
  }
  // sorting is finished -> begin to display..
  
  
  // remove all nodes in the chart-container:
  while ($('product_charts_vote_container').lastChild!=null)
    $('product_charts_vote_container').removeChild( $('product_charts_vote_container').lastChild );
  $('product_charts_vote_container').style.height = (orderedProductArray.length*55)+"px";

  for (var i=0; i<orderedProductArray.length; i++)  
  {
    var productId = orderedProductArray[i];
    var contentTitle = "";
    var contentImgUrl = "";
    var contentUVP = "";
    var contentMinPrice = "";
    if (mStoredProductsOnceDetail!=null && mStoredProductsOnceDetail[productId]!=null)
    {
      contentTitle = mStoredProductsOnceDetail[productId].Title;
      contentImgUrl = mStoredProductsOnceDetail[productId].ImageUrl;
      contentUVP = mStoredProductsOnceDetail[productId].UVP;
      contentMinPrice = mStoredProductsOnceDetail[productId].CooniMinPrice;
    }


    // build the template for the current voting row:
    // (i) row itself:
    var row = new Element('div', { 'class': 'cl_charts_row_'+((i%2==0)?'even':'odd') });
    row.id = "charts_row_"+productId;
    row.style.top = (i*55)+"px";
    $('product_charts_vote_container').appendChild(row);
    // (ii) rank:
    var col = new Element('div', { 'class': 'cl_charts_col_rank' });
    var rankImage = new Element('img', {  });
    rankImage.src = "http://img.cooni.de/img/v2/numbers/"+(i+1)+".png";
    col.appendChild(rankImage);
    row.appendChild(col);
    // (iii) image:
    col = new Element('div', { 'class': 'cl_charts_col_image' });
    var produktImageLink = new Element('a', {  });
    produktImageLink.href = "javascript:getProductDetailAJAXFromProductId('"+productId+"','product_charts_content')";
    var produktImage = new Element('img', { 'class': 'cl_charts_img' });
    produktImage.src = contentImgUrl;
    produktImageLink.appendChild(produktImage);
    col.appendChild(produktImageLink);
    row.appendChild(col);
    // (iv) title:
    col = new Element('div', { 'class': 'cl_charts_col_titel' });
    var titleLink = new Element('a', { 'class': 'cl_charts_title' });
    titleLink.href = "javascript:getProductDetailAJAXFromProductId('"+productId+"','product_charts_content')";
    titleLink.update(contentTitle);
    col.appendChild(titleLink);
    row.appendChild(col);
    // (v) prices:
    //don't display prices anymore:
    col = new Element('div', { 'class': 'cl_charts_col_prices' });
    var priceMin = new Element('span', { 'class': 'cl_charts_min_price' });
    priceMin.update("Tiefstpreis:<br>"+contentMinPrice+"&nbsp;&euro;<br>");
    var priceUVP = new Element('span', { 'class': 'cl_charts_uvp' });
    priceUVP.update("(UVP:&nbsp;"+contentUVP+"&nbsp;&euro;)");
    col.appendChild(priceMin);
    col.appendChild(priceUVP);
    row.appendChild(col);
    // (vi) votes till top:
    col = new Element('div', { 'class': 'cl_charts_col_num_votes' });
    var votesText = new Element('span', { 'class': 'cl_charts_num_votes' });
    votesText.id = "charts_num_votes_"+productId;
    votesText.update(" -- Votes");
    col.appendChild(votesText);
    row.appendChild(col);
    // (vii) voting button:
    col = new Element('div', { 'class': 'cl_charts_col_button' });
    var voteLink = new Element('a', {  });
    voteLink.href = "javascript:voteProduct("+productId+")";
    var voteImageEnabled = new Element('div', { 'class': 'cl_sale_button_vote'});
    voteImageEnabled.id = "charts_voting_button_enabled_"+productId;
    var voteImageDisabled = new Element('div', { 'class': 'cl_sale_button_vote_inactive'});
    voteImageDisabled.id = "charts_voting_button_disabled_"+productId;
    if (mVotingAllowed) voteImageDisabled.style.display = "none";
    else voteImageEnabled.style.display = "none";
    voteLink.appendChild(voteImageEnabled);
    voteLink.appendChild(voteImageDisabled);
    col.appendChild(voteLink);
    row.appendChild(col);
    
    //$('debug_div').innerHTML += "product_id: "+productId+"; Title: "+contentTitle+"<br>";
  }
  //alert(orderedProductArray.length);
  
  // increase voting area in according to the new number of elements therein
  new Effect.Morph('product_charts_content', {
    style: 'height:'+(orderedProductArray.length*55)+'px;',
    duration: 1.0
  });
  
  // let the projection appear:
  if (mVotingAllowed) $('product_charts_projection').appear( {duration: 1.0} );
}




/**
 *
 * UPDATE-SERVER RELATED FUNCTIONS 
 *
 */  
var SALE_BOX_STATUS_LOADING = 0;
var SALE_BOX_STATUS_EMPTY = 1;
var SALE_BOX_STATUS_LIVE_NO_VIEW = 2;
var SALE_BOX_STATUS_LIVE_VIEW = 3;
var SALE_BOX_STATUS_FINISHED = 4;
var SALE_BOX_STATUS_VOTING = 5;


var mMillisBetweenUpdates = 1000;
var mFirstIteration = true;
var mUpdateHandler = null;
var mlastNow = 0;

// stored sale:
var mStoredSale = null;     // Object containing all relevant values for the sale  
var mStoredSaleOld = null;  // same format as in mStoredSale 

// stored products:
var mNextProjectionHandler = null;  // handler for timeout calling reorder method..
var mStoredProducts = null; // as associative array of Objects with productId as key.    
                            // Each Objects therein stores the sale associated
                            // which the productId
var mStoredProductsOld = null;  // same format as mStoredProducts
var mReloadAllProducts = false;
var mStoredProductsOnceDetail = null; // therein are stored some one time initialisation values for the products..
var mVotingAllowed = true;  // be optimistic


/**
 * Query update-server for new sale-values in form of a javascript block which
 * is executed once it is completely transferred. 
 */ 
function queryUpdateserver()
{
  var currentTimeMillis = (new Date()).getTime();
  if ( (currentTimeMillis-mlastNow) < 300 )
  {
    // after hibernation setInterval() tries to catch up.
    // This seems to have been the case here => clear Interval and set it anew:
    if ( mUpdateHandler!=null ) clearInterval( mUpdateHandler );
    mUpdateHandler = setInterval( "queryUpdateserver()", mMillisBetweenUpdates );
    return;
  }
  mlastNow = currentTimeMillis;
  
  // hack in order to allow cross-platform scripting and thus calling
  // update-server with a different IP:
  var script = document.createElement("script");
  script.type = "text/javascript";
  script.src = mUpdateServerURL+"&n="+currentTimeMillis;  // n-parameter only a hack to disable browser-caching
  // remove old script-node:
  if ( !mFirstIteration )
  {
    var node2Remove = document.getElementsByTagName("head")[0].lastChild;
    document.getElementsByTagName("head")[0].removeChild( node2Remove );
  }
  // set new JSON script node
  document.getElementsByTagName("head")[0].appendChild(script);
  mFirstIteration = false;
}
/**
 * Called by javascript-response from update-server before onUp()
 * is called for any sale. 
 */ 
function onUpStarted()
{
  mStoredSaleOld = mStoredSale;
  mStoredSale = new Object;
  
  mStoredProductsOld = mStoredProducts;
  mStoredProducts = new Object;
}
/**
 * Called by javascript-response from update-server for each sale.
 *  
 * @param pStatus: 
 *  0: live sale
 *  1: finished sale
 *  2: voting sale 
 * @param pRemainingViewSeconds: 0 if the current user has no active view - 
 *  the remaining number of seconds otherwise.
 *  -1 if the user has an infinitively long view... 
 * @param pKoCountdown: -1 if no K.O. countdown should be displayed - the number
 *  of seconds to display otherwise.
 * @param pVotingCountdownSeconds: should only be displayed if pStatus indicates
 *  a voting-sale.     
 */ 
function onUpSale(pSaleId, pTLC, pStatus, pRemainingViewSeconds, 
                  pFormattedPrice, pMaxBidderMessage, pVotingCountdownSeconds,
                  pBuyerNickname, pShouldBlink, pVotingToDate, pDisplayFinished4Seconds, 
                  pFormattedRuntime, pSecondsToStart )
{
  // In JavaScript, objects are also associative arrays (or hashes). That is, 
  // the property theStatus.Home can also be read or written by calling
  // theStatus['Home']
  // store the complete current sale in mStoredSale
  
  mStoredSale = 
  {
    SaleId: parseInt(pSaleId),
    TLC: parseInt(pTLC),
    Status: pStatus,
    RemainingViewSeconds: parseInt(pRemainingViewSeconds),
    FormattedPrice: pFormattedPrice,
    MaxBidderMessage: pMaxBidderMessage,
    VotingCountdownSeconds: parseInt(pVotingCountdownSeconds),
    BuyerNickname: (pBuyerNickname.length>0)?"Herzlichen Gl&uuml;ckwunsch "+pBuyerNickname+"!":"",
    ShouldBlink: parseInt(pShouldBlink),
    VotingToDate: pVotingToDate,
    DisplayFinished4Seconds: pDisplayFinished4Seconds,
    FormattedRuntime: pFormattedRuntime,
    SecondsToStartText: pSecondsToStart,
    AJAXUpdate: ''
  };
  //$("debug_div").update(mStoredSale.SaleId+";"+mStoredSale.TLC+";"+mStoredSale.Status+";"+mStoredSale.RemainingViewSeconds+";"+mStoredSale.FormattedPrice+";"+mStoredSale.VotingCountdownSeconds+";"+mStoredSale.BuyerNickname+";"+mStoredSale.ShouldBlink+";"+mStoredSale.AJAXUpdate);
}
function onUpVote(pProductId, pTLC, pNumberVotes, pMayVote)
{
  if (!mVotingAllowed) return;

  mStoredProducts[pProductId] = 
  {
    ProductId: parseInt(pProductId),
    TLC: parseInt(pTLC),
    NumberVotes: pNumberVotes,
    MayVote: pMayVote
  }
  //$("debug_div").innerHTML += (pProductId+"; "+pTLC+"; "+pNumberVotes+"; "+pMayVote+"<br>");
  
  if (mStoredProductsOld==null || mStoredProductsOld[pProductId]==null) mReloadAllProducts = true;
}
function upProjection(nextChartProjectionSeconds)
{
  if (!mVotingAllowed) return;
  
  if (nextChartProjectionSeconds<5 && mNextProjectionHandler==null)
    mNextProjectionHandler = window.setTimeout("reorderProductsAndDisplay()", nextChartProjectionSeconds*1000+1000);
  $('product_charts_projection_seconds').innerHTML = nextChartProjectionSeconds;
}
/**
 * Called by javascript-response from update-server after onUp()
 * was called for all sales. 
 */ 
function onUpFinished()
{
  // (a) update sale:
  //  (i) does the current status match the old one:
  var oldStatus = (mStoredSaleOld==null)?SALE_BOX_STATUS_LOADING:_getStatus(mStoredSaleOld);
  var newStatus = (mStoredSale==null)?SALE_BOX_STATUS_LOADING:_getStatus(mStoredSale);
  if ( oldStatus!=newStatus )
  {
    // status has changed => show the new DIV and hide the old one:
    $(mTLC+"_"+oldStatus).fade( {duration: 0.5} );
    $(mTLC+"_"+newStatus).appear( {duration: 0.5} ); 
  }

  //  (ii) set the values in the new DIV:
  if (newStatus!=SALE_BOX_STATUS_EMPTY) // no need to to anything if sale-box is empty..
  {
    // price:
    if ( (newStatus==SALE_BOX_STATUS_LIVE_NO_VIEW || newStatus==SALE_BOX_STATUS_LIVE_VIEW || newStatus==SALE_BOX_STATUS_FINISHED) &&
         (oldStatus!=newStatus || mStoredSale.FormattedPrice!=mStoredSaleOld.FormattedPrice) )
    {
      if (newStatus==SALE_BOX_STATUS_FINISHED && (mStoredSale.BuyerNickname==null || mStoredSale.BuyerNickname.length==0))
        $(mTLC+"_"+SALE_BOX_STATUS_FINISHED+"_price").innerHTML = "??,??&nbsp;&euro;"; // don't show price if sale finished due to K.O.
      else $(mTLC+"_"+newStatus+"_price").innerHTML = mStoredSale.FormattedPrice+"&nbsp;&euro;";
    }
    // RemainingViewSeconds
    if ( newStatus==SALE_BOX_STATUS_LIVE_VIEW && (oldStatus!=newStatus || mStoredSale.RemainingViewSeconds!=mStoredSaleOld.RemainingViewSeconds) )
    {
      if (mStoredSale.RemainingViewSeconds==-1) $(mTLC+"_"+newStatus+"_view_seconds").innerHTML = "&infin;";  //$(mTLC+"_"+newStatus+"_extend_button").hide();
      else $(mTLC+"_"+newStatus+"_view_seconds").innerHTML = _getCountdown(mStoredSale.RemainingViewSeconds);
    }
    // MaxBidderMessage:
    if ( newStatus==SALE_BOX_STATUS_LIVE_VIEW && mStoredSale!=null && mStoredSale.MaxBidderMessage!=null && mStoredSale.MaxBidderMessage.length>0 &&
        (oldStatus!=newStatus || mStoredSale.MaxBidderMessage!=mStoredSaleOld.MaxBidderMessage) )
    {
      $(mTLC+"_"+newStatus+"_max_bidder_msg").show();
      $(mTLC+"_"+newStatus+"_max_bidder_msg").innerHTML = mStoredSale.MaxBidderMessage;
    }
    // BuyerNickname:
    if ( newStatus==SALE_BOX_STATUS_FINISHED && (oldStatus!=newStatus || mStoredSale.BuyerNickname!=mStoredSaleOld.BuyerNickname) )
    {
      $(mTLC+"_"+newStatus+"_buyer").innerHTML = mStoredSale.BuyerNickname;
    }
    // Finished displayed for seconds:
    if ( newStatus==SALE_BOX_STATUS_FINISHED && (oldStatus!=newStatus || mStoredSale.DisplayFinished4Seconds!=mStoredSaleOld.DisplayFinished4Seconds) )
    {
      if (mStoredSale.DisplayFinished4Seconds>0) $(mTLC+"_"+newStatus+"_finished_display_seconds").innerHTML = mStoredSale.DisplayFinished4Seconds;
    }
    // Voting Date:
    if ( newStatus==SALE_BOX_STATUS_VOTING && (oldStatus!=newStatus || mStoredSale.VotingToDate!=$(mTLC+"_"+newStatus+"_voting_date").innerHTML) )
    {
      $(mTLC+"_"+newStatus+"_voting_date").innerHTML = mStoredSale.VotingToDate;
    }
    // VotingCountdownSeconds:
    if ( newStatus==SALE_BOX_STATUS_VOTING && (oldStatus!=newStatus || mStoredSale.VotingCountdownSeconds!=mStoredSaleOld.VotingCountdownSeconds) )
    {
      // voting seconds in sale-boxes:
      $(mTLC+"_"+newStatus+"_voting_seconds").innerHTML = _getCountdownLong(mStoredSale.VotingCountdownSeconds);
    }
    // running since text:
    if ( newStatus==SALE_BOX_STATUS_LIVE_NO_VIEW && (oldStatus!=newStatus || mStoredSale.FormattedRuntime!=mStoredSaleOld.FormattedRuntime) )
    {
      $(mTLC+"_"+newStatus+"_runtime").innerHTML = mStoredSale.FormattedRuntime;
    }
    // time to start text:
    if ( newStatus==SALE_BOX_STATUS_LIVE_VIEW && (oldStatus!=newStatus || mStoredSale.SecondsToStartText!=mStoredSaleOld.SecondsToStartText) )
    {
      if (mStoredSale.SecondsToStartText.length>1)
      {
        $(mTLC+"_"+newStatus+"_to_start").innerHTML = mStoredSale.SecondsToStartText;
        $(mTLC+"_"+newStatus+"_buy_button").hide();
      }
      else
      {
        $(mTLC+"_"+newStatus+"_to_start").innerHTML = "";
        $(mTLC+"_"+newStatus+"_buy_button").show();
      }
    }
    

    try
    {   
      // get new image and title from server if the saleID has changed or voting was completed
      if ( (mStoredSaleOld!=null && mStoredSale!=null && mStoredSaleOld.SaleId!=mStoredSale.SaleId && newStatus!=SALE_BOX_STATUS_VOTING) || 
           (oldStatus==SALE_BOX_STATUS_VOTING && newStatus!=SALE_BOX_STATUS_VOTING) ||
           (mStoredSaleOld==null && $(mTLC+"_2_img")!=null && $(mTLC+"_2_img").src.substring(0,15)=="http://empty.de") ) // may happen during first time initialisation when loading the page just as voting finishes 
      {
        // set all images to loading:
        $(mTLC+"_2_img").src = "http://img.cooni.de/img/v2/sale_boxes/loader.gif";
        $(mTLC+"_3_img").src = "http://img.cooni.de/img/v2/sale_boxes/loader.gif";
        $(mTLC+"_4_img").src = "http://img.cooni.de/img/v2/sale_boxes/loader.gif";
        
        // asynchronously request new image/title
        mStoredSale.AJAXUpdate = "send";
        new Ajax.Request('/Index',
        {
          method:'get',
          parameters: {'__ia': 'returnSale', 'sale_id':mStoredSale.SaleId, no_cache:(new Date()).getTime()},
          onSuccess: function(transport)
          {
            setAllSaleTitlesAndImages( transport.responseText.evalJSON() );
          }
        });
      }
    }
    catch (e) {} // do nothing..
  
    // blink if necessary
    if (mStoredSale!=null && mStoredSale.ShouldBlink==1 && (newStatus==SALE_BOX_STATUS_LIVE_NO_VIEW || newStatus==SALE_BOX_STATUS_LIVE_VIEW))
    {
      var elementId = mTLC+"_"+newStatus+"_bg";
      $(elementId).show();
      $(elementId).highlight( {duration: 0.5, startcolor: '#ff9900'} );
      window.setTimeout("_saleBoxHide('"+elementId+"')", 700);
      //$(mTLC+"_"+newStatus+"_price").highlight( {duration: 0.5, startcolor: '#ff9900'} );
      //$(mTLC).highlight( {duration: 0.5, startcolor: '#ff9900'} );
    }
    
  
    // logging
    /*if (newStatus!=SALE_BOX_STATUS_VOTING && $(mTLC+"_2_img")!=null && $(mTLC+"_2_img").src.substring(0,15)=="http://empty.de")
    {
       // should not happen - whenever there is no image loaded it should already have been requested
       var feedback = "";
       if (mStoredSale!=null) feedback = mStoredSale.AJAXUpdate;
       $('debug_div').innerHTML += "200: "+mTLC+"; "+feedback+";" +$(mTLC+"_2_img").src+"<br>";
    }*/
  
    _updateLastPriceDigit();
  }
  // end: sale-update..
  
  
  // start product update
  if (mVotingAllowed)
  {
    if (mReloadAllProducts)
    {
      var productIds = "";
      mReloadAllProducts = false;
      
      var hash = $H(mStoredProducts);
      hash.each(function(pair) {
        productIds += pair.key+";";
      });
      
      new Ajax.Request('/Index',
      {
        method:'get',
        parameters: {'__ia': 'returnProducts', 'product_id':productIds, 'no_cache':(new Date()).getTime()},
        onSuccess: function(transport)
        {
          setAllChartTitlesAndImages( transport.responseText.evalJSON() );
        }
      });
    }
    
    // iterate all product-wrapper:
    _updateCharts();
  }
  // end product update
}
function _saleBoxHide(elementId) {
  $(elementId).hide();
}


function _updateCharts()
{
  if (mStoredProducts==null) return;
  var hash = $H(mStoredProducts);
  if (!hash) return;
  hash.each(function(pair)
  {
    var productId = pair.key;
    var values = pair.value;
    if (mStoredProducts[productId]!=null)
    {
      // did any user vote:
      if (mStoredProductsOld!=null && mStoredProductsOld[productId]!=null && 
          mStoredProductsOld[productId].NumberVotes<mStoredProducts[productId].NumberVotes)
      {
        $("charts_row_"+productId).highlight( {duration: 0.5, startcolor: '#ff9900'} );
      }
      
      // update number of votes for all rows:
      if ($("charts_num_votes_"+productId)!=null) $("charts_num_votes_"+productId).innerHTML = mStoredProducts[productId].NumberVotes+" Votes";
      
      if (mVotingAllowed && mStoredProducts[productId].MayVote==0) _disableVoting();
      if (!mVotingAllowed && mStoredProducts[productId].MayVote>0) _enableVoting();
    }
  });
}
function _disableVoting()
{
  if (mStoredProducts==null) return;

  //iterate over all product-wrapper:
  var productsHash = $H(mStoredProducts);
  productsHash.each(function(pair) {
    var productId = pair.key;
    
    //$("charts_voting_button_img_"+productId).toggleClassName("cl_sale_button_vote_inactive");
    //$("charts_voting_button_img_"+productId).style.backgroundImage="url('http://img.cooni.de/img/v2/buttons/button_vote_off.gif')";
    $("charts_voting_button_enabled_"+productId).setStyle({ display:'none' });
    $("charts_voting_button_disabled_"+productId).setStyle({ display:'block' });
  });
  
  $('product_charts_projection').fade( {duration: 1.0} );
  mVotingAllowed = false;
}
function _enableVoting()
{
  if (mStoredProducts==null) return;
  
  //iterate over all product-wrapper:
  var productsHash = $H(mStoredProducts);
  productsHash.each(function(pair) {
    var productId = pair.key;
    
    //$("charts_voting_button_img_"+productId).toggleClassName("cl_sale_button_vote");
    //$("charts_voting_button_img_"+productId).style.backgroundImage="url('http://img.cooni.de/img/v2/buttons/button_vote.gif')";
    $("charts_voting_button_enabled_"+productId).setStyle({ display:'block' });
    $("charts_voting_button_disabled_"+productId).setStyle({ display:'none' });
  });

  $('product_charts_projection').appear( {duration: 1.0} );
  mVotingAllowed = true;
}







function _getStatus(saleObject)
{
  if (saleObject==null) return SALE_BOX_STATUS_EMPTY;
  
  if (saleObject.Status==0)
  {
    // live-sale - decide on the subtype:
    if (saleObject.RemainingViewSeconds>0 || saleObject.RemainingViewSeconds==-1) return SALE_BOX_STATUS_LIVE_VIEW;
    else return SALE_BOX_STATUS_LIVE_NO_VIEW;
  }
  else if (saleObject.Status==1) return SALE_BOX_STATUS_FINISHED;
  else if (saleObject.Status==2) return SALE_BOX_STATUS_VOTING;
  else return SALE_BOX_STATUS_LOADING;
}
function _getCountdown( seconds )
{
  var number_seconds = parseInt(seconds);
  if ( number_seconds<=0 ) return "00:00";

  // minutes:
  var minutes = Math.floor(number_seconds / 60);
  var minutesStr = "00";
  if ( minutes<10 ) minutesStr = "0"+minutes;
  else minutesStr = ""+minutes;
  number_seconds = number_seconds % 60;
  
  // seconds:
  var secondsStr = "00";
  if ( number_seconds<10 ) secondsStr = "0"+number_seconds;
  else secondsStr = ""+number_seconds;
  
  return minutesStr+":"+secondsStr;
}
function _getCountdownLong( seconds )
{
  var number_seconds = parseInt(seconds);
  if ( number_seconds<=0 ) return "00d 00h 00m 00s";

  // days:
  var days = Math.floor(number_seconds / 86400);
  var dayStr = "00";
  if ( days<10 ) dayStr = "0"+days;
  else dayStr = ""+days;
  number_seconds = number_seconds % 86400;
  
  // hours:
  var hours = Math.floor(number_seconds / 3600);
  var hourStr = "00";
  if ( hours<10 ) hourStr = "0"+hours;
  else hourStr = ""+hours;
  number_seconds = number_seconds % 3600;
  
  // minutes:
  var minutes = Math.floor(number_seconds / 60);
  var minutesStr = "00";
  if ( minutes<10 ) minutesStr = "0"+minutes;
  else minutesStr = ""+minutes;
  number_seconds = number_seconds % 60;

  // seconds:
  var secondsStr = "00";
  if ( number_seconds<10 ) secondsStr = "0"+number_seconds;
  else secondsStr = ""+number_seconds;
  
  return dayStr+"d "+hourStr+"h "+minutesStr+"m "+secondsStr+"s";
}





var mStoredPrice = null;
var mPriceCount = 0;
function _updateLastPriceDigit()
{
  if (mStoredSale==null) return;
  mPriceCount = (mPriceCount+1) % 2;
  if (mPriceCount==0) return;
  
  if (mStoredPrice==null)
  {
    mStoredPrice = 
    {
      PriceCents: Math.round(Math.random()*100)+3000, // >99*30 => enough for one cycle
      Velocity: Math.round(Math.random()*30)+10,       // in [10,45]
      ForSeconds: Math.round(Math.random()*30)+5
    };
  }
  
  var status = _getStatus(mStoredSale);
  if (status!=SALE_BOX_STATUS_LIVE_NO_VIEW) return;
  
  mStoredPrice.PriceCents = mStoredPrice.PriceCents - mStoredPrice.Velocity;
  if (mStoredPrice.PriceCents<0) mStoredPrice.PriceCents = Math.round(Math.random()*100)+999999;
  mStoredPrice.ForSeconds = mStoredPrice.ForSeconds-2;  // as only called every other second
  
  if (mStoredPrice.ForSeconds<=0)
  {
    var newVelocity = Math.round( (3.0*mStoredPrice.Velocity + Math.round(Math.random()*40)) / 4.0 );
    var newPriceCent = mStoredPrice.PriceCents - (Math.floor(mStoredPrice.PriceCents/100)*100) + 3000;  // same cent amount, but otherwise at least 3000
    mStoredPrice =
    {
      PriceCents: newPriceCent,
      Velocity: newVelocity,
      ForSeconds: Math.round(Math.random()*30)+5
    };
  }
  
  // output:
  var displayedPrice = mStoredSale.FormattedPrice;
  if (displayedPrice!=null && displayedPrice.length==5 && displayedPrice.charAt(4)=="?" && displayedPrice.charAt(3)=="?")
  {
    var lastDigits = parseInt( mStoredPrice.PriceCents - (Math.floor(mStoredPrice.PriceCents/100)*100) );
    displayedPrice = displayedPrice.substr(0,3)+(lastDigits<10?"0"+lastDigits:""+lastDigits);
    $(mTLC+"_"+SALE_BOX_STATUS_LIVE_NO_VIEW+"_price").innerHTML = displayedPrice+"&nbsp;&euro;";
  }
  
  // debug:
  //$('debug_div').innerHTML = "Price: "+mStoredPrice.PriceCents+" # Vel.:"+mStoredPrice.Velocity+" # For [s.]:"+mStoredPrice.ForSeconds;
}
