/* 
   Note from Raul Parolari, March 2007:
   These file contains 2 common sets of functions: 
   1) the first is generic drawing of boxes; took functions from great David
      Flanagan's Javascript book. Changed and extended the API to:

      - pass in some cases a hash (rather than long lists of parameters)
      - introduce some (non-standard) properties; eg, width_nr and height_nr,
        which return numeric values (without 'px'), useful at run-time (without
	    prefixing 'parseInt()' all over).  
      - draw arrows on tip of lines   
      - add debug info to identify element with problems. 

    2) the second is a set of primitives to draw the objects of our animations
       They could go into another file (the previous should
	   be renamed lib_kernel_draw, and this one lib_app_draw).             
*/
   
/**
 * This constructor function creates a div element into which a
 * CSS-based figure can be drawn.  Instance methods are defined to draw
 * lines and boxes and to insert the figure into the document.
 *
 * The constructor may be invoked using two different signatures:

 * 1) with 4-6 parameters, for an absolute positioned drawing:
 *    
 *    new CSSDrawing(x, y, width, height, cssClass, id)    
 *
 * Here a <div> is created with position:absolute at specified position and size.
 *
 * 2) with 2-4 parameters, for a relative positioned drawing: 
 * 
 *    new CSSDrawing(width, height, cssClass, id)  
 * 
 * In this case, the created <div> has the specified width and height
 * and uses 'position:relative' (which is required so that the child
 * elements used to draw lines and boxes can use 'absolute' positioning).
 * 
 * In both cases, the cssClass and id arguments are optional.  If specified, 
 * they are used as the value of the class and id attributes of the created
 * <div> and can be used to associate CSS styles, such as borders with
 * the figure.

 * RP: my typical call: 
 * drawing = new CSSDrawing(CANVAS_WIDTH, CANVAS_FULL_HEIGHT, 'cl_drawing');

 * NOTICE: the div created by the Constructor CSSDrawing is a PROPERTY (aptly
 *	       named 'div') of the object created. 
 *	       Thus, it will be accessed in this file as 'this.div'.  
 */

var conn_debug=0;

function CSSDrawing(/* variable arguments */) {
    /* Create and remember the <div> element for the drawing

    */
    var d = this.div = document.createElement("div");
    var next;

    // Figure out whether we have four + numbers or two numbers, sizing and 
    // positioning the div appropriately
    if (arguments.length >= 4 && typeof arguments[3] == "number") {
	    /* left, top, width, height */
        d.style.position = "absolute";
        // now set CSS
        d.style.left   = arguments[0] + "px";
        d.style.top    = arguments[1] + "px";
        d.style.width  = arguments[2] + "px";
        d.style.height = arguments[3] + "px";
        // allow user to retrieve width, height wout 'px'
        d.style.width_nr  = arguments[2];
        d.style.height_nr = arguments[3];
        next = 4;
    }
    else {  /* width, height */ 
        d.style.position = "relative"; // This is important
        d.style.width  = arguments[0] + "px";
        d.style.height = arguments[1] + "px";
        // allow user to retrieve width, height wout 'px'
        d.style.width_nr  = arguments[0];
        d.style.height_nr = arguments[1];

        next = 2; // index of next arg to be processed
    }

    // Set class and id attributes if they were specified.
    if (arguments[next])   d.className = arguments[next];
    if (arguments[next+1]) d.id        = arguments[next+1];
   
    //alert('canvas created=' + d.className);
}  // CSSDrawing

CSSDrawing.prototype.get_drawing_width = function() {
   return parseInt(this.div.style.width);
}
CSSDrawing.prototype.get_drawing_height = function() {
   return parseInt(this.div.style.height);
}

/** Add the drawing to the document as a child of the specified container */
CSSDrawing.prototype.insert = function(container) {
    if (typeof container == "string") 
        container = document.getElementById(container);
    container.appendChild(this.div);
}

/* Add the drawing to the document by replacing the specified element:
    replacedNode = parentNode.replaceChild(newChild, oldChild);
*/
CSSDrawing.prototype.replace = function(elt) {
    if (typeof elt == "string") elt = document.getElementById(elt);
    elt.parentNode.replaceChild(this.div, elt);
}

/* this is to recreate (when user reloads page) the page exactly as it was the
   first time; so we reverse the operation done by above 'replace', and we
   replace 'this.div' with with the element given; to do this in the DOM, we
   ask the parent of 'this.div' to do it. 
*/
CSSDrawing.prototype.reinstate_original_holder = function(id) {
   var info = 'CSSDrawing.prototype.reinstate_original_holder: ';
   //this.div.parentNode.removeChild(this.div);
   var elem = document.createElement('div');
   elem.id = id;
   this.div.parentNode.replaceChild(elem, this.div);

   //alert(info + elem + ', id=' + elem.id);
}

/**
 * Add a box to the drawing: box position is 'absolute' (ie the left and top
 * properties are distances from top left corner of the drawing). The element
 * created is appended to the drawing (ie, to  its 'div' property).
 * 
 * x, y, w, h:    specify the position and size of the box.
 * content:       a string of text or HTML to appear in the box
 * cssClass, id: optional class and id values for the box.  Useful to
 *                associate styles with the box for color, border, etc.
 * Returns: The <div> element created to display the box
 */
CSSDrawing.prototype.box = function(dscr) {

    var d = document.createElement("div");
    if (dscr.cssClass) d.className = dscr.cssClass;
    if (dscr.id)        d.id = dscr.id;

    //alert("CSSDrawing.prototype.box called");
    d.style.position = "absolute";
    d.style.left     = dscr.x + "px";
    d.style.top      = dscr.y + "px";
    d.style.width    = dscr.w + "px";
    d.style.height   = dscr.h + "px";
    d.innerHTML      = dscr.content;

    /* append div with grid into 'this.div' (division created in CSSDrawing) */
    this.div.appendChild(d);
    return d;
};

