// debug - v0.3 - 6/8/2009
// http://benalman.com/projects/javascript-debug-console-log/
window.debug=(function(){var c=this,e=Array.prototype.slice,b=c.console,i={},f,g,j=9,d=["error","warn","info","debug","log"],m="assert clear count dir dirxml group groupEnd profile profileEnd time timeEnd trace".split(" "),k=m.length,a=[];while(--k>=0){(function(n){i[n]=function(){j!==0&&b&&b[n]&&b[n].apply(b,arguments)}})(m[k])}k=d.length;while(--k>=0){(function(n,o){i[o]=function(){var q=e.call(arguments),p=[o].concat(q);a.push(p);h(p);if(!b||!l(n)){return}b.firebug?b[o].apply(c,q):b[o]?b[o](q):b.log(q)}})(k,d[k])}function h(n){if(f&&(g||!b||!b.log)){f.apply(c,n)}}i.setLevel=function(n){j=typeof n==="number"?n:9};function l(n){return j>0?j>n:d.length+j<=n}i.setCallback=function(){var o=e.call(arguments),n=a.length,p=n;f=o.shift()||null;g=typeof o[0]==="boolean"?o.shift():false;p-=typeof o[0]==="number"?o.shift():n;while(p<n){h(a[p++])}};return i})();
var report = debug.log;  // mirrors Flash

$(document).ready(function() {
  $('#admin_console_border').mousedown(beginDrag);
  $(document).keydown(hideConsole);
  if(!developing && !$('#original_recipe').hasClass('no_recipe'))
    setTimeout(startRecipes, 1000);
});

var recipeCount = 0;
var recipeTimer;
function startRecipes()
{
  originalRecipe();
  recipeTimer = setInterval(originalRecipe, 5*60*1000);
}

function originalRecipe()
{
  if(++recipeCount >= 6)
    clearInterval(recipeTimer);
  else if(!recipeTimer)
    recipeTimer = setInterval(originalRecipe, 5*60*1000);
  $.getJSON("/recipe", function(recipe) {
    if(!recipe) return;
    $('#original_recipe').html(recipe.recipe).attr("title", recipe.source);
  });
}

function makeIntoOneLine(elem)
{
  var height = elem.offsetHeight;
  var text = elem.innerHTML;
  elem.innerHTML = 'test';
  var normheight = elem.offsetHeight;
  elem.innerHTML = text;
  return shortenTextInElem(elem, normheight);
}

function shortenTextInElem(elem, targetheight) {
  var text = elem.innerHTML;
  var height = elem.offsetHeight;
  if (height > targetheight) {
    if (elem.title == '')
      {
    elem.title = text.replace(/(<([^>]+>))/ig, "");
    $(elem).data('full', text);
      }
    var words = text.split(' ');
    words.pop();
    text = words.shift();
    while(words.length != 0) {
      var word = words.shift();
      elem.innerHTML = text+' '+word+' ...';
      if (elem.offsetHeight > targetheight)
    break;
      text += ' '+word;
    }
    text += ' ...';
  }
  var same = elem.innerHTML = text;
  elem.innerHTML = text;
  return (height > targetheight && !same);
}

function makeGroupSingleLined(objects) {
  var has_multilines = true;
  var cycles = 0;
  while (has_multilines) {
    has_multilines = false;
    for (var i=0; i<objects.length; i++)
      if (makeIntoOneLine(objects[i]))
    has_multilines = true;
    cycles += 1;
    if (cycles > objects.length) break;
  }
}


function RGBtoHex(R,G,B) {return toHex(R)+toHex(G)+toHex(B);}
function toHex(N) {
 if (N==null) return "00";
 N=parseInt(N); if (N==0 || isNaN(N)) return "00";
 N=Math.max(0,N); N=Math.min(N,255); N=Math.round(N);
 return "0123456789ABCDEF".charAt((N-N%16)/16)
      + "0123456789ABCDEF".charAt(N%16);
}

function HexToR(h) {return parseInt((cutHex(h)).substring(0,2),16);}
function HexToG(h) {return parseInt((cutHex(h)).substring(2,4),16);}
function HexToB(h) {return parseInt((cutHex(h)).substring(4,6),16);}
function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7) : h;}

