if (typeof Melk == "undefined") { 
  var Melk = {};
}

Melk.signum = function(v) {
  if (v < 0) {
    return -1;
  }
  else if (v > 0) {
    return 1;
  }
  else {
    return 0;
  }
};

Melk.EPEQ_EPSILON = 1e-10;
Melk.epeq = function(a, b) {
  return Math.abs(a - b) < Melk.EPEQ_EPSILON;
};

Melk.url_join = function(base, rest) {
    if (base == '') {
        return rest;
    }
    if (rest == '' || rest == '/') {
        return base;
    }
    
    var url = base;
    if (url.charAt(url.length - 1) != '/') {
        url += '/';
    }
    if (rest.charAt(0) == '/') {
        rest = rest.substring(1, rest.length);
    }
    url += rest;
    
    return url;
};

/* returns a function with that will call func with 'this' bound to the scope given 
*/
Melk.with_scope = function(scope, func) {
    var bound_func = function() {
        return func.apply(scope, arguments);
    };
    return bound_func;
};


Melk.form_encode_dict = function(dict) {
  var encoded_form = "";
  for (key in dict) {
    v = dict[key];
    if (v instanceof Array) {
      for (var i = 0; i < v.length; ++i) {
        encoded_form += encodeURIComponent(key) + "=" + encodeURIComponent(v[i]) + "&";
      }
    }
    else {
      encoded_form += encodeURIComponent(key) + "=" + encodeURIComponent(v) + "&";
    }
  }
  return encoded_form;
};

Melk.async_submit = function(form, cb, is_upload) {
    YAHOO.util.Connect.setForm(form, is_upload);
    YAHOO.util.Connect.asyncRequest("POST", form.action, cb);    
};

Melk.form_submitter = function(form, cb, is_upload) {
  var form_handler = function(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      Melk.async_submit(form, cb, is_upload);
      return false;
  };
  return form_handler;
};

Melk.make_async_form = function(form, cb, is_upload) {
    jQuery(form).submit(Melk.form_submitter(form, cb, is_upload));
};

Melk.make_async_submit_link = function(link, form, cb, is_upload) {
    jQuery(link).click(Melk.form_submitter(form, cb, is_upload));
};

Melk.make_local_form = function(form, cb, scope) {
  var form_handler = function(event) {
    YAHOO.util.Event.stopEvent(event);
    if (!scope) {
      cb(event);
    }
    else {
      cb.apply(scope, [event]);
    }
  };
  YAHOO.util.Event.addListener(form, 'submit', form_handler);
};

Melk.json_request = function(target_url, callback, payload, custom_method) {
    var json_data = Melk.serialize_json(payload);
    YAHOO.util.Connect.resetFormState();
    YAHOO.util.Connect.setDefaultPostHeader(false);
    YAHOO.util.Connect.initHeader('Content-Type', 'application/json');
    return YAHOO.util.Connect.asyncRequest((custom_method) ? custom_method : 'POST', 
                                           target_url, callback, json_data);
};

Melk.async_get = function(target_url, callback) {
    YAHOO.util.Connect.resetFormState();
    return YAHOO.util.Connect.asyncRequest('GET', target_url, callback);
};

Melk.async_delete = function(target_url, callback) {
    YAHOO.util.Connect.resetFormState();
    return YAHOO.util.Connect.asyncRequest('DELETE', target_url, callback);
};

Melk.post_dict = function(target_url, callback, payload_dict) {
  enc_post_data = Melk.form_encode_dict(payload_dict);
  YAHOO.util.Connect.resetFormState();
  YAHOO.util.Connect.initHeader('Content-Type', 'application/x-www-form-urlencoded');
  return YAHOO.util.Connect.asyncRequest('POST', target_url, callback, enc_post_data);
};


Melk.serialize_json = function(payload) {
  var envelope = {};
  envelope.content = payload;
  return JSON.stringify(envelope);
};

Melk.parse_json_raw = function(json_text) {
  return JSON.parse(json_text);
};

Melk.parse_json = function(json_text) {
  // envelope = eval( "(" + json_text + ")" ); 
  var envelope = JSON.parse(json_text);
  return envelope["content"];
};

/*
* this parses json embedded in HTML 
* which may be returned from file uploads
* in order to get the response to show up 
* as the text of an iframe. Otherwise
* some other content type handler
* for json (such as an external text editor)
* may be triggered...
*/
Melk.parse_upload_json_raw = function(frame_text) {
    var cleanedResponseText = frame_text;
    cleanedResponseText = cleanedResponseText.replace(/[\r\n]/g, "");
    cleanedResponseText = cleanedResponseText.replace(/^\s*<html>\s*<body>/, "");
    cleanedResponseText = cleanedResponseText.replace(/<\/body>\s*<\/html>\s*$/, "")
    cleanedResponseText = cleanedResponseText.replace(/&lt;/g, "<");
    cleanedResponseText = cleanedResponseText.replace(/&gt;/g, ">");

    return Melk.parse_json_raw(cleanedResponseText);
};


