/**
 * Client side image editing tools
 * Copyright Andrew Rice, Invest Solutions Limited 2006
 *
 * http://www.investsolutions.co.uk
 *
 * Please contact us for permission before copying this code.
 *
 * Version 1.0
 */

var image_details = {};
var activePrefix = "NONE";
var xOffset;
var yOffset;
var edgeMove;
var xRef;
var yRef;

/**
 * Set the visibility of the tools.  If visible is true then the tools
 * are shown and javascript callbacks are registered on the Scale and
 * Crop form elements to maintain registration with the dynamic
 * editing components.
 */
function tools_visibility_set(prefix,visible) {
  var tools1 = document.getElementById(prefix+'_tools1');
  var tools2 = document.getElementById(prefix+'_tools2');
  var cont = document.getElementById(prefix+'_container');

  if (visible) {
    tools1.style.visibility='visible';
    tools2.style.visibility='visible';
    cont.style.display='block';
    cont.style.visibility='visible';
    cont.style.position = 'relative';
    document.getElementById(prefix+'_width').onchange       = function() { scale_inputToImage(prefix,'width'); }
    document.getElementById(prefix+'_height').onchange      = function() { scale_inputToImage(prefix,'height'); }
    document.getElementById(prefix+'_crop_width').onchange  = function() { crop_inputToDiv(prefix); }
    document.getElementById(prefix+'_crop_height').onchange = function() { crop_inputToDiv(prefix); }
    document.getElementById(prefix+'_top').onchange    = function() { crop_inputToDiv(prefix); }
    document.getElementById(prefix+'_left').onchange   = function() { crop_inputToDiv(prefix); }    
  }
  else {
    tools1.style.visibility='hidden';
    tools2.style.visibility='hidden';
    cont.style.display='none';
    cont.style.visibility='hidden';
    document.getElementById(prefix+'_width').onchange       = null;
    document.getElementById(prefix+'_height').onchange      = null;
    document.getElementById(prefix+'_crop_width').onchange  = null;
    document.getElementById(prefix+'_crop_height').onchange = null;
    document.getElementById(prefix+'_top').onchange    = null;
    document.getElementById(prefix+'_left').onchange   = null;
  }
}

/**
 * Gecko browsers use a java applet to actually load the image from
 * disk and inline it in the page.  This function is called from the
 * applet to discover the filename that needs to be loaded
 */
function get_image_filename(prefix) {
  return document.getElementById(prefix+'_upload').value;
}

/**
 * Gecko browsers use a java applet to actually load the image from
 * disk and inline it in the page.  This function is called from the
 * applet to set the src of the image to an inline encoded value.
 * Setting the src of the image in this way will NOT work in Internet
 * Explorer
 */
function image_manualload(prefix,data) {
  document.getElementById(prefix+'_preview').src = data;
}

/**
 * Callback function triggered when the image file name in the file
 * upload is changed.  
 * 
 * 1) This function has to make the image preview visible (prior to
 * the preview file being loaded) because Internet Explorer will not
 * work out the size of the image if its not visible at the point its
 * loaded.  
 *
 * 2) The width and height attributes have to be removed from the
 * image in order to make the browser recalculate the size of a new
 * image when another has already been previewed.
 *
 * 3) The onload function for the image is registered.  This gets a
 * callback once the image is loaded (and the width and height have
 * been calculated).  You must wait until this callback is called
 * before trying to do anything with the image or you might get a
 * race condition with the browser's image loading engine.
 *
 * 4) If the element "netscape" exists we know the browser implements
 * the netscape security model and so we trigger the java applet in
 * the page to load the image from disk and write it back into the
 * page.  (The netscape security model prohibits setting an img src to
 * a local file)
 *
 * 5) Otherwise, we assume that we are allowed to load the local file
 * and set the img src as we wanted.  
 *
 * 6) Finally, if the preset dropdown of fixed sizes exists in the
 * page, make sure it is set to the first element (which corresponds
 * to a user defined size)
 */
function image_onchange(prefix) {

  var filename = document.getElementById(prefix+'_upload').value;

  tools_visibility_set(prefix,true);

  var image = document.getElementById(prefix+'_preview');
  image.removeAttribute("width");
  image.removeAttribute("height");
  image.removeAttribute("src");

  image.onload = function() { image_init(prefix); };

  if (typeof netscape != "undefined") {
    document.getElementById('image_load_applet').loadImage(prefix);
  }
  else {
    document.getElementById(prefix+'_preview').src = filename;
  }

  var preset = document.getElementById(prefix+'_preset');
  if (preset.selectedIndex) {
    preset.selectedIndex = 0;
  }

}