function switchToInnerTab(windowid, newtab, windowcontainer) {
  var sclass = "selectedinnertab";
  $('#innertabs div.'+sclass).removeClass(sclass);
  $(newtab).addClass(sclass);

  $('div.window', windowcontainer).addClass("hidden");
  $('#'+windowid).removeClass("hidden");
}

function makeUVLPParams(q) {
  var params = {};
  if(q.charAt(0) == "?") q = q.substr(1);
  jQuery.each(q.split('&'), function()
    {
      params[this.split('=')[0]] = this.split('=')[1];
    });
  return params;
}

function isAE500(response)
{
  return response.length > 300 && response.indexOf("The server encountered an error and could not complete your request.") != -1;
}

$.retried_ajax = function(type, url, data, success, delay) {
  delay = delay || 1000;  // start retries at 1000ms, doubling each time
  if(delay >= 1200000)
    return;  // no response after twenty minutes? quit (show giveup error msg?)
  if(!data) { // no extra params passed
    data = {};
    success = function() {};
  }
  else if(!success) { // missing success
    success = data;
    data = {};
  }

  $[type](url, data, function(response) {
    if(response == "timeout" || response == "retry" || isAE500(response))
      setTimeout(function() { $.retried_ajax(type,url,data,success,delay*2); },
         delay);
    else if (response == "disabled") {
      window.location.href = '/disabled';
    }
    else
      success(response);
  });
};

// If you use two args, the second is treated as the success function instead.
// You can also just pass the URL.
$.retried_get = function(url, data, success) {
  $.retried_ajax("get", url, data, success);
};

$.retried_post = function(url, data, success) {
  $.retried_ajax("post", url, data, success);
};


function inversePct(num, total) {
  return parseInt((100 * (total - num)) / total) + "%";
}


function removeScreen() { $("#screen").fadeOut(500); }
function replaceScreen() { $("#screen").fadeIn(500); }

function logServerError(e) {
  var params = {'message':e.message,
        'fileName':e.fileName,
        'lineNumber':e.lineNumber,
        'stack':e.stack,
        'name':e.name};
  $.retried_get("j_error", params, function() {});
  debug.error("error!: ", params);
}


function clickWithin(e) {
  var jelem = $(e.target);
  if (jelem.hasClass("parent_clicker")) $('*',jelem).click();
}



// Admin console controls

var offsetX = 0;
var offsetY = 0;

function beginDrag(e) {
  var console = $('#admin_console');
  offsetX = e.pageX - parseInt(console.css('left'));
  offsetY = e.pageY - parseInt(console.css('top'));
  $('body').mousemove(drag).mouseup(endDrag);
  /*
  var pageCoords = "( " + e.pageX + ", " + e.pageY + " )";
  var clientCoords = "( " + e.clientX + ", " + e.clientY + " )";
  debug.log("( B e.pageX, e.pageY ) - " + pageCoords);
  debug.log("( B e.clientX, e.clientY ) - " + clientCoords);
   */
}

function drag(e) {
  /*
  var pageCoords = "( " + e.pageX + ", " + e.pageY + " )";
  var clientCoords = "( " + e.clientX + ", " + e.clientY + " )";
  debug.log("( D e.pageX, e.pageY ) - " + pageCoords);
  debug.log("( D e.clientX, e.clientY ) - " + clientCoords);
  */
  $('#admin_console')
    .css('left',e.pageX-offsetX+'px')
    .css('top',e.pageY-offsetY+'px');
}

function endDrag(e) {
  saveConsoleSettings();
  $('body').unbind("mousemove", drag).unbind("mouseup", endDrag);
}

function hideConsole(e) {
  if (e.keyCode==33 && e.shiftKey==1) {
    var console = $('#admin_console');
    console.toggleClass('hidden');
    saveConsoleSettings();
  }
}

function saveConsoleSettings() {
  var console = $('#admin_console');
  if (!console.is('*')) return;
  var params = { left:console.css('left'),
         top:console.css('top'),
             hidden:console.hasClass('hidden') };
  $.retried_get("/admin/saveconsoleparams",params,function() { ; });
}



//var to_replace = [['C','Character'],['D','Definition'],['T','Tone'],
//        ['P','Pinyin'],   ['K','Kanji'],     ['R','Reading']];
//for (var i=0; i<to_replace.length; i++)
//  to_replace[i][1] = ('<span title="'+to_replace[i][1]+'">'+
//            to_replace[i][0]+'</span>');

var part_map = {
  C:'Character', D:'Definition', T:'Tone',
  P:'Pinyin',    K:'Kanji',      R:'Reading'
};

