/**
 * Returns true or false if given parameter is an signed int or not.
 * 
 * @param s
 * @returns {Boolean} true if given parameter is an signed integer or false, if not.
 */
function isInteger(s)
{
  return ( s.toString( ).search(/^-?[0-9]+$/) == 0 );
}

/**
 * Returns true or false if given parameter is an unsigned int or not.
 * 
 * @param s
 * @returns {Boolean} true if given parameter is an unsigned integer or false, if not.
 */
function isUnsignedInteger(s)
{
  return ( s.toString( ).search(/^[0-9]+$/) == 0 );
}

/**
 * Returns true or false if {b} is a Boolean or not.
 * 
 * @param b
 * @returns {Boolean}
 */
function isBoolean(b)
{
  return typeof b === "boolean";
}

/**
 * Check if {obj} is a function.
 * 
 * @param obj
 * @returns {Boolean} true if obj is a object of type Function.
 */
function isFunction(obj)
{
  return toString.call(obj) === "[object Function]";
}

/**
 * Check if {obj} is a type of array.
 * 
 * @param obj
 * @returns {Boolean} true if obj is a object of type Array.
 */
function isArray(obj)
{
  return toString.call(obj) === "[object Array]";
}

/**
 * Check if {obj} is a type of object.
 * 
 * @param obj
 * @returns {Boolean} true if obj is an Object.
 */
function isObject(obj)
{
  return toString.call(obj) === "[object Object]";
}

/**
 * Check if {obj} is empty or not.
 * 
 * @param obj
 * @returns {Boolean} true if obj is an empty object.
 */
function isEmptyObject(obj)
{
  for ( var name in obj) {
    return false;
  }
  return true;
}

function assert(bCondition, sErrorMessage)
{
  if (!bCondition) {
    sDefaultMessage = "Assertation failed!";
    sErrorMessage = ( sErrorMessage !== undefined && sErrorMessage.length !== undefined && ( sErrorMessage.length > 0 ) ) ? sErrorMessage
      : sDefaultMessage;
    console.trace( );
    throw new Error(sErrorMessage);
  }
}

String.prototype.trim = function( )
{
  var str = this.toString( ).replace(/^\s+/, '');
  console.debug(str);
  for ( var i = str.length - 1; i >= 0; i--) {
    if (/\S/.test(str.charAt(i))) {
      str = str.substring(0, i + 1);
      break;
    }
  }
  return str;
};

console = ( console !== undefined ) ? console : {};

/**
 * Tests that an expression is true. If not, it will write a message to the console and throw an exception.
 * 
 * @param {Boolean}
 *          expression
 * @param {String}
 *          message optional
 */
console.assert = console.assert && debug ? console.assert : function( )
{
};

/**
 * Writes the number of times that the line of code where count was called was executed. The optional argument title
 * will print a message in addition to the number of the count.
 * 
 * @param title
 *          optional
 */
console.count = console.count ? console.count : function( )
{
};

/**
 * Writes a message to the console with the visual "debug" icon and color coding and a hyperlink to the line where it
 * was called.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.debug = console.debug && debug ? console.debug : function( )
{
};

/**
 * Prints an interactive listing of all properties of the object. This looks identical to the view that you would see in
 * the DOM tab.
 * 
 * @param object
 */
console.dir = console.dir ? console.dir : function( )
{
};

/**
 * Prints the XML source tree of an HTML or XML element. This looks identical to the view that you would see in the HTML
 * tab. You can click on any node to inspect it in the HTML tab.
 * 
 * @param node
 */
console.dirxml = console.dirxml ? console.dirxml : function( )
{
};

/**
 * Writes a message to the console with the visual "error" icon and color coding and a hyperlink to the line where it
 * was called.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.error = console.error ? console.error : function( )
{
};

/**
 * Writes a message to the console and opens a nested block to indent all future messages sent to the console. Call
 * console.groupEnd() to close the block.
 * 
 * @param {String}
 *          name optional
 */
console.group = console.group ? console.group : function( )
{
};

/**
 * Like {@link console.group()}, but the block is initially collapsed.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.groupCollapsed = console.groupCollapsed ? console.groupCollapsed : function( )
{
};

/**
 * Closes the most recently opened block created by a call to {@link console.group()} or
 * {@link console.groupCollapsed()}
 */
console.groupEnd = console.groupEnd ? console.groupEnd : function( )
{
};

/**
 * Writes a message to the console with the visual "info" icon and color coding and a hyperlink to the line where it was
 * called.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.info = console.info ? console.info : function( )
{
};

/**
 * Writes a message to the console. You may pass as many arguments as you'd like, and they will be joined together in a
 * space-delimited line. The first argument to log may be a string containing printf-like string substitution patterns.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.log = console.log ? console.log : function( )
{
};

/**
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.markTimeline = console.markTimeline ? console.markTimeline : function( )
{
};

/**
 * Turns on the JavaScript profiler. The optional argument title would contain the text to be printed in the header of
 * the profile report.
 * 
 * @param title
 */