/**
 * Add a horizontal line to the drawing.
 * 
 * x, y, width:   specify start position and width of the line
 * cssClass, id: optional class and id values for the box.  At least one
 *                must be present and must specify a border style which
 *                will be used for the line style, color, and thickness.
 * Returns: The <div> element created to display the line
 */

 /* 
    [Raul Parolari:] 
    Drawing horizontal/vertical:

    Notice: what is a 'line'? it is a 'div', whose props depend on line orientation:
    - an horizontal line is a 'div' with 'height' of 1px (which by itself
	  builds an invisible flat rectangle)
    - a vertical one is a div with width of 1px
    In both cases we zero all the border properties but the top one, borderTopWidth,
	which must be set to = 1px in Css, eg:
      div#content .cl_line_M2p_M1p { border: solid black 1px  } 
      div#content .cl_line_M1p_M1  { border: dotted black 1px  }

    We append the line to the main div (this.div, or from outside, drawing.div).
    This means that when we need to move boxes and then redraw connections 
    between them, we can first find the segments based on their id or class, 
    and delete them as children of the main drawing, with:
          drawing.div.removeChild(line_el)

    Then we can move the boxes and redrawing the connections.
	So, we really have a dynamic canvas!
 */

CSSDrawing.prototype.horizontal = function(x, y, width, cssClass, id) {

    var info = 'CSSDrawing.horizontal: cssClass=' + cssClass +  
               ', x=' + x + ', y=' + y + ', width=' + width + ' : ';
    // if (cssClass != 'grid') alert(info + this.div);

    if (width < 0) { 
       alert(info + 'width < 0! : resetting it to 20!');
       width = 20;
    }
    else if (conn_debug && cssClass != 'grid') alert(info);
    
    var d = document.createElement("div");
    // script Css 
    if (cssClass) d.className = cssClass;
    if (id) d.id = id;
   
    // Css properties
    d.style.position = "absolute";
    d.style.left   = x + "px";
    d.style.top    = y + "px";
    d.style.width  = width + "px";
    d.style.height = 1 + "px";
    // set 3 borders with null width; borderTopWidth will have size specified in Css
    d.style.borderLeftWidth = d.style.borderRightWidth =
                              d.style.borderBottomWidth = "0px";
        
    /* append division into main drawing division (this.d) */
    //alert(d.style.top);
    this.div.appendChild(d);
    return d;
};

/**
 * Add a vertical line to the drawing.
 * See horizontal() for details.
 */
CSSDrawing.prototype.vertical = function(x, y, height, cssClass, id) {

    var info = 'CSSDrawing.vertical: cssClass=' + cssClass +  
               ', x=' + x + ', y=' + y + ', height=' + height + ' : ';

    if (height < 0) {
       alert(info + 'height < 0! : resetting it to 20');
       height = 20;
    }
    else if (conn_debug && cssClass != 'grid') alert(info);

    var d = document.createElement("div");
    if (cssClass) d.className = cssClass;
    if (id) d.id = id;
    
    d.style.position = "absolute";
    d.style.left   = x + "px";
    d.style.top    = y + "px";
    d.style.width  = 1 + "px";
    d.style.height = height + "px";
    d.style.borderRightWidth = d.style.borderBottomWidth =
                               d.style.borderTopWidth    = "0px";
    this.div.appendChild(d);
    return d;
};

/* 
    [Raul Parolari]
    How to put an arrow on the tip of a line (funny how this took time to
	figure..) ?
	We draw an arrow as 2 sets of 5 pixels each: the pixels are drawn as 
    'lines of width=1px' at 45 degrees from the point given (backing away for 
     arrow '>', or going forward for arrow '<'). 

    Something like (with 3 points instead of 5):
		     .                  .
		       .              .
		  GT     .          .      LT
		       .              .
		     .                  .
		
	So every arrow is really composed of 5+5 divisions! We of course use the 
	same class for each 'point', so we can find and move the whole set as a 
	whole. 
    Notice: the arrow is drawn in one shot (ie: we don't introduce a scheduler
    to show one pixel at the time!).
*/
CSSDrawing.prototype.arrow = function(x, y, sense, cssClass) {

    var info = 'CSSDrawing.horizontal: cssClass=' + cssClass +  
               ', x=' + x + ', y=' + y + ', width=' + 1+ 'sense=' + sense +' : ';

    if (sense == 'GT') {
        for (var i=0; i < 5; i++) {
           drawing.horizontal(x-i, y+i, 1, cssClass);
        }
        for (var i=0; i < 5; i++) {
           drawing.horizontal(x-i, y-i, 1, cssClass);
       }
    }
    else if (sense == 'LT') {
       for (var i=0; i < 5; i++) {
         drawing.horizontal(x+i, y+i, 1, cssClass);
       }
       for (var i=0; i < 5; i++) {
         drawing.horizontal(x+i, y-i, 1, cssClass);
       }
    }
    else if (sense == 'UP') {        //  /\
       for (var i=0; i < 5; i++) {                   
          drawing.horizontal(x-i, y+i, 1, cssClass); 
       }                                             
       for (var i=0; i < 5; i++) {                   
          drawing.horizontal(x+i, y+i, 1, cssClass); 
       }                                             
    }
    else if (sense == 'DOWN') {       //  \/
       for (var i=0; i < 5; i++) {                   
          drawing.horizontal(x-i, y-i, 1, cssClass); 
       }                                             
       for (var i=0; i < 5; i++) {                   
          drawing.horizontal(x+i, y-i, 1, cssClass); 
       }                                             
    }
    else (alert(info + "sense is not GT|LT|UP|DOWN"));
};


        /****************** END OF CLASS CSSDrawing ******************/