function addPartTitles(elems) {
  elems.each(function()
  {
    var cell = $(this);
    var html = cell.html();
    if (html.indexOf('span')>-1) return true;
    cell.attr('title',part_map[$.trim(html)]);
    //for (var i=0; i<to_replace.length; i++)
      //html = html.replace(to_replace[i][0], to_replace[i][1]);
    //cell.html(html);
    return true;
  });
}


function log(s) {
  var t = $('#admin_console textarea');
  if (t.length==0) return;
  if (t.hasClass('hidden')) {
    t.val('');
    t.removeClass('hidden');
  }
  t.val(s+'\n'+t.val());
}


function findParent(elem, key_func) {
  elem = $(elem)[0];
  try { while (!key_func(elem)) elem = elem.parentNode; }
  catch (e) { return null; }
  return elem;
}

function textToHTML(s, author) {
 //var old_s = s;
 s = $('<div>').html(s).text();  // strip the HTML
 s = s.replace(/img:(http:\/\/\S+)/gi, '<img src="$1"/>')  // img:http://...
   .replace(/\n/gi, '<br/>')                        // newlines
   .replace(/\*([^*]+)\*/gi, '<b>$1</b>')           // *bolds* it
   .replace(/_([^ _][^_]*)_/gi, '<em>$1</em>');     // _italicizes_ it
 //debug.info(old_s, "\n->\n", s);
 if(author)
   s = s + '<span class="other_mnem_byline"> ('+author+')</span>';
 return s;
}

function HTMLToText(s) {
  s = s.replace(/<img src="(http:\/\/\S+)"\/?>/gi, 'img:$1')
    .replace(/<br\/?>/gi, '\n')
    .replace(/<b>(.*?)<\/b>/gi, '*$1*')
    .replace(/<em>(.*?)<\/em>/gi, '_$1_')
    .replace(/<span class="other_mnem_byline">.*?<\/span>/gi, '');
  return s;
}

// http://www.vanyli.net/?p=3
// could extend this to retry timeouts with a failure func, too
$.delayed_ajax = function(options) {
  options.tstamp = (new Date().getTime() + new Date().getMilliseconds());
  options.success_original = options.success;
  options.success = function(data, status) {
    var last = $('body').data('ajax_pool' + options.request_type);
    if (last.tstamp == options.tstamp && options.success_original)
       options.success_original(data, status);
  };

  $('body').data('ajax_pool' + options.request_type, options);

  if ($('body').data('timeout_pool' + options.request_type))
    clearTimeout($('body').data('timeout_pool' + options.request_type));

  $('body').data('timeout_pool' + options.request_type,
         setTimeout("do_request('" + options.request_type + "')",
                 options.delay));

  do_request = function(request_type) {
    $.ajax($('body').data('ajax_pool' + request_type));
  };
};

/*
 * POP UPS (to replace dialogs)
 *
 * To create a popup, pass in the url and params for the popup's body,
 * and the parent popup if there is one.
 *
 * Things like locking and callbacks are done with the javascript object.
 *
 */