/**
 * Callback to toggle the visibility of the image editing tools based
 * on the clickable link on the page.  The text of the link is
 * inspected to determine if the tools should be visible or hidden.
 */ 
function image_visibility_toggle(prefix) {
  var toggle = document.getElementById(prefix+'_showhide');
  var container = document.getElementById(prefix+'_container');
  if (toggle.innerHTML == '(hide preview)') {
    toggle.replaceChild(document.createTextNode('(show preview)'),toggle.firstChild);
    container.style.visibility = 'hidden';
    container.style.display = 'none';
  }
  else {
    toggle.replaceChild(document.createTextNode('(hide preview)'),toggle.firstChild);
    container.style.visibility = 'visible';
    container.style.display = 'block';
    container.style.position = 'relative';
  }
}

/**
 * Callback once the preview image is loaded by the browser.  If the
 * image width is a bad value then we assume that the file requested
 * was not a valid image and so hide the tools again.  Otherwise we:
 * 
 * 1) Store the original dimensions of the image in the image_details vector
 * 2) Rescale the image to have a width of 200 pixels
 * 3) Update the draggable crop area to cover the whole image
 * 4) Update the crop input boxes to the new draggable crop size
 * 5) Update the scale input boxes to the new image size
 */
function image_init(prefix) {
  var image = document.getElementById(prefix+'_preview');

  if (image.width <= 0) {
    tools_visibility_set(prefix,false);
    return false;
  }

  var maxlen = (image.width > image.height ? image.width : image.height);
  var factor = 200/maxlen;
  image_details[prefix+'_width']  = image.width;
  image_details[prefix+'_height'] = image.height;

  image_resize_absolute(prefix,factor);

  var image_crop = document.getElementById(prefix+'_crop');
  image_crop.style.width  = image.width + "px";
  image_crop.style.height = image.height + "px";
  image_crop.style.left   = "0px";
  image_crop.style.top    = "0px";

  crop_divToInput(prefix);
  scale_imageToInput(prefix);     
  preset_onchange(prefix);
}

/**
 * Applies the curent values in the scale input boxes to the image and
 * also maintains the image aspect ratio.  The field parameter
 * indicates whether the width or height is the altered value.
 */
function scale_inputToImage(prefix,field) {
  var altered = document.getElementById(prefix+'_'+field);
  var dependant = document.getElementById(prefix+'_'+ (field == 'width' ? 'height' : 'width'));
  var image = document.getElementById(prefix+'_preview');
  
  if (altered.value != parseInt(altered.value)) {
    // if its not an int then overwrite it again with the value from the image
    altered.value = (field == 'width' ? image.width : image.height);
  }
  else {
    var factor = altered.value / image_details[prefix+'_'+ field];
    image_resize_absolute(prefix,factor);
  }
}

/**
 * Copy the current image size values in the preview to the scale
 * input boxes.
 */
function scale_imageToInput(prefix) {
  var image = document.getElementById(prefix+'_preview');
  document.getElementById(prefix+'_width').value  = image.width;
  document.getElementById(prefix+'_height').value = image.height;
}

/**
 * Copy the current draggable crop area parameters into the crop input
 * boxes
 */
function crop_divToInput(prefix) {
  var image_crop = document.getElementById(prefix+'_crop');
  document.getElementById(prefix+'_crop_width').value  = parseInt(image_crop.style.width);
  document.getElementById(prefix+'_crop_height').value = parseInt(image_crop.style.height);
  document.getElementById(prefix+'_left').value        = parseInt(image_crop.style.left);
  document.getElementById(prefix+'_top').value         = parseInt(image_crop.style.top);
}

/**
 * Resize the image by a factor relative to the current preview size.
 * The new size is worked out by multiplying the current width by this
 * factor.  
 *
 * A new scale factor is derived which converts the _original_ image
 * width to this new width and the call passed on to
 * image_resize_absolute.  This is necessary to avoid distorting the
 * image aspect ratio due to dilution of precision in the image
 * dimensions.
 */
function image_resize_relative(prefix,factor) {
  var image = document.getElementById(prefix+"_preview");
  var new_width = image.width * factor;
  var new_factor = new_width / image_details[prefix+'_width'];

  image_resize_absolute(prefix,new_factor);
}