console.profile = console.profile && debug ? console.profile : function( )
{
};

/**
 * Turns off the JavaScript profiler and prints its report.
 */
console.profileEnd = console.profileEnd && debug ? console.profileEnd : function( )
{
};

/**
 * Creates a new timer under the given name. Call {@link console.timeEnd(name)} with the same name to stop the timer and
 * print the time elapsed..
 * 
 * @param name
 */
console.time = console.time ? console.time : function( )
{
};

/**
 * Stops a timer created by a call to {@link console.time(name)} and writes the time elapsed.
 * 
 * @param name
 */
console.timeEnd = console.timeEnd ? console.timeEnd : function( )
{
};

/**
 * Prints an interactive stack trace of JavaScript execution at the point where it is called. The stack trace details
 * the functions on the stack, as well as the values that were passed as arguments to each function. You can click each
 * function to take you to its source in the Script tab, and click each argument value to inspect it in the DOM or HTML
 * tabs.
 */
console.trace = console.trace && debug ? console.trace : function( )
{
};

/**
 * Writes a message to the console with the visual "warning" icon and color coding and a hyperlink to the line where it
 * was called.
 * 
 * @param {Object}
 *          object
 * @param {Any}
 *          optional
 */
console.warn = console.warn ? console.warn : function( )
{
};

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

function AbstractTuneUpClass( )
{
  return this.initialize.apply(this, arguments);
}

AbstractTuneUpClass.prototype = {
  initialize : function( )
  {
    console.debug("AbstractTuneUpClass.initialize(", arguments, ")");
  },
  /**
   * 
   */
  send : function(msg)
  {
    console.debug("AbstractTuneUpClass.send(", arguments, ")");
    alert(msg);
  }
};
AbstractTuneUpClass.extend = function(base, child)
{
  return jQuery.extend(true, base, child);
};

function TuneUpScheduler( )
{
  AbstractTuneUpClass.apply(this, arguments);
}
TuneUpScheduler.prototype = new AbstractTuneUpClass( );

AbstractTuneUpClass.extend(TuneUpScheduler.prototype, {
  fn : null,
  context : null,
  initialize : function(fn, context)
  {
    console.debug("TuneUpScheduler.initialize(", arguments, ")");
    this.fn = fn;
    this.context = context;
  },
  scheduleDelayed : function(delay)
  {

  },

  scheduleInterval : function(interval)
  {

  },

  scheduleImmediately : function( )
  {
    this.scheduleDelayed(0);
  }
});

/**
 * Builds an event object with given parameters. {eventData} Object will be comitted to the handler on each time the
 * event will triggered. {handleFn} is the function who will be triggered on event. {handleObj}, if not null, will be
 * used as context for {handleFn}.
 * 
 * @author TobiasTrozowski
 * @constructor
 * @param eventType
 * @param eventData
 * @param handleFn
 * @param handleObj
 * @returns {TuneUpDispatchEvent}
 */
function TuneUpDispatchEvent(eventType, eventData, handleFn, handleObj)
{
  return AbstractTuneUpClass.apply(this, arguments);
};
TuneUpDispatchEvent.prototype = new AbstractTuneUpClass( );

AbstractTuneUpClass.extend(TuneUpDispatchEvent.prototype, {

  /**
   * The arguments for this event. These arguments will be passed to the bounded handler
   */
  args : undefined,

  /**
   * The triggered event type
   */
  type : "",

  /**
   * The object who will passed to the handler each time
   */
  data : {},

  internal : undefined,

  /**
   * @param eventType
   * @param defaultData
   * @param handleFn
   * @param handleObj
   * @returns {TuneUpDispatchEvent}
   */
  initialize : function(eventType, defaultData, handleFn, handleObj)
  {
    // console.debug( "TuneUpDispatchEvent.initialize(", arguments, ")" );
    this.type = eventType;

    if (isFunction(defaultData)) {
      handleObj = handleFn;
      handleFn = defaultData;
      defaultData = undefined;
    }

    this.internal = {
      fn : handleFn,
      ctx : handleObj ? handleObj : this
    };

    this.data = defaultData || {};

    return this;
  },

  /**
   * schedule will execute the function in specified context. Using setTimeout to generate a new native Thread.
   * 
   * @returns void
   */
  schedule : function( )
  {
    var args = Array.prototype.slice.call(this.args), fn = this.internal.fn, ctx = this.internal.ctx;
    args.unshift(this);
    setTimeout(function( )
    {
      if (args !== undefined && args !== null) {
        fn.apply(ctx, args);
      } else {
        fn.apply(ctx);
      }
    });
  },

  /**
   * Create a exactly clone of the current object.
   * 
   * @returns A cloned object of type {TuneUpDispatchEvent}
   */
  clone : function( )
  {
    // clone this object. using a empty TuneUpDispatchEvent as clone
    return jQuery.extend(true, new TuneUpDispatchEvent( ), this);
  }

});