function Popup(url, params) {

  // PROPERTIES

  this.callback = null;
  this.ret_val = null;
  this.parent = null; // Javascript Object
  this.child = null; // Javascript Object
  this.jqobj = null; // jQuery Object
  this.locked = false;
  this.url = null;
  this.params = null;

  // FUNCTIONS

  this.setCallback = function(callback) {
    this.callback = callback;
    return this;
  };

  this.mouseClick = function(e) {
    //debug.info('mouse click callback');
    // this function gets called whenever the mouse is clicked when a popup
    // is open
    var popup = findParent(e.target, function(elem)
    {
      return $(elem).hasClass('popup') || $(elem).hasClass('popup_button');
    });
    if (popup==null) {
      // outside of any popup, need to close it
      popup = $('.root_popup');
      if (popup.length==0)
    $('body').unbind('click', this.mouseClick);
      else
    popup.data('j_obj').close();
    }
    else {
      // inside a popup, close any child popups
      var j_obj = $(popup).data('j_obj');
      if (j_obj==null) return;
      var child = j_obj.child;
      if (child!=null) child.close();

      if ($(e.target).hasClass('popup_close'))
    j_obj.close();
    }
  };

  this.openOnPage = function() {
    $('#popup_screen').height($(document).height());
    //debug.info('put this as body height: ',$(document).height());
    $(window).resize(function () {
      $('#popup_screen').height($(document).height());
    }); // ensures that the screen covers eeeeeeverything!
    // inserts this popup in the page, so not nested in any other popups
    this.jqobj.addClass('root_popup');
    $('#popup_screen').show();
    $('#popup_wrapper').show().append(this.jqobj);
    $('body').click(this.mouseClick);
    return this;
  };

  this.openInPopup = function(parent_popup) {
    // puts this popup, nested in another popup
    this.jqobj.addClass('child_popup');
    if (!this.jqobj.hasClass('inner_node_popup') &&
    !this.jqobj.hasClass('leaf_node_popup'))
      this.jqobj.addClass('leaf_node_popup');
    $(parent_popup).append(this.jqobj);
    this.parent = $(parent_popup).data('j_obj');
    this.parent.child = this;
    return this;
  };

  this.showNav = function(parent_popup) {
    this.jqobj.find('.popup_nav').removeClass('hidden');
    return this;
  };

  this.open = function(parent_popup) {
    if(typeof skritter != "undefined")
      skritter.giveMacMouse(true);
    if (parent_popup==null) return this.openOnPage();
    else return this.openInPopup(parent_popup);
  };

  this.close = function() {
    //debug.info('close');
    // closes this popup, calling any callback with any return value
    // that has been set prior to the closing to the callback and ret_val
    // values
    if (this.callback!=null) {
      try { this.callback(this.ret_val); }
      catch (e) { ; }
    }

    if (this.jqobj.hasClass('root_popup')) {
      $(window).unbind('resize');
	  $('#popup_screen').hide();
      $('#popup_wrapper').hide().html('');
      $('body').unbind('click', this.mouseClick);
      if(typeof skritter != "undefined")
        skritter.giveMacMouse(false);
    }

    this.jqobj.remove();
    return this;
  };

  this.lock = function() {
    // Makes it so that you can't close this popup with clicks outside
    // the box or with the remove button
    $('.popup_close', this.jqobj).hide();
    $('body').unbind('click', this.mouseClick);
    this.locked = true;
    return this;
  };

  this.positionFromTop = function(pageY) {
    var top = Math.max(pageY-474, 0);
    this.jqobj.css('top',top+'px');
    return this;
  };

  this.loadUrl = function(url, params) {
    if (!params) params = {};
    this.url = url;
    this.params = params;

    $.retried_get(url, params, function(popup, url, params) {
      return function(response)
    {
      if (popup.url!=url || popup.params!=params) return;
      $('.popup_body', popup.jqobj).remove();
      $('.popup_loading', popup.jqobj).remove();
      $('.popup_bar', popup.jqobj)
        .after($(response).addClass('popup_body'));
      if (popup.locked) $('input[value="Cancel"]', popup.jqobj).hide();
    };
    } (this, url, params)
    );
  };

  this.setAsInnerNode = function() {
    //makeFullWidth
    this.jqobj.removeClass('leaf_popup');
    this.jqobj.addClass('inner_node_popup');
    return this;
  };

  // INITIALIZATION

  this.jqobj = $('#blank_popup .popup').clone();
  this.jqobj.data('j_obj',this);

  this.loadUrl(url, params);
}

function findParentPopup(elem) {
  debug.info(elem);
  var key = function(elem) { return $(elem).hasClass('popup'); };
  debug.info($(findParent(elem, key)));
  return $(findParent(elem, key)).data('j_obj');
}

function closeParentPopup(elem) {
  findParentPopup(elem).close();
}


/* CANVAS BUTTON! */

/*
 * The canvas button takes a div with text in it and turns it effectively
 * into an input button with a background rendered with canvas. The button
 * has five states: neutral, hover, active, focus, and hover+focus, rendered
 * in that order.
 */