/**
 * Resize the image by a factor relative to the original image size.
 * The new size is calculated by multiplying the original dimensions
 * by the factor provided.  
 *
 * 1) If we are using a preset crop size and the new size is too small
 * to contain the crop dialog then we change the scale factor to fix
 * and call ourselves again.
 *
 * 2) Resize the image to the new size and copy the image values back
 * to the scale input boxes
 *
 * 3) If the crop is of variable size and it falls out of the bounds
 * of the newly resized image then move/shrink it.
 *
 * 4) Update the position of the resize dragger so that it sits on the
 * bottom corner of the image.
 */
function image_resize_absolute(prefix,factor) {
  var image = document.getElementById(prefix+'_preview');
  var preset = document.getElementById(prefix+'_preset');

  var crop_width  = document.getElementById(prefix+'_crop_width');
  var crop_height = document.getElementById(prefix+'_crop_height');
  var crop_top    = document.getElementById(prefix+'_top');
  var crop_left   = document.getElementById(prefix+'_left');

  var new_width   = image_details[prefix+'_width'] * factor;
  var new_height  = image_details[prefix+'_height'] * factor;

  if (preset.selectedIndex >= 0 && preset.options[preset.selectedIndex].value != 'User') {
    if (new_width  < crop_width.value) {
      return image_resize_absolute(prefix,crop_width.value / image_details[prefix+'_width']);
    }
    
    if (new_height < crop_height.value) {
      return image_resize_absolute(prefix,crop_height.value / image_details[prefix+'_height'])
    }
  }
   
  image.width  = new_width;
  image.height = new_height;
  scale_imageToInput(prefix);

  if (Number(crop_left.value) + Number(crop_width.value) > image.width) { 
    crop_left.value = image.width - crop_width.value;
    if (crop_left.value < 0) {
      crop_left.value  = 0;
      crop_width.value = image.width;
    }

  }
  if (Number(crop_top.value) + Number(crop_height.value) > image.height) { 
    crop_top.value = image.height - crop_height.value; 
    if (crop_top.value < 0) {
      crop_top.value    = 0;
      crop_height.value = image.height;
    }
  }      

  var cont = document.getElementById(prefix+'_resize');
  cont.style.left = (image.width - 10) + "px";
  cont.style.top = (image.height - 10) + "px";
  crop_inputToDiv(prefix);
}


/**
 * Apply the crop input box values to the draggable div for the crop.
 * If this means that the crop won't fit in the image then resize the
 * image accordingly.
 */
function crop_inputToDiv(prefix) {
  var image_crop = document.getElementById(prefix+'_crop');   

  var crop_width  = parseInt(document.getElementById(prefix+'_crop_width').value) || 0;
  var crop_height = parseInt(document.getElementById(prefix+'_crop_height').value) || 0;
  var crop_top    = parseInt(document.getElementById(prefix+'_top').value) || 0;
  var crop_left   = parseInt(document.getElementById(prefix+'_left').value) || 0;


  var image = document.getElementById(prefix+'_preview');
  var image_width  = image.width;
  var image_height = image.height;

  var need_resize = (crop_width > image_width) || (crop_height > image_height);
  var factor;

  if (need_resize) {
    factor = Math.max(crop_width / image_details[prefix+'_width'], crop_height / image_details[prefix+'_height']);
    image_width  = image_details[prefix+'_width'] * factor;
    image_height = image_details[prefix+'_height'] * factor;
  }

  if (crop_left + crop_width > image_width) {
    crop_left = image_width - crop_width;
  }
   
  if (crop_top + crop_height > image_height) {
    crop_top = image_height - crop_height;
  }

  image_crop.style.width  = crop_width + "px";
  image_crop.style.height = crop_height + "px";
  image_crop.style.top    = crop_top + "px";
  image_crop.style.left   = crop_left + "px";

  if (need_resize) {
    image_resize_absolute(prefix,factor);
  }
}

/**
 * Callback for when the drop down list of preset sizes is
 * changed. 
 *
 * 1) Parse the value to work out the size or if it is a user
 * defined value.
 * 
 * 2) Write the new size into the crop input box and then call the
 * appropriate function to copy these values to the div.
 *
 * 3) Set the visibility of the draggable corners of the
 * crop box according to if we have chosen a fixed size or not.  
 */ 
