/**
 * mansofk - The manso javascript framework 
 * 
 * Author:  
 *   Pau Sanchez (http://www.codigomanso.com/en/)
 *
 * Version:
 *   1.00
 * 
 * Description:
 *   The idea of this framework is to provide the minimum set of 
 * functions to be able to inject this code on any website or project
 * without much trouble, and still be able to change CSS, 
 * import external JS and CSS, do ajax requests and animate things.
 *
 *   The minified version is below 4 KB and below 2 KB when gzipped.
 *
 *   All lighweight javascript frameworks I'm aware of are above 16 KB minified,
 * this one is below 4KB and supports animations, ajax, styling, events, and some
 * other things.
 *
 * Features:
 *   - Rename framework easily
 *
 *   - Select elements by ID (or apply to any HTML element)
 *
 *   - Chaining
 *
 *   - AJAX (ajax/get/post)
 *       - POST requests
 *       - GET requests
 *       - 3 Different formats: JSON/XML/TEXT
 *
 *   - Dynamically load external components (dynload)
 *      - can load external javascript files
 *      - can load external style sheets
 *
 *   - Simple DOM manipulations
 *      - append
 *      - replace
 *
 *   - CSS styling (css/hide/show)
 *       - get CSS property value
 *       - set CSS property value
 *       - set multiple values at once
 *
 *   - CSS animations (animate)
 *       - multiple attributes at once
 *       - change duration and even frames per second
 *       - 'linear' and 'cubic' easings 
 *
 *   - Event binding (bind/unbind)
 *   
 *   - Lightweight:
 *       - 3.3 KB minified!
 *       - 1.5 KB gzipped!
 *
 * Misc:
 *   Other lightweight frameworks:
 *     - AJS
 *         - 18.5 KB minified
 *         - 6.2 KB gzipped
 *         - http://orangoo.com/labs/AJS/
 *
 *     - fleegix.js
 *         - 21.7 KB minified
 *         - 7.5 KB  gzipped
 *         - http://js.fleegix.org/ 
 *
 *     - microajax (ONLY AJAX)
 *         - 805 bytes minified
 *         - 399 bytes gzipped
 *         - http://code.google.com/p/microajax/
 *
 * License:
 *   Feel free to do whatever you want with this. 
 *   This is free, so don't blame me if you find a bug.
 */