Melk.std_callback = function(success_cb, scope, error_message) {
    var cb = {
        success: function(response) {
            result = Melk.parse_json(response.responseText);
            if (!scope) {
                success_cb(result); 
            }
            else {
                success_cb.apply(scope, [result]);
            }
        },
        
        failure: function(response) {
            Melk.handle_errors(response, error_message);
        }
    };
    return cb;
};

Melk.trim = function(str) {
    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};

Melk.std_upload_callback = function(success_cb, scope, error_message) {
    var cb = {
        upload: function(response) {

            result = Melk.parse_upload_json_raw(response.responseText);
            if (!scope) {
                success_cb(result); 
            }
            else {
                success_cb.apply(scope, [result]);
            }
        }
    };
    /* !? failure case .... */
    return cb;
};



Melk.replace_element = function(el, html) {
  var new_els = Melk.jq(html);
  Melk.jq(el).replaceWith(new_els);
  return new_els.get(0);
};

Melk.remove_element = function(el, callback) {
  var cb = function() {
    Melk.jq(el).remove();
    if (callback) {
      callback();
    }
  };
  Melk.jq(el).slideUp(150, cb);
};

Melk.append_element = function(to_el, html) {
  return Melk.jq(html).appendTo(to_el).get(0);
};

Melk.tl_on_screen = function(el) {
  var widget_xy = YAHOO.util.Dom.getXY(el);
  
  var wq = Melk.jq(window);

  var wy1 = wq.scrollTop();
  var wy2 = wy1 + wq.height();
  var wx1 = wq.scrollLeft();
  var wx2 = wx1 + wq.width();

  if (wy1 > widget_xy[1] || wy2 < widget_xy[1] ||
      wx1 > widget_xy[0] || wx2 < widget_xy[0]) {
    return false;
  }
  
  return true;

};

Melk.focus = function(el, callback) {
  var opts = {'duration': 450, 'offset': -40}
  if (callback) {
    opts['onAfter'] = callback;
  }
  Melk.jq.scrollTo(el, opts);
};

Melk.focus_if_offscreen = function(el, callback) {
    if (!Melk.tl_on_screen(el)) {
      Melk.focus(el, callback);
    }
    else if (callback) {
        callback();
    }
};

Melk.dummy_callback = {
 success: function(response) {},
 failure: function(response) {}
};

Melk.handle_errors = function(response, fallback_message) {
  if (fallback_message) {
    Melk.display_error_message(fallback_message);
  }
  else {
    Melk.display_error_message('last action failed.');
  }
};


Melk.handle_errors_callback = function() {
  var cb = {
  success: function(response) {
    },
  failure: function(response) {
      Melk.handle_errors(response);
    }
  };
  return cb;
};

// Debug Function.  Turn off for live code or IE
Melk.debug = function(string) {
  if( typeof console != 'undefined' ) {
    console.log(string);
  }
};


Melk.CallbackSequence = function() {
  this.init();
};

Melk.CallbackSequence.prototype = {
  /**
   * This object creates a sequence of callbacks
   * such that only the latest callback created 
   * will execute regardless of the order they 
   * are actually executed in. 
   *
   * This is useful in the situations where a number of 
   * asynchronous requests have been issued, but only the 
   * latest request should be processed.
   * 
   * The particulars of what the callbacks do 
   * are provided to the creation function
   * in the form of ... a callback.
   *
   */

  _sequence_id: 0,
  
  init: function() {
  },
  
  make_callback: function(cb) {
    this._sequence_id += 1;
    var my_seq = this._sequence_id;

    var outcb = {
    success: function(response) {
        if (this._sequence_id == my_seq) {
          if (!cb.scope) {
            cb.success(response);
          }
          else {
            cb.success.apply(cb.scope, [response]);
          }
        }
      },
    failure: function(response) {
        if (this._sequence_id == my_seq) {
          if (!cb.scope) {
            cb.failure(response);
          }
          else {
            cb.failure.apply(cb.scope, [response]);
          }
        }
      },
    scope: this
    };

    return outcb;
  }, 

  /* make sure that no outstanding callback executes */ 
  invalidate: function() {
    this._sequence_id += 1;
  }
};

Melk.normalize_feed_url = function(url) {
    return url.toLowerCase();
};


/* eg: 
 * rs = new RequestSequence();
 * cb = {...}
 * rs.connection = some_async(..., rs.prep_request(cb))
 * rs.connection = some_async(..., rs.prep_request(cb))
 * rs.connection = some_async(..., rs.prep_request(cb))
 */ 
Melk.RequestSequence = function() {
  this.init();
};

Melk.RequestSequence.prototype = {
 callback_sequence: null,
 connection: null,

 init: function() {
    this.callback_sequence = new Melk.CallbackSequence();
  },
 
 prep_request: function(cb) {
   this.abort();
   return this.callback_sequence.make_callback(cb);
 },

 in_progress: function() {
   if (this.connection != null && YAHOO.util.Connect.isCallInProgress(this.connection)) {
     return true;
   }
   else {
     return false; 
   }
 },
 
 abort: function() {
   this.callback_sequence.invalidate();
   if (this.connection != null && YAHOO.util.Connect.isCallInProgress(this.connection)) {
     YAHOO.util.Connect.abort(this.connection);
   } 
 }

};