function preset_onchange(prefix) {
  var preset = document.getElementById(prefix+"_preset");
  var value = preset.options[preset.selectedIndex].value;

  var split = value.split('x');
  var visibility = 'visible';

  var crop_width  = document.getElementById(prefix+'_crop_width');
  var crop_height = document.getElementById(prefix+'_crop_height');

  if (split.length == 2) {
    crop_width.value     = split[0];
    crop_height.value    = split[1];

    crop_inputToDiv(prefix);
    visibility = 'hidden';
  }

  document.getElementById(prefix+'_tl').style.visibility=visibility;
  document.getElementById(prefix+'_tr').style.visibility=visibility;
  document.getElementById(prefix+'_bl').style.visibility=visibility;
  document.getElementById(prefix+'_br').style.visibility=visibility;
}

/**
 * Callback function for end of dragging - degregister the mouse handler.
 */
function end_drag() {
  document.onmousemove=null;
  activePrefix='NONE';
  return false;
}

/**
 * Callback function for dragging the crop area.
 *
 * - activePrefix describes the current image under manipulation (in
 * case we have more than one on a page).  
 *
 * - edgeMove describes which edges we are moving - a resize only
 * moves two edges at a time whereas a move moves all four.
 *
 * - xOffset,yOffset contain the position of the original mouse down
 * minus the top left position of the crop drag.  Therefore the
 * current mouse position minus the offset values gives the original
 * top left position + the total change in position - i.e. the
 * absolute position that the top left of the crop drag would be at
 * now if we were moving it.
 *
 * - wStart,hStart contain the original width and height of the crop
 * box at the beginning of the drag.  Therefore the absolute position
 * computed above + the start dimensions gives the position that the
 * bottom right corner would be at if we were moving that
 *
 * 1) When moving the whole crop area we just set its top and left style
 * attributes based on the position above.  Limit the proposed move to
 * the dimensions of the preview image.
 *
 * 2) When resizing we move each of top, left, bottom and right.
 * bottom and right don't really exist as a style attribute but we use
 * them for convenience and then derive the correct width and height
 * values later.  Limit the proposed resize to the dimensions of the
 * image.  Also limit the resize so that width and height are greater
 * than zero.  Do this by checking if left >= right and then
 * rectifying the situation based on which edge was moved - if you
 * moved the left edge to achieve this we have to change the left
 * variable but if you moved the right edge we have to change the
 * right variable.  If you don't make this check then the crop box
 * will move accross the image preview when you take the size down to
 * zero in one direction but remain static if you do the same thing in
 * the other direction.
 *
 * 3) Finally, we copy the new draggable crop area size in to the
 * input boxes
 */
function crop_dragging(event){
  if (!event) { event = window.event; }

  if (activePrefix != "NONE") {
    // move:
    //   Edge    Left   Width    Top    Height
    //   ALL      +x      0       +y       0 
    //   TL       +x     -x       +y      -y
    //   TR	   0     +x       +y      -y
    //   BR	   0	 +x        0      +y
    //   BL       +x     -x        0      +y

    // 1 = Left
    // 2 = Top
    // 4 = Right
    // 8 = Bottom

    var xPos = event.clientX - xOffset;
    var yPos = event.clientY - yOffset;

    var dragElement = document.getElementById(activePrefix+"_crop");

    var left   = parseInt(dragElement.style.left);
    var top    = parseInt(dragElement.style.top);
    var width  = parseInt(dragElement.style.width);
    var height = parseInt(dragElement.style.height);
    var right  = left + width;
    var bottom = top + height;

    container = document.getElementById(activePrefix+"_preview");
    var cont_width  = parseInt(container.width);
    var cont_height = parseInt(container.height);

    if (edgeMove == 15) {
      left = xPos;
      top = yPos;

      if (left < 0)                   { left = 0;}
      if (top < 0)                    { top  = 0;}
      if (left + width > cont_width)  { left = cont_width - width; }
      if (top + height > cont_height) { top  = cont_height - height; }

      dragElement.style.left = left + "px";	   
      dragElement.style.top = top + "px";	   
    }
    else {
      if (edgeMove & 1) { left   = xPos; }
      if (edgeMove & 2) { top    = yPos; }
      if (edgeMove & 4) { right  = xPos + wStart; }
      if (edgeMove & 8) { bottom = yPos + hStart; }
	   
      if (left < 0)             { left = 0; }
      if (top < 0)              { top = 0;  }
      if (right > cont_width)   { right = cont_width; }
      if (bottom > cont_height) { bottom = cont_height; }

      if ((edgeMove & 1) && (left >= right)) { left = right-1; }
      if ((edgeMove & 4) && (left >= right)) { right = left+1; }
      if ((edgeMove & 2) && (top >= bottom)) { top = bottom-1; }
      if ((edgeMove & 8) && (top >= bottom)) { bottom = top+1; }

      dragElement.style.left   = left + "px";	   
      dragElement.style.top    = top + "px";	   
      dragElement.style.width  = (right - left) + "px";	   
      dragElement.style.height = (bottom - top) + "px";	   
    }

    crop_divToInput(activePrefix);
  }
}