(function (window, name, undefined) {
  var mansofk = function (selector) {
    return new mansofk.fn.init (selector);
  };

  mansofk.fn = mansofk.prototype = {
    ctx   : null,

    /** 
     * Init function is used to select a element.
     *
     * The selector provided should be the ID of the element to be selected
     * or the DOM element itself (for example document.body)
     */
    init : function (selector) {
      if (typeof (selector) == 'object')
        this.ctx = selector;
      else
        this.ctx = document.getElementById (selector);
      return this;
    }, 

    /** display selected element */
    show : function () { this._css_set ('display', 'block'); },

    /** hide selected element */
    hide : function () { this._css_set ('display', 'none'); },

    /**
     * Get one CSS property or set one or several CSS properties at once.
     *
     * Get value of CSS property:
     *   css('property')
     *
     * Set the value of a CSS property:
     *   css('property', 'value')
     *
     * Set multiple properties at once:
     *   css({
     *     'property1' : 'value1',
     *     'property2' : 'value2',
     *     ...
     *   })
     */
    css : function (property /*, value */) {
      if (this.ctx == null) 
        return;

      var arg = arguments;
      var len = arg.length;
      if (len == 1) { // map or want to get
        var type = typeof (property);
        if (type == 'string') {
          return this._css_get (property);
        }
        else { //if (type == 'object')
          for (key in property) 
            this._css_set (key, property[key]);
        }
      }
      else if (len >= 2) {
        this._css_set (property, arg[1]);
      }

      return this;
    },

    // helper function to set 1 CSS property at a time
    _css_set : function (property, value) {
      var style = this.ctx.style;
      if (property == 'opacity') {
        if (document.all) { // IE
          style.filter = 'alpha(opacity=' + value * 100 + ')';
        }
        // Moz/compat uses a decimal value
        else {
          style.opacity = value;
        }
      }
      else if (property.indexOf('color') != -1) {
        style[property] = value;
      }
      else if (/^[\.0-9]+/.test(value)) { // numeric?
        style[property] = parseInt(value, 10) + 'px';
      }
      else { // any non-numeric property
        style[property] = value;
      }
    },

    // helper function to get the value of a CSS property
    _css_get : function (property) {
      var style = {};
      if (window.getComputedStyle) {
        style = window.getComputedStyle (this.ctx, null);
        if (property == 'opacity' && style [property] == "")
          return 1;
      }
      else {
        style = this.ctx.currentStyle;
        if (property == 'opacity') {
          return /opacity=([^)]*)/.test(style.filter || "") ? (parseFloat(RegExp.$1) / 100) : "1";
        }
      }

      return style [property] || 0;
    },

    /**
     * animate (to [, options]);
     *
     * options is a map of:
     *   - fps: frames per second
     *   - duration: duration in milliseconds
     *   - from: this can be a map of properties to define where the animation should start
     *   - ease: this is the easing function, it can be 'linear' or 'cubic' (linear is the default)
     */
    animate : function (to /*, optionsOrDuration */) 
    {
      var options = arguments[1] || {};
      if (this.ctx == null)
        return this;

      // the second parameter could be either options or duration
      if (typeof (options) != 'object')
        options = { duration : options };

      var i=0, key, self = this,
          duration = options.duration || 600,
          fps      = options.fps || 24,
          from     = options.from || {},
          freq     = 1000 / fps,
          ease_func_map = { 
            'linear' : function (a,b,i,t) { return (b-a) * (i/t); },
            'cubic'  : function (a,b,i,t) { return (b-a) * ((i*i*i)/(t*t*t)); }
          };

      var ease_func = ease_func_map[options.ease || 'cubic'];

      // get current CSS
      for (key in to) {
        if (!(key in from))
          from[key] = parseFloat(this._css_get (key), 10);
      }


      self._i  = 0;
      self._t = 1+parseInt (duration / freq); // total number of iterations
      self._q = []; // queue

      for (i = 0; i <= self._t; i++)
        self._q[self._q.length] = {};

      // get the difference for each step using a linear approach
      for (key in to) {
        for (i = 0; i < self._t; i++)
          self._q[i][key] = from[key] + ease_func (from[key], to[key], i, self._t);
        self._q[self._t][key] = to[key]; // adjust last frame
      }

      // start the animation and save the interval handler _ih
      self._ih = setInterval (function () {
        if (self._ih  && self._i>= self._t) {
          clearInterval (self._ih);
          self._i= self._t;
        }

        var frame = self._q[self._i];

        for (key in frame)
          self._css_set (key, frame[key]);

        self._i++;
      }, freq);

      return this;
    },

    /** bind a event to a function */
    bind : function (eventName, func) {
      // self.ctx['on' + eventName] = function () { func.apply (self); };
      var self = this;
      if (document.attachEvent) {
        this.ctx.attachEvent ('on'+eventName, function () { func.apply (self); });
      }
      else {
        this.ctx.addEventListener (eventName, function () { func.apply (self); }, false);
      }
      return this;
    },

    /** unbind all functions associated to a event */
    unbind : function (eventName) {
      // self.ctx['on' + eventName] = null
      if (document.attachEvent) 
        this.ctx.attachEvent ('on'+eventName);
      else
        this.ctx.removeEventListener (eventName);
      return this;
    },

    /** append given HTML as a child element of current selected object */
    append : function (html) {
      var e = document.createElement('div');
      e.innerHTML = html;
      this.ctx.appendChild (e);
    },

    /** replace the HTML code of the selected element */
    replace : function (html) {
      this.ctx.innerHTML = html;
    }
  };

  /** Dynamically load a JS or a CSS (guess type by default) */
  mansofk.dynload = function (url /*, type*/)
  {
    var isScript = (url.indexOf('.css') == -1); 
    if (arguments.length > 1)
      isScript = (arguments[1] == 'js');

    var elem = document.createElement(isScript ? 'script' : 'link')
    elem.type = 'text/' + (isScript ? 'javascript' : 'css');
    if (isScript) {
      elem.src = url;
    }
    else {
      elem.rel  = "stylesheet";
      elem.href = url;
    }
  
    document.getElementsByTagName('head')[0].appendChild(elem);
    // document.body.appendChild(elem);
  };

  /** Append some HTML at the end of the document */
  mansofk.append = function (html) {
    var e = document.createElement('div')
    e.innerHTML = html;
    document.body.appendChild (e);
  };

  /** Serialize given object to a string */
  mansofk.serialize = function (object) {
    var k, str = '';
    if (typeof (object) != 'object')
      return object;

    for (k in object)
      str += '&' + encodeURIComponent(k) + '=' + encodeURIComponent(object[k]);

    return str.substr (1);
  };
  
  /** AJAX POST */
  mansofk.post = function (url, data, callback) {
    return this.ajax ('POST', url, callback, data, 'js');
  };

  /** AJAX GET */
  mansofk.get = function (url, callback) {
    return this.ajax ('GET', url, callback, null, 'js');
  };
  
  /**
   * Valid methods are:
   *   - 'POST'
   *   - 'GET'
   *
   * Valid formats are:
   *   - json | text | xml
   */
  mansofk.ajax = function (method, url, callback /*, data, format*/) {
    var self   = this;
    var format = arguments[4] || 'js';
    self.r = null;
    try {
      if (window.ActiveXObject)
        self.r = new ActiveXObject('Microsoft.XMLHTTP');
      else if (window.XMLHttpRequest)
        self.r = new XMLHttpRequest();
    } catch (e) {
      return this;
    }

    if (self.r.overrideMimeType)
      self.r.overrideMimeType('text/xml');

    self.r.open (method, url, true); // async = true
    
    if (method == 'POST') {
      self.r.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      self.r.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    }

    self.r.onreadystatechange = function() {
      if ((self.r.readyState == 4) && (self.r.status == 200)) {
        var data = self.r.responseText;
        if (format[0] == 'x') { data = self.r.responseXML }
        if (format[0] == 'j') { data = (new Function("return " + data.replace(/[\n\r]/g,'')))(); }
        callback (data);
      }
    };

    self.r.send(self.serialize (arguments[3] || ""));
    return this;
  };

  // allow later instantiation
  mansofk.fn.init.prototype = mansofk.fn;

  window[name] = mansofk;
})(window, 'mansofk'); // change mansofk with whatever you want to avoid collisions