/* 
   Interface to application: the html page has:

   </div>
     <div id="placeholder_1"></div>
  </div>

   The app usually does:
     id_holder = "placeholder_1";
     create_canvas(schedule.id_holder);

   create_canvas instantiates a drawing, and replaces in the DOM the 
   original html element (placeholder_1) with the new one created
    
*/

function create_canvas(id_placeholder, new_id) {
   var info="create_canvas(" + id_placeholder + ", " + new_id +"): ";

   //alert(info);
   if (! drawing) {
	
      drawing = new CSSDrawing(CANVAS_WIDTH, CANVAS_FULL_HEIGHT, 
	                           'cl_drawing', new_id);
      drawing.replace(id_placeholder);    // insert canvas in document
   }
}

/*
   the prior version iterated on the members of the childNodes of the element;
   but the List of child nodes is alive (therefore each time we remove one, the
   list shrinks, so the real 'next' element has the same index...). 
   
   The best is: while the element has children, remove the first one
   Notice: recursion is not needed; if a child C has children, the removal 
   of C removes its children also
*/

function remove_children_of(el) {
   var info="remove_children_of element " + el.name ? el.name : "";
 
   while(el.hasChildNodes()) el.removeChild(el.firstChild);

   //alert(el + ' children removed');
}

function reinit_canvas(id_to_reinstate) {
   var info = 'reinit_canvas: ';
   adm_coord_boxes.go_to_initial_state();

   // if the page has just been loaded, drawing does not exist; if it exists,
   // remove all children
   if (drawing && drawing.div) {
	  var el = drawing.div;
      remove_children_of(el);
      //alert(info);
      draw_Grid(0, 0, 20, 20);
   }
   else {
      alert(info + 'oops, did not find drawing or drawing.div');
   }
} // reinit_canvas
/************************************************************/

function old_remove_canvas(id_to_reinstate) {
   var info = 'remove canvas: ';
   adm_coord_boxes.go_to_initial_state();

   if (drawing) {
      drawing.reinstate_original_holder(id_to_reinstate);  
   }
} // remove_canvas


function draw_Grid(x, y, dx, dy) {

   var width  = drawing.get_drawing_width();
   var height = drawing.get_drawing_height();

   for(var x0 = x; x0 < x + width; x0 += dx) 
      drawing.vertical(x0, y, height, "grid");

   for(var y0 = y; y0 < y + height; y0 += dy)
      drawing.horizontal(x, y0, width, "grid");
} // drawGrid



/*
     Other functions, less pure and generic, but useful to our drawing
     applications.
     If this section grows, they could be put in a separate file (but for now we 
	 want to avoid a myriad of files to download in browser).
*/

var switch_animation = {
   on : 1,
	
   is_on    : function() { return  this.on; },
   is_off   : function() { return !this.on; },

   turn_off : function() { this.on = 0 },
   turn_on  : function() { this.on = 1 },

   end: null
}


function convert_modName_to_title(modName) {
	
   // discard everything since % included (given to have unique hash key
   // but we don't want it displayed in title)
   return modName.replace(/%.*$/, '');

} // convert_modName_to_title

	
function calc_frame_rate(distance, notify) {
   var info = 'calc_frame_rate:  distance=' + distance + '; ';

   var pix_per_frame = get_pix_per_frame();
   /* assume all distances are multiples of ppf 
      (to be done: if distance not multiple, determine a sensible ppf factor of 
	   distance..)
   */
   var numFrames = (distance/pix_per_frame).toFixed();
   var numPixels = distance/numFrames;

   //alert('frames: ' + numFrames + ', pixels: ' + numPixels );
   return { frames: numFrames, pixels: numPixels };
} // calc_frame_rate