/**
 * Initialise the dragging of the crop area.  This callback is
 * triggered on a mouse down event on one of the four corners of the
 * crop box.  The edge value passed in is a bit mask indicating which
 * edges to move.  If we are at a preset size then override the edge
 * value with a move instead.
 *
 * The definition and use of xOffset,yOffset,wStart,hStart are given
 * in the comment for crop_dragging.
 *
 * Register crop_dragging for mousemove and end_drag for mouseup on
 * the whole document so that we don't have to keep the mouse inside
 * the image preview to move the crop around.
 */
function crop_drag_init(prefix,edge,event){
  if (!event) { event = window.event; }
  if (activePrefix != "NONE") { return true; }
  activePrefix = prefix;
  edgeMove = edge;

  var dragElement = document.getElementById(prefix+"_crop");
  var image = document.getElementById(prefix+"_preview");
  var preset = document.getElementById(prefix+"_preset");
  if (preset.selectedIndex && preset.options[preset.selectedIndex].value != 'User') {edgeMove = 15;}

  xOffset = event.clientX - parseInt(dragElement.style.left);
  yOffset = event.clientY - parseInt(dragElement.style.top);
  wStart  = parseInt(dragElement.style.width);
  hStart  = parseInt(dragElement.style.height);

  document.onmousemove=crop_dragging;
  document.onmouseup=end_drag;
  return false;
}

/**
 * Initialise dragging of the resize box.  
 *
 * The definitions and use of xOffset,yOffset,wStart and hStart are
 * given in the comment for resize_dragging.
 *
 * Register resize_dragging for mousemove and end_drag for mouseup on
 * the whole document so that we don't have to keep the mouse inside
 * the image preview to do resizing.
 */
function resize_drag_init(prefix,event) {
  if (!event) { event = window.event; }
  if (activePrefix != "NONE") { return true; }

  activePrefix = prefix;
  
  var dragElement = document.getElementById(prefix+"_resize");
  var image = document.getElementById(prefix+"_preview");
  xOffset = event.clientX;
  yOffset = event.clientY;
  wStart  = image.width;
  hStart  = image.height;

  document.onmousemove=resize_dragging;
  document.onmouseup=end_drag;

  return false;
}

/**
 * Mousemove callback for resizing the image.  
 *
 * - activePrefix indicates which image we are manipulating (in the event that there might be more than one on the page)
 *
 * - xOffset, yOffset contain the position that the mouse movement
 * began at.  Therefore subtracting these from the new position tells
 * us how much we have moved by since we began the drag
 *
 * - hStart, wStart contain the original width and height of the image
 * at the beginning of the drag.  Therefore we add these to the
 * distance we've moved to get the new size.
 *
 * 1) work out which of the changes to width and height would make the
 * biggest impact on image size (remember that we preserve the aspect
 * ratio)
 *
 * 2) call the resize function to do the resize
 */
function resize_dragging(event) {

  if (!event) { event = window.event; }

  if (activePrefix != "NONE") {  

    var new_height = hStart + (event.clientX - xOffset);
    var new_width  = wStart + (event.clientY - yOffset);

    var image = document.getElementById(activePrefix+'_preview');
    
    var factor;
    if (new_height > new_width) {
      factor = new_height / image_details[activePrefix+'_height'];
    }
    else {
      factor = new_width / image_details[activePrefix+'_width'];
    }
    
    image_resize_absolute(activePrefix,factor);

    return false;

  }
}
 
/**
 * Add the applet for remoting the image load in gecko browsers to the
 * page if required.
 */
function add_applet(baseURL) {
  if ((typeof netscape != "undefined") &&
      (!document.getElementById("image_load_applet"))) {
    document.write('<applet width="1" height="1" code="uk.co.investsolutions.applet.proxyload.ProxyLoad" id="image_load_applet" archive="'+baseURL+'/binary/static/applet.proxyload.jar" MAYSCRIPT></applet>');
  }
}
