/* --------------------------------------------------------------------------------------- */
/* No freakin' copyright, no stinkin' license, no guarantees or warranties                 */
/* (implied, explicit or whatever). Usage is totally and completely at your own risk.      */
/* Please keep this comment block as is when modifying this code. Thanks in advance,       */
/*   Ruurd Idenburg                                                                        */
/* --------------------------------------------------------------------------------------- */
/**
	The <i>eventable</i> class is a <i>mixin</i> class, that can be used in the 
	inheriting class to define events (by name) and trigger those events so that
	other classes may act upon those events.
	<p>
	Events are defined as indexes in an events directory. The events directory
	is created in the events instance <b><i>init</i></b> method and is therefore
	available after the inheriting class instance creation has called it's super 
	class <b><i>init</i></b> method, so the logical thing to do is to define the 
	class instance events in the inheriting class <b><i>init</i></b> method for 
	new instances. However events can also be defined later on by the inheriting 
	class instance methods.
	<p>
	Event handlers/listeners need to define an instance method with the name of
	the defined event. That method will be invoked by the triggering of the event.
	The handler/listener method should return the boolean <b>.true</b> or <b>.false</b>
	depending on whether possible other handlers/listeners should be given the 
	opportunity to act on the event. Returning <b>.false</b> will allow other 
	handlers/listeners to be invoked as well, returning <b>.true</b> will stop 
	invoking other handlers/listeners.
	<p>
	Handlers/Listeners are kept as members of an array that is the item in the 
	directory entry for the index that has the name of the event.
*/
::class eventable public mixinclass object

  /**
    The events attribute is a directory, whose entries can only be set privately
    by the inheriting class instance methods. The directory is created in the
    <b><i>init</i></b> instance method of this (the <i>events</i>) class.
  */
  ::attribute events get

  ::attribute events set private

  /**
    The <b><i>init</i></b> method creates the directory that will have the event 
    names as indexes and the array of handlers/listeners as items.
    <br>
    @param eventArray=(.array~new) - optional, an array of event names provided by the inheriting class
    <p>
    If an array with event names is provided, the entries are created in the events directory
    with empty arrays as items to hold future handlers/listeners.
  */
  ::method init 
    expose events
    use strict arg eventArray=(.array~new)
    events = .directory~new
    if eventArray~class~id==.array then do
      do a over eventArray
        events[a] = .array~new
      end
    end
  
  /**
    <b><i>addEventHander</i></b> is a synonym for <b><i>addEventListener</i></b>
    <br>
    @see #addEventListener
  */
  ::method addEventHandler
    use strict arg event, handler
    self~addEventListener(event, handler)

  /**
    Adds an object to the group of interested parties
    <br>
    @param event - the name of an event
    @param listener - the object interested in that event
    <p>
    If the array of handlers/listeners does not exist yet for the event it will
    be created. The handler/listener object is appended to the existing or new array
    <p>
    It is not verified if the event is a valid event for the object, the result for 
    that condition will be that the handler/listener will never be invoked.
    <p>
    The same handler/listener can be added multiple times, if for some reason that
    would be necessary for proper functioning and provided some earlier handler/listener 
    did not return <b>.true</b>.
  */
  ::method addEventListener
    expose events
    --trace i
    use strict arg event, listener
    if events[event]==.nil then do
      events[event] = .array~new
    end
    events[event]~append(listener)

  /**
    <b><i>removeEventHandler</i></b> is a synonym for <b><i>removeEventListener</i></b>
    <br>
    @see #removeEventListener
  */
  ::method removeEventHandler
    use strict arg event, handler
    self~removeEventListener(event, handler)

  /**
    Removes a party from the group interested in an event
    <br>
    @param event - the name of an event
    @param listener - the handler/listener object to be removed
    <p>
    If the event is not known, the object can not be removed obviously
    <br>
    If the listener is not a member of the interested handlers/listeners,
    nothing can be removed.
    <br>
    The handlers/listeners will be regenerated to prevent sparseness.
  */  
  ::method removeEventListener 
    expose events
    --trace i
    use strict arg event, listener
    if events[event]~class~id==.array then do
      events[event]~removeItem(listener)
      events[event] = events[event]~makeArray
    end

  /**
    Returns the list of defined events for  the object
    <br>
    @return anArray - an array of event names for the object
    <p>
    The array can be empty if no events have been defined
  */
  ::method eventNames
    use strict arg
    return self~events~makeArray

  /**
    <b><i>eventHandlers</i></b> is a synonym for <b><i>eventListeners</i></b>
    <br>
    @see #eventListeners
  */
  ::method eventHandlers
    use strict arg event
    return self~eventListeners(event)

  /**
    Returns an array of handlers/listeners for an event.
    <br>
    @param event - the name of an event 
    @return anArray - an array of handlers/listeners for an event
    <p>
    The returned array can be empty if there are no listeners for the event
    or if the event is unknown.
  */  
  ::method eventListeners
    expose events
    use strict arg event
    if events[event]~isA(.array) then return events[event]
    return .array~new
   
  /**
    Invokes the event handling method in the handlers/listeners for an
    event while successive handlers/listeners return <b>.false</b>, and stops 
    to invoke following handlers/listeners on the first <b>.true</b> return.
    <br>
    @expose events - the directory of triggerable events for the class
    @param event - the name of the event to be handled
    @param args=(.array~new) - optional arguments for the handler method
    <p>
  */ 
   ::method triggerEvent
    expose events
    --trace i
    use strict arg event, args=(.array~new)
    if events[event]==.nil then do
      --.error~lineout('Event:' event 'is not defined')
      return
    end
    if events[event]~items==0 then do
      --.error~lineout('Event:' event 'does not have listeners')
      return
    end
    result = .false
    do l over events[event] while result==.false
      if l~hasMethod(event) then do 
        msg = .message~new(l,event,'I',args~makeString('L',','))
        --.traceoutput~say(msg~messageName msg~target msg~arguments~makeString('L',','))
        msg~send --start
        result = msg~result
      end
      else do
        --.error~lineout('Event:' event 'is not a defined method in handler/listener:' l)
      end
    end
	