function build_class_dscr(className, type, methods, options) {
	
   // this function works both for CLASS and META(class): when needed, 'type'
   // is checked: TYPE_CLASS vs TYPE_META

   if (!options) options = { };

   var info = 'build_class: className= ' + className + ', type= ' + type + ' : ';
   
   // MetaClass of Class A is named internally Ap, but externally it is A'
   // (as per Pickaxe). Therefore replace suffix.
   className    = (type == TYPE_CLASS)? className  : className.replace(/p$/, "'");
   var cssClass = type == TYPE_CLASS? 'boxClass' : 'boxMeta';

   // fixed part of content
   var arr_title_cont = get_classBox_content(className, type, methods);
   var title   = arr_title_cont[0];
   var content = arr_title_cont[1];

   var coord = adm_coord_boxes.gen_next_coord(className, type);
   //alert(coord.x + ', ' + coord.y);

   var opt_w  = options['width']? options['width'] : BOX_WIDTH;
   var opt_x  = coord.x + BOX_WIDTH - opt_w;

   var dscr = { 
                  x: opt_x,  y: coord.y,  
                  w: opt_w,
                  h: BOX_HEIGHT, 
                  name:       className,   // 'ruby class' : eg; A, A'
                  title:      title,
                  id:        'id_'    + name,
                  cssClass:   cssClass,
                  content:    content,
                  conn   :    { },
                  elem:       null
               };

   // Connections:    (inst -->)   class   (--> superclass)
   //  conn from instance, arrives at left edge of box (+ small vert offset)

   var x_inbox = 0;   // let the ones that need (outg conn) override it

   dscr.conn.inc_from_inst  =  function() {
                              var style = dscr.elem.style;
                              var x     = parseInt(style.left);
                              var y     = parseInt(style.top)  + SEG_OFFSET_TOP;
                              return [x, y, x_inbox];
                           }

   // conn from previous class, arrives at left edge, at 'super' level 
   dscr.conn.inc_from_super =  function() {
                              var style = dscr.elem.style;
                              var x     = parseInt(style.left);
                              var y     = parseInt(style.top) + Y_SUPER;
                              //alert(y);
                              if (conn_debug) alert(info + ', inc_from_super: x=' 
                                 + x + ', left= ' + style.left + ', y=' + y);

                              return [x, y, x_inbox];
                           }

   // conn from klass ptr in Class, arrives to top of meta class, right edge
   dscr.conn.inc_from_klass  =  function() {
                              var style = dscr.elem.style;
                              // ensure vertical connection, leaving x null
                              // parseInt(style.left) + parseInt(style.width);
                              var x     = null; 
                              var y     = parseInt(style.top)  + SEG_OFFSET_TOP;
                              return [x, y, x_inbox];
                           }
               
   // leaves at super, right edge (horiz towards super next class/meta)
   dscr.conn.out_to_super =  
               function() {
                  // coordinates right edge of the box for super
                  var style = dscr.elem.style;
                  var x     = parseInt(style.left) + parseInt(style.width);
                  x_inbox   = LEN_SEG_IN_BOX;
                  var y     = parseInt(style.top)  + Y_SUPER;
                  if (conn_debug) alert(info + ', out_to_super: x(start)=' + x +  
                                    ', left= ' + style.left + ', y=' + y);
                  return [x, y, x_inbox];
            }

   // leaves class at klass ptr level (goes down to corresponding meta class)
   dscr.conn.out_to_meta =         
               function() {
                  // coordinates right edge of box
                  var style = dscr.elem.style;
                  var x     = parseInt(style.left) + parseInt(style.width);
                  var y     = parseInt(style.top)  + Y_KLASS;
                  if (conn_debug) alert(info + ', out_to_super: x(start)=' + x +  
                                    ', left= ' + style.left + ', y=' + y);
                  x_inbox = LEN_SEG_IN_BOX;  // x offset (inside box) 
                  return [x, y, x_inbox];
               }

   dscr.conn.out_to_mhm_mod =  
               function() {
                  // coordinates left edge of box
                  var style = dscr.elem.style;
                  var x     = parseInt(style.left); //+ parseInt(style.width);
                  x_inbox   = 5; // LEN_SEG_IN_BOX; // now it leaves from left
                  var y     = parseInt(style.top)  + Y_MHM;
                  if (conn_debug) alert(info + ', out_to_super: x(start)=' + x +  
                                    ', left= ' + style.left + ', y=' + y);
                  return [x, y, x_inbox];
               }

      return dscr;
}  // build_class_dscr


function remove_connections_between_classBoxes(left_box_name, right_box_name) {
   
   // the class & meta (title should be modified)
   var cssClass_class, cssClass_meta, arr_div, arr_seg, arr_meta;

   arr_div  = document.getElementsByTagName('div');
   // form cssClass name of segments
   cssClass_class = 'cl_line_' + left_box_name + '_'  + right_box_name;
   cssClass_meta  = 'cl_line_' + left_box_name + 'p_' + right_box_name + 'p';

   // assumptions: 1) segments are 'divs', so restrict search inside them
   //              2) all segments are children of drawing.div
   arr_seg  = lib_find_subset_class(arr_div, cssClass_class);
   arr_meta = lib_find_subset_class(arr_div, cssClass_meta);
   arr_seg  = arr_seg.concat(arr_meta);

   // remove each segment
   for (var i=0; i<arr_seg.length; i++) {
      //alert(info + 'removing ' + arr_seg[i]);
      drawing.div.removeChild(arr_seg[i]); 
   }
} //  remove_connections_between_classBoxes


function remove_connections_between_given_boxes(left_box_name, right_box_name) {
   
   var cssClass, arr_div, arr_seg, arr_meta;

   // assumptions: 1) segments are 'divs', so restrict search inside them
   //              2) all segments are children of drawing.div

   cssClass = convert_namePair_to_cssClassName(left_box_name, right_box_name);

   arr_div  = document.getElementsByTagName('div');
   // form cssClass name of segments
   arr_seg  = lib_find_subset_class(arr_div, cssClass);

   // remove each segment
   for (var i=0; i<arr_seg.length; i++) {
      //alert(info + 'removing ' + arr_seg[i]);
      drawing.div.removeChild(arr_seg[i]); 
   }
} //  remove_connections_between_proxyBoxes


function remove_connections_Class_Proxy_Class(left_box_name, proxy_name, right_box_name) {
   
   var cssClass_class, cssClass_meta, arr_div;

   arr_div  = document.getElementsByTagName('div');
   // form cssClass name of segments
   cssClass_left  = 'cl_line_' + left_box_name + '_'  + proxy_name;
   cssClass_right = 'cl_line_' + proxy_name    + '_'  + right_box_name;

   // assumptions: 1) segments are 'divs', so restrict search inside them
   //              2) all segments are children of drawing.div
   var arr_left_seg, arr_right_seg, arr_seg;
   arr_seg       = lib_find_subset_class(arr_div, cssClass_left);
   arr_right_seg = lib_find_subset_class(arr_div, cssClass_right);
   arr_seg       = arr_seg.concat(arr_right_seg);

   // remove each segment
   for (var i=0; i<arr_seg.length; i++) {
      //alert(info + 'removing ' + arr_seg[i]);
      drawing.div.removeChild(arr_seg[i]); 
   }
} //  remove_connections_Class_Proxy_Class