function makeCanvasButton(div) {
  // button starts out as a div with text in it. Move the text into a button
  var input = $('<input type="button"></input>').val(div.text());
  div.text('').append(input);

  // figure out the width and height of the div and lock it in place
  div.attr('height',div.css('height')).attr('width',div.css('width'));
  var height = div.outerHeight();
  var width  = div.outerWidth();

  // radius and line_width based on the size of the button
  var radius = 5;
  var line_width = 1;

  // make a canvas element and add it to the div, then get the context
  var canvas = $('<canvas width='+width+' height='+(height*5)+'></canvas>');
  canvas.addClass('button');
  var ctx = canvas[0].getContext('2d');

  // check if it's a browser that doesn't have a context, handle if it doesn't
  ctx = null;
  if (!ctx) {
    input.addClass('alt_button');
    return;
  }
  div.append(canvas);

  // a little geometry, figure out where the linear gradient should go relative
  // to (0,0) so that the end of the gradient hits both the lower left and
  // upper right corners of the button.
  var x = (Math.pow(height,2)) / (width + (height/width));
  var y = (width*x) / height;

  // figure out the colors based on the class. Colors are start and stop
  // gradient, and focus color
  var colors = ['#FD4239','#EB0404','#39C3FD'];
  if (div.hasClass('blue')) colors = ['#3B39FD','#2A2664','#39C3FD'];
  if (div.hasClass('green')) colors = ['#3AFD39','#2BC433','#39C3FD'];
  if (div.hasClass('white')) colors = ['#FFFFFF','#D6D6D6','#39C3FD'];

  // make the rounded rectangles and borders
  for (var i=0; i<5; i++) {
    // move into position
    ctx.save();
    ctx.translate(0,height*i);

    // make the gradients for the background and the stroke style
    var fill_gradient = ctx.createLinearGradient(0,0,0,height);
    fill_gradient.addColorStop(0,colors[0]);
    fill_gradient.addColorStop(1,colors[1]);
    ctx.fillStyle = fill_gradient;

    var border_stroke_style = null;
    if (i==2) {
      border_stroke_style = ctx.createLinearGradient(0,0,x,y);
      border_stroke_style.addColorStop(0,'rgba(0,0,0,0.7)');
      border_stroke_style.addColorStop(1,'rgba(0,0,0,0.4)');
    }
    else {
      border_stroke_style = '#777';
    }

    // draw rounded rectangle with borders and gradients
    roundRect(ctx, line_width, line_width, width-line_width*2, height-line_width*2, radius);
    ctx.fill();

    ctx.strokeStyle=border_stroke_style;
    roundRect(ctx, line_width*1.5, line_width*1.5,
	      width-line_width*3, height-line_width*3, radius-line_width);
    ctx.lineWidth = line_width;
    ctx.stroke();

    // if this is a hover state, need to whiten it a bit
    if (i==1 || i==4) {
      ctx.fillStyle = "rgba(255,255,255,0.15)";
      ctx.fill();
    }
    // if pressed, make it darker
    if (i==2) {
      ctx.fillStyle = "rgba(0,0,0,0.2)";
      ctx.fill();
    }
    // if focused, add a complimentary color glow
    if (i==2 || i==3 || i==4) {
      roundRect(ctx, line_width*1,line_width*1,
		width-(line_width*1), height-(line_width*1),
		radius);
      ctx.lineWidth = line_width*2;
      ctx.strokeStyle = colors[2];
      ctx.globalAlpha = 0.6;
      ctx.stroke();
      ctx.globalAlpha = 0.3;
      ctx.fillStyle = colors[2];
      //ctx.fill();
    }
    // go back to original state
    ctx.restore();
  }

  //debug.info(canvas[0]);
  //debug.info('url("'+canvas[0].toDataURL()+'")');

  /*
   * Note: tried putting the image url in as a background-image so that
   * the canvas built data could be used for input buttons, but the image
   * gets screwed up when Firefox or Chrome changes page zoom. Note sure
   * why. When you zoom, then refresh, it's fine.
   */
  //div.css('background-image','url("'+canvas[0].toDataURL()+'")');
  //canvas.hide();
}


/**
 * http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
 *
 * Draws a rounded rectangle using the current state of the canvas.
 * If you omit the last three params, it will draw a rectangle
 * outline with a 5 pixel border radius
 * @param {CanvasRenderingContext2D} ctx
 * @param {Number} x The top left x coordinate
 * @param {Number} y The top left y coordinate
 * @param {Number} width The width of the rectangle
 * @param {Number} height The height of the rectangle
 * @param {Number} radius The corner radius. Defaults to 5;
 * @param {Boolean} fill Whether to fill the rectangle. Defaults to false.
 * @param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
 */
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
  stroke = stroke === undefined ? false : false;
  radius = radius === undefined ? 5 : radius;
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
  if (stroke) {
    ctx.stroke();
  }
  if (fill) {
    ctx.fill();
  }
}