/**
 * @author TobiasTrozowski
 * @constructor
 * @returns
 */

function TuneUpDispatcher( )
{
  return AbstractTuneUpClass.apply(this, arguments);
}
TuneUpDispatcher.prototype = new AbstractTuneUpClass( );

AbstractTuneUpClass.extend(TuneUpDispatcher.prototype, {

  // NOTICE:
  // HTML Events: load unload abort error resize scroll
  // HTML Form Events: select change submit reset focus blur
  // Mouse Events: click dblclick mousedown mouseup mousemove mouseover mouseout
  // Keyboard Events: keydown keypress keyup
  // SEE: http://en.wikipedia.org/wiki/DOM_events
  events : [
    "load", "unload", "abort", "error", "resize", "scroll", "select", "change", "submit", "reset", "focus", "blur", "click", "dblclick",
    "mousedown", "mouseup", "mouseover", "mouseout", "mousemove", "keydown", "keypress", "keyup"
  ],

  inactiveTuneUpEvents : {},

  /**
   * list of registered events. Events will stored like this: array( 'eventType' => array( handlerData1, handlerData2,
   * handlerData3 ), 'eventType2' => array( handlerData1, handlerData2, handlerData3 ))
   */
  tuneUpEvents : undefined,

  /**
   * @returns {TuneUpDispatcher}
   */
  initialize : function( )
  {
    console.debug("TuneUpDispatcher.initialize()");

    if (this.tuneUpEvents === undefined) {
      this.tuneUpEvents = new Array( );
    }

    // singleton implementation
    if (TuneUpDispatcher.instance !== undefined) {
      console.warn("TuneUpDispatcher instantiated twice, returning previous instance.");
      return TuneUpDispatcher.instance;
    }
    TuneUpDispatcher.instance = this;
    return TuneUpDispatcher.instance;
  },

  /**
   * Find a list of handlers for the specified {eventType}.
   * 
   * @param eventType
   * @returns {Array} of {TuneUpDispatchEvent} objects if the event was found or {null} if there is no such event.
   */
  findEvent : function(eventType)
  {
    if (eventType !== undefined && eventType !== null && this.tuneUpEvents[ eventType ] !== undefined) {
      return this.tuneUpEvents[ eventType ];
    }
    return null;
  },

  /**
   * Add the specified {event} to the internal event list.
   * 
   * @param event
   *          Has to be a object with type of {TuneUpDispatchEvent}.
   * @returns {TuneUpDispatcher}
   */
  addEvent : function(event)
  {
    // console.debug( "TuneUpDispatcher.addEvent(", arguments, ")" );
    if (event !== undefined && event.initialize !== undefined && event.type !== undefined) {
      var eventHandlers = null;
      if (( ( eventHandlers = this.findEvent(event.type) ) === null ) || eventHandlers.length === undefined) {
        this.tuneUpEvents[ event.type ] = new Array( );
        eventHandlers = this.findEvent(event.type);
      }
      eventHandlers[ eventHandlers.length ] = event;
    } else {
      throw new TypeError("Cannot add invalid events.");
    }
    return this;
  },

  /**
   * Removes a specified event. If only {type} is present than all handlers of these type will be removed, otherwise
   * just the matched {handler} will be removed from list.
   * 
   * @param type
   * @param handler
   * @returns {TuneUpDispatcher}
   */
  removeEvent : function(type, handler)
  {
    var eventHandlers = event = null, i = l = 0;
    if (( eventHandlers = this.findEvent(type) ) !== null && ( eventHandlers.length > 0 )) {
      if (handler !== undefined) {
        l = eventHandlers.length;
        for (; i < l; i++) {
          event = eventHandlers[ i ];
          if (event !== undefined && event.handleFn !== undefined && event.handleFn === handler) {
            delete this.tuneUpEvents[ type ][ i ];
          }
        }
      } else { // if no handler is set, remove all events of same type
        delete this.tuneUpEvents[ type ];
      }
    }
    return this;
  },

  /**
   * Dispatch the given event.
   * 
   * @param event
   *          Has to be an object of type {TuneUpDispatchEvent}.
   * @returns {TuneUpDispatcher}
   */
  dispatch : function(event)
  {
// console.debug("TuneUpDispatcher.dispatch(", arguments, ")");
    var eventType = event.type, args = null;
    if (eventType === undefined) {
      console.warn("Cannot dispatch a undefined event: ", event);
      return this;
    }

    if (this.findEvent(eventType) === null) {
      console.warn("There are no handlers this event: '", event, "'.");
      return this;
    }

    // slice the full event to prevent that we modify the object
    args = Array.prototype.slice.call(event.args);
    args.unshift(event);
    try {
      // schedule event
      event.schedule(args);
    } catch (e) {
      console.error("Error while calling a handler for event '", eventType, "': ", e);
      console.trace( );
      return this;
    }
    return this;
  },

  /**
   * Binds a function to a specific event. If {eventData} is set this map of properties will be dispatched to the event
   * each time it will be executed.
   * 
   * @param eventType
   *          A string containing one or more TuneUp event types, such as "load" or "close," or custom event names.
   * @param eventData
   *          optional A map of data that will be passed to the event handler.
   * @param fn
   *          A function to execute each time the event is triggered. If set to false will attach a function that
   *          prevents the default action from occurring and stops the event from bubbling.
   * @param thisObject
   *          optional The context where {fn} should be executed in.
   * @returns {TuneUpDispatcher}
   */
  bind : function(eventType, eventData, fn, thisObject)
  {
    // console.debug( "TuneUpDispatcher.bind(", arguments, ")" );

    // TODO: add support for multiple event listening
    assert(eventType.indexOf(" ") == -1, "Cannot bind multiple events, cause this currently not supported.");

    // TODO: if eventType contains whitespaces split and register for all events
    // TODO: support binding for events with namespace, e.g. bind('event.namespace', ...)
    if (isFunction(eventData)) {
      if (isObject(fn)) {
        thisObject = fn;
      }
      fn = eventData;
      eventData = undefined;
    }

    assert(fn !== undefined, String.format("Cannot listen to an event ('%s') without a handler.", eventType));

    return this.addEvent(new TuneUpDispatchEvent(eventType, eventData, fn, thisObject));
  },

  /**
   * Unbind a specified {eventType} from event list. If {handler} isn't present than all handlers of type {eventType}
   * will be removed, otherwise the matched {handler} will be removed.
   * 
   * @param eventType
   * @param handler
   * @returns {TuneUpDispatcher}
   */
  unbind : function(eventType, handler)
  {
// console.debug("TuneUpDispatcher.unbind(", arguments, ")");

    // TODO: add support for multiple event listening
    // TODO: support unbinding for events with namespace, e.g. unbind('event.namespace')
    assert(eventType.indexOf(" ") == -1, "Multiple events are currently not supported.");

    return this.removeEvent(eventType, handler);
  },

  /**
   * Trigger a specified event. If {eventData} is present and type of object than this object will be passed through the
   * event just this time. If the second argument to this function is not a object than the arguments will be passed
   * through the handler as own arguments.
   * 
   * @param eventType
   * @param eventData
   *          optional
   * @param args
   *          optional
   * @returns {TuneUpDispatcher}
   */
  trigger : function(eventType, eventData, args)
  {
    assert(eventType !== undefined, "Cannot trigger an undefined event.");

    var evtList = data = args_ = event = null, i = length = 0;

    // copy the arguments
    args_ = Array.prototype.slice.call(arguments);
    if (args_.length == 1) {
      // do nothing if just the eventType is given
    } else if (args_.length == 2) {
      // if first arg is an object and the second param is an argument
      // if property type is missing than this is just a eventType
      if (isObject(eventType) && eventType.type !== undefined) {
        eventData = ( eventType.data !== undefined ) ? eventType.data : eventData;
        eventType = eventType.type;
      }
      // remove eventType from arguments
      args_.shift( );
    } else if (args_.length > 2) { // more than 2 arguments
      // if property type is missing than this is just a argument
      if (isObject(eventType) && eventType.type !== undefined) {
        eventData = ( eventType.data !== undefined ) ? eventType.data : eventData;
        eventType = eventType.type;
        // if eventData is an object, so shift them from arguments
        if (isObject(eventData)) {
          args_.shift( );
        }
      }
      // remove eventType from arguments
      args_.shift( );
    }

    if (( evtList = this.findEvent(eventType) ) !== null) {
      length = evtList.length;
      for (; i < length; i++) {
        // clone the object cause we may be want to modify it later, so the original event never change
        event = evtList[ i ].clone( );
        event.args = args_;
        if (eventData !== undefined && eventData !== null && isObject(eventData)) {
          event.data = jQuery.extend(event.data, eventData);
        }
        // TODO: may be we should dispatch the events via setTimeout to save more time
        this.dispatch(event);
      }
    } else {
      console.warn("Cannot find the event: ", eventType);
    }

    return this;
  }
});

/**
 * directly initialize the dispatcher
 */
var tuDispatcher = new TuneUpDispatcher( );