function find_connection_endpoints(conn_type, dscr_src, dscr_dst) {
   
   var info = 'find_connection_endpoints: conn_type= ' + conn_type + ': ';
   if (conn_debug) { alert(info) }
   //alert(info);
   var out_to_dest, inc_from_src, func_src, func_dest;

   switch(conn_type) {
      case REF_TO_INST:
         out_to_dest   = 'out_to_inst';
         inc_from_src  = 'inc_from_ref';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;
   
      case INST_TO_CLASS:
         out_to_dest   = 'out_to_class';
         inc_from_src  = 'inc_from_inst';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;

      case CLASS_TO_META:         
         out_to_dest   = 'out_to_meta';
         inc_from_src  = 'inc_from_klass';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;

      // all these values are identical (listed for document): it means that it 
      // is the same type of connection, in this case super to super pointer
      case CLASS_TO_CLASS : 
      case META_TO_META   :
      case PROXY_TO_CLASS :     
      case CLASS_TO_PROXY :    
      case PROXY_TO_PROXY :
         out_to_dest   = 'out_to_super';
         inc_from_src  = 'inc_from_super';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
         /*
         alert(info + 'func_src=' + func_src+ 'func_dst=' + func_dst);
         a = func_src(); 
         alert('src: a[0]=' +  a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
         a = func_dst(); 
         alert('dst: a[0]=' + a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
         */
      break;

      case CLASS_TO_META:         
         out_to_dest   = 'out_to_meta';
         inc_from_src  = 'inc_from_klass';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;
      
      case PROXY_TO_MODULE_IV_TBL:  
         out_to_dest   = 'out_to_iv_tbl';
         inc_from_src  = 'inc_from_iv_tbl';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
         //alert(info + 'func_src=' + func_src + 'func_dst=' + func_dst);
      break;

      case PROXY_TO_MODULE_METHODS:         
         out_to_dest   = 'out_to_methods'  ;
         inc_from_src  = 'inc_from_methods';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
         //alert(info + 'func_src=' + func_src + 'func_dst=' + func_dst);
      break;

      case MINI_PROXY_TO_MODULE:
         out_to_dest   = 'out_to_methods'  ;
         inc_from_src  = 'inc_from_proxy';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
         /*
           alert(info + 'func_src=' + func_src+ 'func_dst=' + func_dst);
           a = func_src(); 
           alert('src: a[0]=' +  a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
           a = func_dst(); 
           alert('dst: a[0]=' + a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
         */
      break;

      case CLASS_TO_MHM_MOD:
         out_to_dest   = 'out_to_mhm_mod'  ;
         inc_from_src  = 'inc_from_mhm_ptr';

         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
         /*
           alert(info + 'func_src=' + func_src+ 'func_dst=' + func_dst);
           a = func_src(); 
           alert('src: a[0]=' +  a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
           a = func_dst(); 
           alert('dst: a[0]=' + a[0] + ', a[1]=' + a[1] +  ', a[2]=' + a[2]);
         */
      break;

      case OUT1_TO_INC1:
         out_to_dest  = 'out1';
         inc_from_src = 'inc1';
         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;

      case VIEW_TO_CNTRL_INST:
         out_to_dest  = 'out_to_cntrl';
         inc_from_src = 'inc_from_view';
         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;

      case CNTRL_TO_VIEW_INST:
         out_to_dest  = 'out_to_view';
         inc_from_src = 'inc_from_cntrl';
         func_src = dscr_src.conn[out_to_dest];
         func_dst = dscr_dst.conn[inc_from_src];
      break;

      default:
         alert(info + 'conn_type unknown');
         return false;
      break;
   } // switch

   if (! (func_src && func_dst) ) {
      alert(info + 'did not find ' + func_src ? '' : 'func_src, ' +
                                     func_dst ? '' : 'func_dst, ' );
      return false;
   }

   return [ func_src, func_dst ];
} // find_connection_endpoints

/*
   Every line has a Css class: its name is generated from nameSrc and nameDst: 
   However we must remove following patterns:    blank-space   ::   %
   Example:
     issue_connect(CLASS_TO_META, 'ActCntr::Base', 'ActCntr::Basep', ...)
                           
     div#id_drawing .cl_line_ActCntr_Base_ActCntr_Basep
                     -----------------------------------
                               the css class name

   Why bother to generate one css classes per line (and write it manually in 
   the css file!)? to be able to retrieve the lines when we need to move boxes;
   so, just giving the name of the boxes src and destination, we can move boxes
   and their connections. 

   Of course, it sucks to have to insert a line manually in the Css file
   for each segment created.
*/
function convert_namePair_to_cssClassName(nameSrc, nameDst) {
	
   cssClassName = 'cl_line' + '_' + 
         convert_name_to_cssString(nameSrc) + 
         '_' +
         convert_name_to_cssString(nameDst);

   //if (cssClassName.match(/UserHelperp.+Applic/)) { alert(cssClassName); }

   return cssClassName;
} // convert_namePair_to_cssClassName


function convert_name_to_cssString(name) {
  return name.replace('::', '_').replace(/\s+/, '').replace(/%/, '');
}


function connect_src_to_dst(conn_type, dscr_src, dscr_dst, name_src, 
	                        name_dst, notify) {
		
   var info = 'connect_src_to_dst:  conn_type: ' + conn_type +
               ', src/dst: ' + name_src + ', ' + name_dst + ' : ';

   if (! lib_check_args(info, arguments.length, arguments.callee.length)) {
	  alert(info + "lib_check_args failed");
      return false;
   }

   if (! (dscr_src.elem && dscr_dst.elem) ) {
      var result  = dscr_src.elem? 'only src' : 
                     dscr_dst.elem? 'only dst' : 'neither src nor dst';
      alert(info + 'found elem property in ' + result);
      return false;
   }
   // end validation

   // Real work: find connection endpoints
   var endpoints = find_connection_endpoints(conn_type, dscr_src, dscr_dst,
                                             notify);
   if (! endpoints) { 
      alert(info +  'endpoints not found'); 
      return false;
   }

   var cssClass = convert_namePair_to_cssClassName(name_src, name_dst);

   //if (name_src.match(/%/) || name_src.match(/%/)) alert(info + cssClass);

   connect_2_endpoints(drawing, cssClass, endpoints[0], endpoints[1], notify); 

} // connect_src_to_dst()


function connect_2_endpoints(drawing, cssClass, func_src, func_dst, report) {

   var info= 'connect_2_endpoints: cssClass=' + cssClass; 
             // + ', trigger: ' +  report +", ";

   if (arguments.length < 4) {
         alert(info + 'expected 4 arguments, got= ' + arguments.length);
         return false;
   }
   
   var arr_src = func_src();  // => [x, y, x_inbox]
   var x_inbox = arr_src[2];

   var x_a     = arr_src[0];
   var y_a     = arr_src[1];

   // connection between iv_tbl (proxy-module) is more outdented
   var indent  = 0;
   if (arr_src.length == 4) indent = arr_src[3];   

   // if (cssClass.match(/instA/)) {alert(info +'x_a=' +x_a +', x_inbox='+ x_inbox);}

   var arr_dst = func_dst();  // ignore x_inbox for segment end
   var x_b = arr_dst[0];
   var y_b = arr_dst[1];

   //alert(info + 'x_a=' + x_a + ', y_a=' + y_a + ', x_b=' + x_b + ', y_b=' + y_b);
   
   if (!y_b || y_b == y_a) { 
	
      // horizontal case
      x_a  -= x_inbox; 
      if (conn_debug) alert("horiz line: x_a, y_a, x_b - x_a=" + 
                              x_a + ", " + y_a + ", " + (x_b - x_a));
      //alert(y_a);
      anim_horizontal(x_a, y_a, x_b - x_a, cssClass, 'GT', report); 
   }

   else if (!x_b || x_b == x_a ) {
	     
      // vertical: but generally we indent the vertical outside the box)
      if (x_inbox == 0) { 
         // if x_b == x_a  and x_inbox zero, straight vertical line)
      
         alert("vertical line wout indent: " + x_a);  // we don't have this case!
         anim_vertical(x_a, y_a, y_b - y_a, cssClass, NO_ARROW, report);
      }
      else {
         // vertical line, outdented (not to adhere to the boxes)
         x_b    = x_a;  // in case x_b was null
         if (y_b > y_a) {
	         /* 
	            upper box points to lower one; we assume to draw from the right 
	            edge of the box. Thus: horiz right + vertical down + horiz left
	         */
            var x_start = x_a - x_inbox;
            var x_vert  = x_a + (indent || INDENT_VERT_CONN);
            var arrow_dir = 'LT';
         }
         else {
	        /*  lower box points to upper one: we assume to draw from the 
	            left edge of the box. Thus: horiz left, vert up, horiz right
	        */ 
            var x_start = x_a + x_inbox;
            var x_vert  = x_a - (indent || INDENT_VERT_CONN);	
            var arrow_dir = 'GT'; 
         }

         //alert('anim_horizontal: x_start=' + x_start + 'dst=' + (x_vert - x_start));
	     var sequencer = {
	        index: -1,

	        tasks: 
	        [
	           function() { anim_horizontal(x_start, y_a, x_vert - x_start, 
				                    cssClass,  NO_ARROW, sequencer.next_task);
	           },

	           function() { anim_vertical(x_vert, y_a, y_b - y_a, cssClass,
				                            NO_ARROW, sequencer.next_task);
	           },

	           function() {  
		                    anim_horizontal(x_vert, y_b, x_b - x_vert, cssClass, 
		    								arrow_dir, sequencer.next_task);
	           },

	           null  // last array entry, no comma (we check for method not null)
	        ],

	        next_task: function() {
	           sequencer.index++;
	           next_method = sequencer.tasks[sequencer.index];

	           if (sequencer.index < sequencer.tasks.length && next_method) {
	              next_method();
	           }
	           else { 
		          if (report) { report(); }
	           }
	        } // next_task()
	      
	     }  // end sequencer object

	     sequencer.next_task();  // kick in sequencer
      }  // end of vertical outdented line

   }  // end of vertical case

   else if (x_b - x_a >= 0) {  // not horizontal, not vertical case, draw towards right
	
      /* decompose in 3 segments: horiz, vert, horiz
         this small segment is LEN_SEG_IN_BOX (to start line from pointer)
         |-----| 
            x_a
         ___|___xnext         y_a
               |              |     vertical seg len = y_b - y_a
               |              |    
               |______|       y_b
               x_a     x_b
                  last horiz segment is: xnext - x_a
      */

      /* north, south */
      // var is_south = (y_b - y_a > 0) ? 1 : 0; 

      /*
      if (x_b - x_a < 0) {
	    alert(info + 'x_a=' + x_a + ', y_a=' + y_a + ', x_b=' + x_b + ', y_b=' + y_b);
	  }
	  */
	
      var len_seg_in = x_inbox;  
      // split horizontal path (outside box) in 2 equal parts
      var half_horiz = (x_b - x_a) / 2;
      // set where the vertical starts
      var x_vert = x_a + half_horiz;

      // now, make x_a start inside the box

      x_a -= x_inbox;

      var sequencer = {
	     index: -1,
	
	     tasks: 
	     [
	        function() { 
		       //alert('anim: x_a: ' + x_a);
		       anim_horizontal(x_a, y_a, x_vert - x_a, cssClass, 
	                                     NO_ARROW, sequencer.next_task);
	        },
	
	        function() { 
		       var dist = y_b - y_a;  // can be negative
		       //alert(info + 'x_vert=' +x_vert +', y_a ' +y_a +', dist=' + dist);
		       anim_vertical(x_vert, y_a, dist, cssClass, 
			                             NO_ARROW, sequencer.next_task);
	        },
	
	        function() { 
		       	anim_horizontal(x_vert, y_b, x_b - x_vert, cssClass, 
			                                 'GT', sequencer.next_task);
	        },
	
	        null  // last array entry, no comma
	     ],
	
	     next_task: function() {
	        sequencer.index++;
            next_method = sequencer.tasks[sequencer.index];
           
	        if (sequencer.index < sequencer.tasks.length && next_method) {
	            next_method();
	        }
	        else {
	           if (report) {  report();  }
	        }
	     } // next_task()
	
	  }  // end sequencer
	  
	  sequencer.next_task(); // kick in sequencer
   } 

   else {  // not horizontal, not vertical case, x_b < x_a

      // make x_a start slightly inside the box

      x_a += 5;

      var sequencer = {
	     index: -1,
	
	     tasks: 
	     [
	        function() { 
		       //alert('anim: x_a: ' + x_a + 'x_b: ' + x_b);
		       anim_horizontal(x_a, y_a, x_b - x_a, cssClass, 
	                                     NO_ARROW, sequencer.next_task);
	        },
	
	        function() { 
		       var dist = y_b - y_a;  // can be negative
		       var dir_arrow = dist > 0 ? 'DOWN' : 'UP';
		       //alert(info + 'x_vert=' +x_vert +', y_a ' +y_a +', dist=' + dist);
		       anim_vertical(x_b, y_a, dist, cssClass, 
			                             'UP', sequencer.next_task);
	        },
	
	        null  // last array entry, no comma
	     ],
	
	     next_task: function() {
	        sequencer.index++;
            next_method = sequencer.tasks[sequencer.index];
           
	        if (sequencer.index < sequencer.tasks.length && next_method) {
	            next_method();
	        }
	        else {
	           if (report) {  report();  }
	        }
	     } // next_task()
	
	  }  // end sequencer
	  
	  sequencer.next_task(); // kick in sequencer
   }

} // connect_2_endpoints()
  

function anim_horizontal(x, y, distance, cssClass, arrow_dir, report) {
   // drawing.horizontal((x_a, y_a, x_b - x_a, cssClass);
   // drawing.arrow(x_b, y_a, 'GT', cssClass);
   var info = "anim_horizontal: cssClass: " + cssClass + "report: " + report;

   var msec_per_frame = MSEC_PER_FRAME;

   if (distance > 0) { 
      // calculate nr pixels, frames
      var result_h = calc_frame_rate(distance, PIX_PER_FRAME);
      var pix_per_frame = result_h.pixels;
      var numFrames     = result_h.frames;   
   }
   else {
      // distance negative: we can't draw right to left: so reverse it
      distance = - distance;
      x       -= distance;      // start drawing from lower x
      // if we want to do it in 1 shot, to hide absurdity
      /* pix_per_frame = distance; // and in 1 shot, so we don't confuse user
         numFrames = 1;
      */
      var result_h = calc_frame_rate(distance, PIX_PER_FRAME);
      var pix_per_frame = result_h.pixels;
      var numFrames     = result_h.frames;

      /*if      (arrow_dir == 'GT') { arrow_dir = 'LT' }
      else if (arrow_dir == 'LT') { arrow_dir = 'GT' }
      else    { ; } // NO_ARROW
      */
      if (arrow_dir != NO_ARROW) arrow_dir = 'LT';
   }

   // sequencer for line + arrow
   var sequencer = {
      index:  -1,

      tasks: 
      [
         // the first task is the animation itself
         function() {

            // create the element with null width
            var el = drawing.horizontal(x, y, 0, cssClass);

            animateCSS(el, numFrames, msec_per_frame,
                {
                   width: function(d, frame, time) {  // 'd' is for div element
                             var width = parseInt(d.style.width) + pix_per_frame;
                             return (width  + 'px');
                           } 
                 },
                 sequencer.next_task);   // "whendone" must activate next task
         }, // end 1st task

         // second task
         function() { 
            if (arrow_dir == 'GT') {
               drawing.arrow(x + distance, y, arrow_dir, cssClass);
            }
            else if (arrow_dir == 'LT') {
               drawing.arrow(x, y, arrow_dir, cssClass);
            }

            sequencer.next_task();
         },

         null  // last entry wout commma

      ],  // end array tasks

      next_task: function() {
	     sequencer.index++;
         next_method = sequencer.tasks[sequencer.index];
          
         if (sequencer.index < sequencer.tasks.length && next_method) {
            next_method();
         }
         else {
           if (report) {  report();  }
         }
      }, // next_task()

      end: null   // last entry gets no comma
   } // sequencer object

   /* kick in sequencer. Notice: 
      a) next call will return at the first timer started by animateCSS, and  
         this function will exit, returning to main scheduler.
	  b) the main scheduler will do nothing (other than return, freeing the stack)
	  c) when animateCSS completes, it will call 'whendone' (sequencer.interval)
	     which will call the second task (arrow); our 2nd task invokes arrow 
	     creation (which is done in one shot, so it returns control to second
		 taks when arrow is done) and calls sequencer.interval(), which schedules
		 next_task
	  d) sequencer.next_task() sees all tasks done and schedules main scheduler.
   */
   sequencer.next_task();  // kick in sequencer

}  // anim_horizontal()


function anim_vertical(x, y, distance, cssClass, arrow_dir, report) {
   var info = 'anim_vertical: distance=' + distance + ', ';

   var sequencer = {
      index:  -1,

      tasks: 
      [
         function() {

            /* we cannot draw south->north; thus, if distance is negative, draw
               from north, and in 1 shot, to avoid the strange result of seeing
               the segment drawn in the opposite sense from the one expected
            */
            y = distance > 0 ? y : y + distance;

            // create the element with null width
            var el = drawing.vertical(x, y, 0, cssClass);

            // calculate nr pixels, frames
            var abs_dist = distance > 0 ? distance : - distance;
            var result_h = calc_frame_rate(abs_dist, PIX_PER_FRAME);

            var pix_per_frame = result_h.pixels;

            var numFrames     = result_h.frames;

            if (distance < 0) {
	           distance = - distance;
	           /*
	              pix_per_frame = pix_per_frame * numFrames;
	              numFrames = 1;
	           */
            }
            
            var msec_per_frame = MSEC_PER_FRAME;

            //alert(info + 'pix_per_frame=' + pix_per_frame);

            animateCSS(
                  el, numFrames, msec_per_frame,
                  {
                     height: function(d, frame, time) {
                        var height = parseInt(d.style.height) + pix_per_frame;
                        return (height  + 'px');
                     }  // comma here killed IE 
                  },
                  sequencer.next_task);   // "whendone"
         },

         // next task: 
         // if we use vertical arrows, intf modif needed(^, not <  > !)
         function() { 
            if (arrow_dir == 'UP') {
               drawing.arrow(x, y, arrow_dir, cssClass);
            }
            else if (arrow_dir == 'DOWN') {
               drawing.arrow(x, y + distance, arrow_dir, cssClass);
            }

            sequencer.next_task();
         },

         null      // last entry without comma
      ],

      next_task: function() {
	     sequencer.index++;
         next_method = sequencer.tasks[sequencer.index];
          
         if (sequencer.index < sequencer.tasks.length && next_method) {
            next_method();
         }
         else {
           if (report) {  report();  }
         }
      }, // next_task()

      end: null   // last entry gets no comma

   }; // end sequencer

   // kick in sequencer
   sequencer.next_task();

}  // anim_vertical()


/**
 * This function from the great David Flanagan 
 *
 * animateCSS.js: a framework for creating CSS-based animations. Arguments:
 
    element:      the HTML element (or array of elements) to be animated.
    numFrames:    the total number of frames in the animation.
    timePerFrame: the number of milliseconds between each frame.
    animation:    an object that defines the animation; described below.
    whendone:     an optional function to call when the animation finishes;
                  if specified, this function is passed 'element' as its argument.

   The 4th parameter ('animation') is a Javascript object which specifies the 
   animation to be done, via its properties-values:
   - each property should have the same name as a CSS style property. 
   - the value of each property must be a function that returns values for that
     style property.  
   - each function is passed the frame number and the total elapsed time, and
     it can use these to compute the style value to return for that frame.

   For example, to animate an image so that it slides in from the upper left, 
   we could invoke animateCSS as follows:
 
     animateCSS( image,  // image to animate
	             25,     // for 25 frames
	             50,     // of 50ms each
                 {       // Set top and left attributes for each frame as follows:
                    top:  function(frame,time) { return frame*8 + "px"; },
                    left: function(frame,time) { return frame*8 + "px"; }
                 }
               );

   So, what must animateCSS() do? invoke the functions in hash 'animation' and
   use their returns to update the CSS property!
 **/

function animateCSS(element, numFrames, timePerFrame, animation, whendone) {

    // frame 0 was displayed by caller, eg with: drawing.box(dscr)
    var frame = 1;   // Store current frame number
    var time  = 0;   // Store total elapsed time
    var elements = (element instanceof Array) ? element : [ element ]; 

    //alert('element=' +   elements[0].style.left +  ', ' + elements[0].style.top);

    /* Arrange to call displayNextFrame() every timePerFrame milliseconds.
       This will display each of the frames of the animation
    */
    var intervalId = setInterval(displayNextFrame, timePerFrame);

    /* The call to animateCSS() returns AT THIS POINT! but the previous line 
       ensures that the following nested function will be invoked once for 
       each frame of the animation. However, be aware: control is given 
       back right away!
    */
    function displayNextFrame() {
        //alert('frame=' + frame + ', time=' + time);

        /* first, see if we're done */
        if (frame > numFrames) {     
            clearInterval(intervalId);        // If so, stop calling ourselves
            if (whendone) whendone();         // Invoke whendone (wout element param)
            return;                           // And we're finished
        }

        // Now loop through all properties defined in the animation object
        for(var cssprop in animation) {
           for (var i = 0; i < elements.length; i++) {
              /* For each property, call its animation function, passing the
                 frame number and the elapsed time. Use the return value of the
                 function as the new value of the corresponding style property
                 of the specified element. Use try/catch to ignore any
                 exceptions caused by bad return values.
              */
              var el = elements[i];
              try {
                     el.style[cssprop] = animation[cssprop](el, frame, time);
                     //alert('el left=' + el.style.left + ', top=' + el.style.top);
                  } 
              catch(e) { alert('animateCSS: exception=' + e) }
              }
        }  

        frame++;               // Increment the frame number
        time += timePerFrame;  // Increment the elapsed time
    }
} // end animateCSS()
