package org.cocos2d;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

//import org.cocos2d.actions.CCTimer;
import org.cocos2d.actions.UpdateCallback;
import org.cocos2d.include.ccConfig;
import org.cocos2d.include.ccMacros;
import org.cocos2d.utils.CCFormatter;
import org.cocos2d.utils.collections.ConcurrentArrayHashMap;

/**
 * @addtogroup global
 * @{
 */

//
//CCScheduler
//
/** @brief Scheduler is responsible for triggering the scheduled callbacks.
 * You should not use NSTimer. Instead use this class.
 * 
 * There are 2 different types of callbacks (selectors):
 * 
 * - update selector: the 'update' selector will be called every frame. You can customize the priority.
 * - custom selector: A custom selector will be called every frame, or with a custom interval of time
 * 
 * The 'custom selectors' should be avoided when possible. It is faster, and consumes less memory to use the 'update selector'.
 * 
 */
public class CCScheduler {
	private static final String TAG = CCScheduler.class.getSimpleName();

	// Priority level reserved for system services.
	public static final int kCCPrioritySystem = Integer.MIN_VALUE;

	// Minimum priority level for user scheduling.
	public static final int kCCPriorityNonSystemMin = (kCCPrioritySystem+1);

	public CCScheduler() {
		m_fTimeScale = 1.0f;
		m_pUpdatesNegList = new ArrayList<tListEntry>();
		m_pUpdates0List = new ArrayList<tListEntry>();
		m_pUpdatesPosList = new ArrayList<tListEntry>();
		m_pHashForUpdates = new ConcurrentHashMap<Object, tHashUpdateEntry>();
		m_pHashForTimers = new ConcurrentArrayHashMap<Object, tHashTimerEntry>();
		m_pCurrentTarget = null;
		m_bCurrentTargetSalvaged = false;
		m_bUpdateHashLocked = false;
//		m_pScriptHandlerEntries = null;

		// TODO legacy -->

		// used to trigger CCTimer#update
		updateSelector = "update";
//        try {
//			impMethod = CCTimer.class.getMethod(updateSelector, Float.TYPE);
//    	} catch (NoSuchMethodException e) {
//    		impMethod = null;
//    		e.printStackTrace();
//    	}

		// updates with priority
		hashForUpdates = new ConcurrentHashMap<Object, tHashSelectorEntry>();
		hashForSelectors = new ConcurrentArrayHashMap<Object, tHashSelectorEntry>();

		// selectors with interval
		currentTarget = null;
		currentTargetSalvaged = false;

		// TODO legacy <--
	}

	public float getTimeScale() {
		return m_fTimeScale;
	}

	/** Modifies the time of all scheduled callbacks.
	 * You can use this property to create a 'slow motion' or 'fast forward' effect.
	 * Default is 1.0. To create a 'slow motion' effect, use values below 1.0.
	 * To create a 'fast forward' effect, use values higher than 1.0.
	 * @since v0.8
	 * @warning It will affect EVERY scheduled selector / action.
	 */
	public void setTimeScale(float fTimeScale) {
		m_fTimeScale = fTimeScale;
	}

	/** 'update' the scheduler.
	 * You should NEVER call this method, unless you know what you are doing.
	 */
	public void update(float dt) {
		m_bUpdateHashLocked = true;

		if(m_fTimeScale != 1.0f) {
			dt *= m_fTimeScale;
		}

		// updates with priority < 0
		synchronized(m_pUpdatesNegList) {
			for(tListEntry pEntry : m_pUpdatesNegList) {
				if((! pEntry.paused) && (! pEntry.markedForDeletion)) {
					if(pEntry.callback != null) {
						pEntry.callback.update(dt);
					} else {
						try {
							pEntry.impMethod.invoke(pEntry.target, dt);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		}

		// updates with priority == 0
		synchronized(m_pUpdates0List) {
			for(tListEntry pEntry : m_pUpdates0List) {
				if((! pEntry.paused) && (! pEntry.markedForDeletion)) {
					if(pEntry.callback != null) {
						pEntry.callback.update(dt);
					} else {
						try {
							pEntry.impMethod.invoke(pEntry.target, dt);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		}

		// updates with priority > 0
		synchronized(m_pUpdatesPosList) {
			for(tListEntry pEntry : m_pUpdatesPosList) {
				if((! pEntry.paused) && (! pEntry.markedForDeletion)) {
					if(pEntry.callback != null) {
						pEntry.callback.update(dt);
					} else {
						try {
							pEntry.impMethod.invoke(pEntry.target, dt);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		}

		// Iterate over all the custom selectors
		for(ConcurrentArrayHashMap<Object, tHashTimerEntry>.Entry e = m_pHashForTimers.firstValue();e != null;) {
			tHashTimerEntry elt = e.getValue();

			m_pCurrentTarget = elt;
			m_bCurrentTargetSalvaged = false;

			if(! m_pCurrentTarget.paused) {
				// The 'timers' array may change while inside this loop
				for(elt.timerIndex = 0; elt.timerIndex < elt.timers.size(); ++(elt.timerIndex)) {
					elt.currentTimer = elt.timers.get(elt.timerIndex);
					elt.currentTimerSalvaged = false;

					elt.currentTimer.update(dt);

					if(elt.currentTimerSalvaged) {
						// The currentTimer told the remove itself. To prevent the timer from
						// accidentally deallocating itself before finishing its step, we retained
						// it. Now that step is done, it's safe to release it.
						elt.currentTimer = null;
					}

					elt.currentTimer = null;
				}
			}

			// elt, at this moment, is still valid
			// so it is safe to ask this here (issue #490)
			e = m_pHashForTimers.nextValue(e);

			// only delete currentTarget if no actions were scheduled during the cycle (issue #481)
			if (m_bCurrentTargetSalvaged && m_pCurrentTarget.timers.size() == 0) {
				removeHashElement(m_pCurrentTarget);
			}
		}
/* TODO
		// Iterate over all the script callbacks
		if(m_pScriptHandlerEntries) {
			for(int i = m_pScriptHandlerEntries.size() - 1;i >= 0;i--) {
				CCSchedulerScriptHandlerEntry pEntry = m_pScriptHandlerEntries.get(i);
				if(pEntry.isMarkedForDeletion()) {
					m_pScriptHandlerEntries.remove(i);
				} else if(! pEntry.isPaused()) {
					pEntry.getTimer().update(dt);
				}
			}
		}
*/
		// delete all updates that are marked for deletion
		// updates with priority < 0
		synchronized(m_pUpdatesNegList) {
			for(tListEntry pEntry : m_pUpdatesNegList) {
				if(pEntry.markedForDeletion) {
					this.removeUpdateFromHash(pEntry);
				}
			}
		}

		// updates with priority == 0
		synchronized(m_pUpdates0List) {
			for(tListEntry pEntry : m_pUpdates0List) {
				if(pEntry.markedForDeletion) {
					this.removeUpdateFromHash(pEntry);
				}
			}
		}

		// updates with priority > 0
		synchronized(m_pUpdatesPosList) {
			for(tListEntry pEntry : m_pUpdatesPosList) {
				if(pEntry.markedForDeletion) {
					this.removeUpdateFromHash(pEntry);
				}
			}
		}

		m_bUpdateHashLocked = false;

		m_pCurrentTarget = null;



		// TODO legacy -->

		currentTargetSalvaged = false;

		// updates with priority < 0
        synchronized (m_pUpdatesNegList) {
        	int len = m_pUpdatesNegList.size();
        	
        	try {  // BRIGOSX 24JUL2012 protect execution flaw
        		for (int i = 0; i < len; i++) {
        			tListEntry e = m_pUpdatesNegList.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (InvocationTargetException e1) {
        						if(e1.getTargetException() instanceof RuntimeException)
        	        				throw (RuntimeException)e1.getTargetException();
        	        			else
        	        				e1.printStackTrace();
        					} catch (Exception e1) {
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdatesNegList.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }

        // updates with priority == 0
        synchronized (m_pUpdates0List) {
        	int len = m_pUpdates0List.size();
        	
        	try { // BRIGOSX 24JUL2012 protect execution flaw
        		for(int i=0; i < len; i++) {
        			tListEntry e = m_pUpdates0List.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (Exception e1) {
        						// TODO Auto-generated catch block
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdates0List.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }
        
        // updates with priority > 0
        synchronized (m_pUpdatesPosList) {
        	int len = m_pUpdatesPosList.size();
        	
        	try { // BRIGOSX 24JUL2012 protect execution flaw
        		for (int i=0; i < len; i++) {
        			tListEntry e = m_pUpdatesPosList.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (Exception e1) {
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdatesPosList.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }

        for(ConcurrentArrayHashMap<Object, tHashSelectorEntry>.Entry e = hashForSelectors.firstValue();
        	e != null; e = hashForSelectors.nextValue(e)) {
        	tHashSelectorEntry elt = e.getValue();

        	currentTarget = elt;
            currentTargetSalvaged = false;

            if( ! currentTarget.paused && elt.timers != null) {
                // The 'timers' ccArray may change while inside this loop.
                for( elt.timerIndex = 0; elt.timerIndex < elt.timers.size(); elt.timerIndex++) {
                    elt.currentTimer = elt.timers.get(elt.timerIndex);
                    elt.currentTimerSalvaged = false;

                    elt.currentTimer.update(dt);

                    if( elt.currentTimerSalvaged ) {
                        // The currentTimer told the remove itself. To prevent the timer from
                        // accidentally deallocating itself before finishing its step, we retained
                        // it. Now that step is done, it's safe to release it.
                        elt.currentTimer = null;
                    }
                    
                    elt.currentTimer = null;
                }			
            }
	            
	            // elt, at this moment, is still valid
	            // so it is safe to ask this here (issue #490)
	            // elt=elt->hh.next;
	            
	            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
            if( currentTargetSalvaged && currentTarget.timers.isEmpty()) {
//            	removeHashElement(elt);
            	hashForSelectors.remove(elt.target);
                // [self removeHashElement:currentTarget];
            }
        }

        currentTarget = null;
        
	}

	/** The scheduled method will be called every 'interval' seconds.
	 * If paused is YES, then it won't be called until it is resumed.
	 * If 'interval' is 0, it will be called every frame, but if so, it's recommended to use 'scheduleUpdateForTarget:' instead.
	 * If the selector is already scheduled, then only the interval parameter will be updated without re-scheduling it again.
	 * repeat let the action be repeated repeat + 1 times, use kCCRepeatForever to let the action run continuously
	 * delay is the amount of time the action will wait before it'll start
	 * 
	 * @since v0.99.3, repeat and delay added in v1.1
	 */
	public void scheduleSelector(String pfnSelector, Object pTarget, float fInterval, int repeat, float delay, boolean bPaused) {
		assert pfnSelector != null: "Argument selector must be non-NULL";
		assert pTarget != null: "Argument target must be non-NULL";	

		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);

		if(pElement == null) {
			pElement = new tHashTimerEntry();
			pElement.target = pTarget;
			m_pHashForTimers.put(pTarget, pElement);

			// Is this the 1st element ? Then set the pause level to all the selectors of this target
			pElement.paused = bPaused;
		} else {
			assert pElement.paused == bPaused : "";
		}

		if(pElement.timers == null) {
			pElement.timers = new ArrayList<CCTimer>();
		} else {
			for(CCTimer timer : pElement.timers) {
				if(pfnSelector.equals(timer.getSelector())) {
					ccMacros.CCLOG(TAG, CCFormatter.format("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer.getInterval(), fInterval));
					timer.setInterval(fInterval);
					return;
				}
			}
		}

		CCTimer pTimer = new CCTimer();
		pTimer.initWithTarget(pTarget, pfnSelector, fInterval, repeat, delay);
		pElement.timers.add(pTimer);
	}

	/** calls scheduleSelector with kCCRepeatForever and a 0 delay */
	public void scheduleSelector(String pfnSelector, Object pTarget, float fInterval, boolean bPaused) {
		this.scheduleSelector(pfnSelector, pTarget, fInterval, ccMacros.kCCRepeatForever, 0.0f, bPaused);
	}

	/** Schedules the 'update' selector for a given target with a given priority.
	 * The 'update' selector will be called every frame.
	 * The lower the priority, the earlier it is called.
	 * @since v0.99.3
	 */
	public void scheduleUpdateForTarget(Object pTarget, int nPriority, boolean bPaused) {
		tHashUpdateEntry pHashElement;
		pHashElement = m_pHashForUpdates.get(pTarget);
		if(pHashElement != null) {
			if (ccConfig.COCOS2D_DEBUG >= 1) {
				assert pHashElement.entry.markedForDeletion : "";
			}
			// TODO: check if priority has changed!

			pHashElement.entry.markedForDeletion = false;
			return;
		}

		// TODO legacy -->
		tHashSelectorEntry hashElement;
		hashElement = hashForUpdates.get(pTarget);
		if(hashElement != null) {
			if (ccConfig.COCOS2D_DEBUG >= 1) {
				assert hashElement.entry.markedForDeletion : "";
			}
			// TODO: check if priority has changed!

			hashElement.entry.markedForDeletion = false;
			return;
		}
		// TODO legacy <--

		// most of the updates are going to be 0, that's why there
		// is an special list for updates with priority 0
		if(nPriority == 0) {
			appendIn(m_pUpdates0List, pTarget, bPaused);
		} else if( nPriority < 0 ) {
			priorityIn(m_pUpdatesNegList, pTarget, nPriority, bPaused);
		} else {
			// priority > 0
			priorityIn(m_pUpdatesPosList, pTarget, nPriority, bPaused);
		}
	}

	/** Unschedule a selector for a given target.
	 * If you want to unschedule the "update", use unscheudleUpdateForTarget.
	 * @since v0.99.3
	 */
	public void unscheduleSelector(String pfnSelector, Object pTarget) {
		// explicitly handle nil arguments when removing an object
		if((pTarget == null) || (pfnSelector == null)) {
			return;
		}

		// assert pTarget != null: "Target MUST not be null";
		// assert pfnSelector != null: "Selector MUST not be null";

		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);

		if(pElement != null) {
			for(int i = 0;i < pElement.timers.size();i++) {
				CCTimer timer = pElement.timers.get(i);

				if(pfnSelector.equals(timer.getSelector())) {
					if(timer == pElement.currentTimer && (! pElement.currentTimerSalvaged)) {                        
						pElement.currentTimerSalvaged = true;
					}

					pElement.timers.remove(i);

					// update timerIndex in case we are in tick:, looping over the actions
					if(pElement.timerIndex >= i) {
						pElement.timerIndex--;
					}

					if(pElement.timers.size() > 0) {
						if(m_pCurrentTarget == pElement) {
							m_bCurrentTargetSalvaged = true;
						} else {
							removeHashElement(pElement);
						}
					}

					return;
				}
			}
		}
	}

	/** Unschedules the update selector for a given target
	 * @since v0.99.3
	 */
	public void unscheduleUpdateForTarget(final Object pTarget) {
		if(pTarget == null) {
			return;
		}

		tHashUpdateEntry pElement = null;
		pElement = m_pHashForUpdates.get(pTarget);
		if(pElement != null) {
			synchronized(pElement.list) {
				if(m_bUpdateHashLocked) {
					pElement.entry.markedForDeletion = true;
				} else {
					this.removeUpdateFromHash(pElement.entry);
				}
			}
	
			m_pHashForUpdates.remove(pTarget);
		}

		// TODO legacy -->
		tHashSelectorEntry entry = null;
		entry = hashForUpdates.get(pTarget);
		if(entry != null) {
			synchronized(entry.list) {
				if(currentEntry==entry.entry) {
					currentTargetSalvaged = true;
				} else {
					entry.list.remove(entry.entry);
				}
			}
	
			m_pHashForUpdates.remove(pTarget);
		}
		// TODO legacy <--
	}

	/** Unschedules all selectors for a given target.
	 * This also includes the "update" selector.
	 * @since v0.99.3
	 */
	public void unscheduleAllForTarget(Object pTarget) {
		// explicit NULL handling
		if(pTarget == null) {
			return;
		}

		// Custom Selectors
		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);

		if(pElement != null) {
			if(pElement.timers.contains(pElement.currentTimer)
					&& (! pElement.currentTimerSalvaged)) {
				pElement.currentTimerSalvaged = true;
			}
			pElement.timers.clear();

			if(m_pCurrentTarget == pElement) {
				m_bCurrentTargetSalvaged = true;
			} else {
				removeHashElement(pElement);
			}
		}

		// TODO legacy -->
		// Custom Selectors
		tHashSelectorEntry element = hashForSelectors.get(pTarget);

		if( element != null) {
			if(!element.currentTimerSalvaged ) {
				// element.currentTimer retain;
				element.currentTimerSalvaged = true;
			}
			element.timers.clear();
			// ccArrayRemoveAllObjects(element->timers);
			if( currentTarget == element )
				currentTargetSalvaged = true;
			else {
				hashForSelectors.remove(element.target);
//           	this.removeHashElement(element.target, element);
				// [self removeHashElement:element];
			}
		}

		// TODO legacy <--

		// Update Selector
		unscheduleUpdateForTarget(pTarget);
	}

	/** Unschedules all selectors from all targets.
	 * You should NEVER call this method, unless you know what you are doing.
	 * 
	 * @since v0.99.3
	 */
	public void unscheduleAll() {
		// Custom Selectors
		for(ConcurrentArrayHashMap<Object, tHashSelectorEntry>.Entry e = hashForSelectors.firstValue();
				e != null; e = hashForSelectors.nextValue(e)) {
			tHashSelectorEntry element = e.getValue();

			Object target = element.target;
			unscheduleAllForTarget(target);
		}

		// Updates selectors
		for (tListEntry entry:m_pUpdates0List) {
			unscheduleUpdateForTarget(entry.target);
		}
		for (tListEntry entry:m_pUpdatesNegList) {
			unscheduleUpdateForTarget(entry.target);
		}
		for (tListEntry entry:m_pUpdatesPosList) {
			unscheduleUpdateForTarget(entry.target);
		}
	}

	/** Unschedules all selectors from all targets with a minimum priority.
	 * You should only call this with kCCPriorityNonSystemMin or higher.
	 * @since v2.0.0
	 */
	public void unscheduleAllWithMinPriority(int nMinPriority) {
		;
	}

	/** The scheduled script callback will be called every 'interval' seconds.
	 * If paused is YES, then it won't be called until it is resumed.
	 * If 'interval' is 0, it will be called every frame.
	 * return schedule script entry ID, used for unscheduleScriptFunc().
	 */
	public int scheduleScriptFunc(int nHandler, float fInterval, boolean bPaused) {
/* TODO
		CCSchedulerScriptHandlerEntry pEntry = CCSchedulerScriptHandlerEntry.create(nHandler, fInterval, bPaused);
		if(m_pScriptHandlerEntries == null) {
			m_pScriptHandlerEntries = new ArrayList<CCSchedulerScriptHandlerEntry>();
		}
		m_pScriptHandlerEntries.add(pEntry);
		return pEntry.getEntryId();
*/
		return 0;
	}

	/** Unschedule a script entry. */
	public void unscheduleScriptEntry(int uScheduleScriptEntryID) {
/* TODO
		for(int i = m_pScriptHandlerEntries.size() - 1;i >= 0;i--) {
			CCSchedulerScriptHandlerEntry pEntry = m_pScriptHandlerEntries.get(i));
			if(pEntry.getEntryId() == uScheduleScriptEntryID) {
				pEntry.markedForDeletion();
				break;
			}
		}
*/
	}

	/** Pauses the target.
	 * All scheduled selectors/update for a given target won't be 'ticked' until the target is resumed.
	 * If the target is not present, nothing happens.
	 * @since v0.99.3
	 */
	public void pauseTarget(Object pTarget) {
		assert pTarget != null : "target must be non nil";

		// custom selectors
		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);
		if(pElement != null) {
			pElement.paused = true;
		}

		// update selector
		tHashUpdateEntry pElementUpdate = null;
		pElementUpdate = m_pHashForUpdates.get(pTarget);
		if(pElementUpdate != null) {
			assert pElementUpdate.entry != null : "";
			pElementUpdate.entry.paused = true;
		}

		// TODO legacy -->
		// Custom selectors
		tHashSelectorEntry element = hashForSelectors.get(pTarget);
		if( element != null )
			element.paused = true;

		// Update selector
		tHashSelectorEntry elementUpdate = hashForUpdates.get(pTarget);
		if( elementUpdate != null) {
			assert elementUpdate.target != null:"pauseTarget: unknown error";
			elementUpdate.setPaused(true);
		}
		// TODO legacy <--
	}

	/** Resumes the target.
	 * The 'target' will be unpaused, so all schedule selectors/update will be 'ticked' again.
	 * If the target is not present, nothing happens.
	 * @since v0.99.3
	 */
	public void resumeTarget(Object pTarget) {
		assert  pTarget != null: "target must be non nil";

		// custom selectors
		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);
		if(pElement != null) {
			pElement.paused = false;
		}

		// update selector
		tHashUpdateEntry pElementUpdate = null;
		pElementUpdate = m_pHashForUpdates.get(pTarget);
		if(pElementUpdate != null) {
			assert pElementUpdate.entry != null : "";
			pElementUpdate.entry.paused = false;
		}

		// TODO legacy -->

		// Custom Selectors
		tHashSelectorEntry element = hashForSelectors.get(pTarget);
		if( element != null )
			element.paused = false;

		// Update selector
		tHashSelectorEntry elementUpdate = hashForUpdates.get(pTarget);
		if( elementUpdate != null) {
			assert elementUpdate.target != null: "resumeTarget: unknown error";
			elementUpdate.setPaused(false);
		}

		// TODO legacy <--
	}

	/** Returns whether or not the target is paused
	 * @since v1.0.0
	 */
	public boolean isTargetPaused(Object pTarget) {
		assert pTarget != null : "target must be non nil";

		// Custom selectors
		tHashTimerEntry pElement = null;
		pElement = m_pHashForTimers.get(pTarget);
		if(pElement != null) {
			return pElement.paused;
		}

		// We should check update selectors if target does not have custom selectors
		tHashUpdateEntry elementUpdate = null;
		elementUpdate = m_pHashForUpdates.get(pTarget);
		if(elementUpdate != null) {
			return elementUpdate.entry.paused;
		}

		return false;	// should never get here
	}

	/** Pause all selectors from all targets.
	 * You should NEVER call this method, unless you know what you are doing.
	 * @since v2.0.0
	 */
/* TODO
	public CCSet* pauseAllTargets() {
		;
	}
*/
	/** Pause all selectors from all targets with a minimum priority.
	 * You should only call this with kCCPriorityNonSystemMin or higher.
	 * @since v2.0.0
	 */
/* TODO
	public CCSet* pauseAllTargetsWithMinPriority(int nMinPriority) {
		;
	}
*/
	/** Resume selectors on a set of targets.
	 * This can be useful for undoing a call to pauseAllSelectors.
	 * @since v2.0.0
	 */
/* TODO
	public void resumeTargets(CCSet* targetsToResume) {
		;
	}
*/

	private void removeHashElement(tHashTimerEntry pElement) {
		pElement.timers.clear();
		pElement.timers = null;
		pElement.target = null;
		m_pHashForTimers.remove(pElement);
	}

	private void removeUpdateFromHash(tListEntry entry) {
		tHashUpdateEntry element;

		element = m_pHashForUpdates.get(entry.target);
		if(element != null) {
			// list entry
			element.list.remove(element.entry);

			// hash entry
			m_pHashForUpdates.remove(element);
		}
	}

	// update specific

	public void priorityIn(ArrayList<tListEntry> ppList, Object pTarget, int nPriority, boolean bPaused) {
		tListEntry pListElement = new tListEntry();

		pListElement.target = pTarget;
		pListElement.priority = nPriority;
		pListElement.paused = bPaused;
		pListElement.markedForDeletion = false;

		// TODO legacy -->

		if(pTarget instanceof UpdateCallback) {
			pListElement.callback = (UpdateCallback)pTarget;
		} else {
			try {
				pListElement.impMethod = pTarget.getClass().getMethod(updateSelector, Float.TYPE);
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			}
		}

		synchronized (ppList) {
			if(ppList.isEmpty()) {
				ppList.add(pListElement);
			} else {
				boolean added = false;

				int len = ppList.size();
				for( int i = 0; i < len; i++ ) {
					tListEntry elem = ppList.get(i);
					if( nPriority < elem.priority ) {
						ppList.add(i, pListElement);
						added = true;
						break;
					}
				}

				// Not added? priority has the higher value. Append it.
				if( !added )
					ppList.add(pListElement);
			}
		}

		// update hash entry for quick access
		tHashSelectorEntry hashElement = new tHashSelectorEntry();
		hashElement.target = pTarget;
		hashElement.list = ppList;
		hashElement.entry = pListElement;
		hashForUpdates.put(pTarget, hashElement);
	}

	public void appendIn(ArrayList<tListEntry> ppList, Object pTarget, boolean bPaused) {
		tListEntry pListElement = new tListEntry();

		pListElement.target = pTarget;
		pListElement.paused = bPaused;
		pListElement.markedForDeletion = false;

		// TODO legacy -->

		if(pTarget instanceof UpdateCallback) {
			pListElement.callback = (UpdateCallback)pTarget;
		} else {
			try {
				pListElement.impMethod = pTarget.getClass().getMethod(updateSelector, Float.TYPE);
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			}
		}

		synchronized (ppList) {
			ppList.add(pListElement);
		}

		// update hash entry for quicker access
		tHashSelectorEntry hashElement = new tHashSelectorEntry();
		hashElement.target = pTarget;
		hashElement.list = ppList;
		hashElement.entry = pListElement;
		hashForUpdates.put(pTarget, hashElement);

		// TODO legacy <--
	}

	protected float m_fTimeScale;

	//
	// "updates with priority" stuff
	//
	protected ArrayList<tListEntry> m_pUpdatesNegList;	// list of priority < 0
	protected ArrayList<tListEntry> m_pUpdates0List;	// list priority == 0
	protected ArrayList<tListEntry> m_pUpdatesPosList;	// list priority > 0
	protected ConcurrentHashMap<Object, tHashUpdateEntry> m_pHashForUpdates;	// hash used to fetch quickly the list entries for pause,delete,etc

	// Used for "selectors with interval"
	protected ConcurrentArrayHashMap<Object, tHashTimerEntry> m_pHashForTimers;
	protected tHashTimerEntry m_pCurrentTarget;
	protected boolean m_bCurrentTargetSalvaged;
	// If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
	protected boolean m_bUpdateHashLocked;
// TODO	protected CCArray* m_pScriptHandlerEntries;

	// A list double-linked list used for "updates with priority"
	private static class tListEntry {
		public Object	target;				// not retained (retained by hashUpdateEntry)
		public int		priority;
		public boolean	paused;
		public boolean	markedForDeletion;	// selector will no longer be called and entry will be removed at end of the next tick

		// TODO legacy -->
		public Method impMethod;
		public UpdateCallback callback; // instead of method invocation
	};

	private static class tHashUpdateEntry {
		ArrayList<tListEntry>	list;	// Which list does it belong to ?
		tListEntry				entry;	// entry in the list
		public Object			target;	// hash key (retained)
		// UT_hash_handle      hh;
	}

	// Hash Element used for "selectors with interval"
	private static class tHashTimerEntry {
		ArrayList<CCTimer>	timers;
		Object				target;		// hash key (retained)
		int					timerIndex;
		CCTimer				currentTimer;
		boolean				currentTimerSalvaged;
		boolean				paused;
		// UT_hash_handle	hh;
	}

	public interface SEL_SCHEDULE {
		public void update(float dt);
	}

	// TODO legacy -->

	// Hash Element used for "selectors with interval"
	private static class tHashSelectorEntry {
		ArrayList<CCTimer>		timers;
		Object					target;		// hash key (retained)
		int						timerIndex;
		CCTimer					currentTimer;
		boolean					currentTimerSalvaged;
		boolean					paused;
		// UT_hash_handle  hh;

		ArrayList<tListEntry>	list;
		tListEntry				entry;
		void setPaused(boolean b){
			paused = b;
			if (entry != null){
				entry.paused = b;
			}
		}
	}

	// Used for "selectors with interval"
	ConcurrentHashMap<Object, tHashSelectorEntry> hashForUpdates;	// hash used to fetch quickly the list entries for pause,delete,etc
	ConcurrentArrayHashMap<Object, tHashSelectorEntry>  hashForSelectors;
	protected tHashSelectorEntry currentTarget;
	protected boolean currentTargetSalvaged;
    
    tListEntry							currentEntry;
    
	
	// Optimization
//	Method			    impMethod;
	String				updateSelector;

    /** Modifies the time of all scheduled callbacks.
      You can use this property to create a 'slow motion' or 'fast fordward' effect.
      Default is 1.0. To create a 'slow motion' effect, use values below 1.0.
      To create a 'fast fordward' effect, use values higher than 1.0.
      @since v0.8
      @warning It will affect EVERY scheduled selector / action.
    */

    private static CCScheduler _sharedScheduler = null;

    /** returns a shared instance of the Scheduler */
    public static CCScheduler sharedScheduler() {
        if (_sharedScheduler != null) {
            return _sharedScheduler;
        }
        synchronized (CCScheduler.class) {
            if (_sharedScheduler == null) {
                _sharedScheduler = new CCScheduler();
            }
            return _sharedScheduler;
        }
    }

    /** purges the shared scheduler. It releases the retained instance.
      @since v0.99.0
      */
    public static void purgeSharedScheduler() {
        _sharedScheduler = null;
    }

//    private void removeHashElement(Object key, tHashSelectorEntry element){
//    	removeHashElement(element);
//        hashForSelectors.remove(key);
//    }
//    
//    private void removeHashElement(tHashSelectorEntry element)
//    {
//    	element.timers.clear();
//        element.timers = null;
//        element.target = null;
//    }

    /** 'tick' the scheduler.
      You should NEVER call this method, unless you know what you are doing.
    */
    public void tick(float dt) {
        if( m_fTimeScale != 1.0f )
            dt *= m_fTimeScale;
        
        currentTargetSalvaged = false;
        // updates with priority < 0
        synchronized (m_pUpdatesNegList) {
        	int len = m_pUpdatesNegList.size();
        	
        	try {  // BRIGOSX 24JUL2012 protect execution flaw
        		for (int i = 0; i < len; i++) {
        			tListEntry e = m_pUpdatesNegList.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (InvocationTargetException e1) {
        						if(e1.getTargetException() instanceof RuntimeException)
        	        				throw (RuntimeException)e1.getTargetException();
        	        			else
        	        				e1.printStackTrace();
        					} catch (Exception e1) {
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdatesNegList.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }

        // updates with priority == 0
        synchronized (m_pUpdates0List) {
        	int len = m_pUpdates0List.size();
        	
        	try { // BRIGOSX 24JUL2012 protect execution flaw
        		for(int i=0; i < len; i++) {
        			tListEntry e = m_pUpdates0List.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (Exception e1) {
        						// TODO Auto-generated catch block
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdates0List.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }
        
        // updates with priority > 0
        synchronized (m_pUpdatesPosList) {
        	int len = m_pUpdatesPosList.size();
        	
        	try { // BRIGOSX 24JUL2012 protect execution flaw
        		for (int i=0; i < len; i++) {
        			tListEntry e = m_pUpdatesPosList.get(i);
        			currentEntry = e;
        			if( ! e.paused ) {
        				if(e.callback !=null) {
        					e.callback.update(dt);
        				} else {
        					try {
        						e.impMethod.invoke(e.target, dt);
        					} catch (Exception e1) {
        						e1.printStackTrace();
        					}
        				}
        				if(currentTargetSalvaged) {
        					m_pUpdatesPosList.remove(i);
        					i--;
        					len--;
        					currentTargetSalvaged = false;
        				}
        			}
        		}
        	} catch(IndexOutOfBoundsException e) {
        		e.printStackTrace();
        	}
        	
	        currentEntry = null;
        }
        
        for(ConcurrentArrayHashMap<Object, tHashSelectorEntry>.Entry e = hashForSelectors.firstValue();
        	e != null; e = hashForSelectors.nextValue(e)) {
        	tHashSelectorEntry elt = e.getValue();

        	currentTarget = elt;
            currentTargetSalvaged = false;

            if( ! currentTarget.paused && elt.timers != null) {
                // The 'timers' ccArray may change while inside this loop.
                for( elt.timerIndex = 0; elt.timerIndex < elt.timers.size(); elt.timerIndex++) {
                    elt.currentTimer = elt.timers.get(elt.timerIndex);
                    elt.currentTimerSalvaged = false;

                    elt.currentTimer.update(dt);

                    if( elt.currentTimerSalvaged ) {
                        // The currentTimer told the remove itself. To prevent the timer from
                        // accidentally deallocating itself before finishing its step, we retained
                        // it. Now that step is done, it's safe to release it.
                        elt.currentTimer = null;
                    }
                    
                    elt.currentTimer = null;
                }			
            }
	            
	            // elt, at this moment, is still valid
	            // so it is safe to ask this here (issue #490)
	            // elt=elt->hh.next;
	            
	            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
            if( currentTargetSalvaged && currentTarget.timers.isEmpty()) {
//            	removeHashElement(elt);
            	hashForSelectors.remove(elt.target);
                // [self removeHashElement:currentTarget];
            }
        }
        currentTarget = null;
//        }
    }

    static class SchedulerTimerAlreadyScheduled extends RuntimeException {

		/**
		 * 
		 */
		private static final long serialVersionUID = 5996803998420105321L;

		public SchedulerTimerAlreadyScheduled(String reason) {
            super(reason);
        }
    }

    static class SchedulerTimerNotFound extends RuntimeException {
        /**
		 * 
		 */
		private static final long serialVersionUID = -1912889437889458701L;

		public SchedulerTimerNotFound(String reason) {
            super(reason);
        }
    }

    /** The scheduled method will be called every 'interval' seconds.
      If paused is YES, then it won't be called until it is resumed.
      If 'interval' is 0, it will be called every frame, but if so, it recommened to use 'scheduleUpdateForTarget:' instead.

      @since v0.99.3
    */
    public void schedule(String selector, Object target, float interval, boolean paused) {
        assert selector != null: "Argument selector must be non-nil";
        assert target != null: "Argument target must be non-nil";	

        tHashSelectorEntry element = hashForSelectors.get(target);

        if( element == null ) {
            element = new tHashSelectorEntry();
            element.target = target;
            hashForSelectors.put(target, element);
            // Is this the 1st element ? Then set the pause level to all the selectors of this target
            element.paused = paused;

        } else {
            assert element.paused == paused : "CCScheduler. Trying to schedule a selector with a pause value different than the target";
        }

        if( element.timers == null) {
            element.timers = new ArrayList<CCTimer>();
        }/* else if( element.timers.size() == element.timers )
            ccArrayDoubleCapacity(element->timers);
		*/
//        CCTimer timer = new CCTimer(target, selector, interval);
        CCTimer timer = CCTimer.timerWithTarget(target, selector, interval);
        element.timers.add(timer);
    }
    
    /*
     * This is java way version, uses interface based callbacks. UpdateCallback in this case.
     * It would be preffered solution. It is more polite to Java, GC, and obfuscation.  
     */
    public void schedule(UpdateCallback callback, Object target, float interval, boolean paused) {
        assert callback != null: "Argument callback must be non-nil";
        assert target != null: "Argument target must be non-nil";	

        tHashSelectorEntry element = hashForSelectors.get(target);

        if( element == null ) {
            element = new tHashSelectorEntry();
            element.target = target;
            hashForSelectors.put(target, element);
            // Is this the 1st element ? Then set the pause level to all the selectors of this target
            element.paused = paused;

        } else {
            assert element.paused == paused : "CCScheduler. Trying to schedule a selector with a pause value different than the target";
        }

        if( element.timers == null) {
            element.timers = new ArrayList<CCTimer>();
        }/* else if( element.timers.size() == element.timers )
            ccArrayDoubleCapacity(element->timers);
		*/
//        CCTimer timer = new CCTimer(target, callback, interval);
        CCTimer timer = CCTimer.timerWithTarget(target, callback, interval);
        element.timers.add(timer);
    }

	/** Unschedule a selector for a given target.
	 * If you want to unschedule the "update", use unscheudleUpdateForTarget.
	 * @since v0.99.3
	 */
	public void unschedule(String pfnSelector, Object pTarget) {
		// explicity handle nil arguments when removing an object
		if( pTarget==null || pfnSelector==null)
			return;

		assert pTarget != null: "Target MUST not be null";
		assert pfnSelector != null: "Selector MUST not be null";

		tHashSelectorEntry element = hashForSelectors.get(pTarget);
		if( element != null ) {
			for( int i=0; i< element.timers.size(); i++ ) {
				CCTimer timer = element.timers.get(i);

				if(pfnSelector.equals(timer.getSelector())) {
					if( timer == element.currentTimer && !element.currentTimerSalvaged ) {                        
						element.currentTimerSalvaged = true;
					}

					element.timers.remove(i);

					// update timerIndex in case we are in tick:, looping over the actions
					if( element.timerIndex >= i )
						element.timerIndex--;

					if( element.timers.isEmpty()) {
						if( currentTarget == element ) {
							currentTargetSalvaged = true;
						} else {
							hashForSelectors.remove(element.target);
//                       	this.removeHashElement(element.target, element);
						}
					}
					return;
				}
			}
		}

		// Not Found
		//	NSLog(@"CCScheduler#unscheduleSelector:forTarget: selector not found: %@", selString);
	}

    /*
     * This is java way version, uses interface based callbacks. UpdateCallback in this case.
     * It would be preffered solution. It is more polite to Java, GC, and obfuscation.  
     */
    public void unschedule(UpdateCallback callback, Object target) {
        // explicity handle nil arguments when removing an object
        if( target==null || callback==null)
            return;

        assert target != null: "Target MUST not be null";
        assert callback != null: "Selector MUST not be null";

        tHashSelectorEntry element = hashForSelectors.get(target);
        if( element != null ) {
            for( int i=0; i< element.timers.size(); i++ ) {
                CCTimer timer = element.timers.get(i);

                if(callback == timer.getCallback()) {
                    if( timer == element.currentTimer && !element.currentTimerSalvaged ) {                        
                        element.currentTimerSalvaged = true;
                    }
                    	
                    element.timers.remove(i);

                    // update timerIndex in case we are in tick:, looping over the actions
                    if( element.timerIndex >= i )
                        element.timerIndex--;

                    if( element.timers.isEmpty()) {
                        if( currentTarget == element ) {
                            currentTargetSalvaged = true;						
                        } else {
                        	hashForSelectors.remove(element.target);
//                        	this.removeHashElement(element.target, element);
                        }
                    }
                    return;
                }
            }
        }

        // Not Found
        //	NSLog(@"CCScheduler#unscheduleSelector:forTarget: selector not found: %@", selString);
    }

    /*
     * This is java way version, uses interface based callbacks. UpdateCallback in this case.
     * It would be preffered solution. It is more polite to Java, GC, and obfuscation. 
     * Target class must implement UpdateCallback or scheduleUpdate will be used.
     */
	public void scheduleUpdate(UpdateCallback target, int priority, boolean paused) {
        // TODO Auto-generated method stub
        if (ccConfig.COCOS2D_DEBUG >= 1) {
        	tHashSelectorEntry hashElement = hashForUpdates.get(target);
            assert hashElement == null:"CCScheduler: You can't re-schedule an 'update' selector'. Unschedule it first";
        }

        // most of the updates are going to be 0, that's way there
        // is an special list for updates with priority 0
        if( priority == 0 ) {
        	this.appendIn(m_pUpdates0List, target, paused);
        } else if( priority < 0 ) {
        	this.priorityIn(m_pUpdatesNegList, target, priority, paused);
        } else { // priority > 0
        	this.priorityIn(m_pUpdatesPosList, target, priority, paused);
        }
	}

    /** schedules a Timer.
     It will be fired in every frame.
     
     @deprecated Use scheduleSelector:forTarget:interval:paused instead. Will be removed in 1.0
    */
    public void scheduleTimer(CCTimer timer) {
        assert false: "Not implemented. Use scheduleSelector:forTarget:";
    }

    /** unschedules an already scheduled Timer
     
     @deprecated Use unscheduleSelector:forTarget. Will be removed in v1.0
     */
    public void unscheduleTimer(CCTimer timer) {
	    assert false: "Not implemented. Use unscheduleSelector:forTarget:";
    }

    /** unschedule all timers.
     You should NEVER call this method, unless you know what you are doing.
     
     @deprecated Use scheduleAllSelectors instead. Will be removed in 1.0
     @since v0.8
     */
    public void unscheduleAllTimers() {
	    assert false:"Not implemented. Use unscheduleAllSelectors";
    }

    @Override
    public void finalize () throws Throwable  {
        unscheduleAll();
        _sharedScheduler = null;

        super.finalize();
    }

}

//
// CCTimer
//
/** @brief Light-weight timer */
//
class CCTimer {

	public CCTimer() {
		m_pTarget = null;
		m_fElapsed = -1;
		m_bRunForever = false;
		m_bUseDelay = false;
		m_uTimesExecuted = 0;
		m_uRepeat = 0;
		m_fDelay = 0.0f;
		m_fInterval = 0.0f;
		m_pfnSelector = null;
		callback = null;
		m_nScriptHandler = 0;
	}

	/** get interval in seconds */
	public float getInterval() {
		return m_fInterval;
	}

	/** set interval in seconds */
	public void setInterval(float fInterval) {
		m_fInterval = fInterval;
	}

	public String getSelector() {
		return m_pfnSelector;
	}

	public UpdateCallback getCallback() {
		return callback;
	}

	/** Initializes a timer with a target and a selector. */
	public boolean initWithTarget(Object pTarget, String pfnSelector) {
		return initWithTarget(pTarget, pfnSelector, 0, ccMacros.kCCRepeatForever, 0.0f);
	}

	/** Initializes a timer with a target, a selector and an interval in seconds, repeat in number of times to repeat, delay in seconds. */
	public boolean initWithTarget(Object pTarget, String pfnSelector, float fSeconds, int nRepeat, float fDelay) {
		m_pTarget = pTarget;
		m_pfnSelector = pfnSelector;
		callback = null;
		m_fElapsed = -1;
		m_fInterval = fSeconds;
		m_fDelay = fDelay;
		m_bUseDelay = (fDelay > 0.0f) ? true : false;
		m_uRepeat = nRepeat;
		m_bRunForever = (nRepeat == ccMacros.kCCRepeatForever) ? true : false;

		return true;
	}

	/** Initializes a timer with a target and a selector. */
	public boolean initWithTarget(Object pTarget, UpdateCallback pfnCallback) {
		return initWithTarget(pTarget, pfnCallback, 0, ccMacros.kCCRepeatForever, 0.0f);
	}

	/** Initializes a timer with a target, a selector and an interval in seconds, repeat in number of times to repeat, delay in seconds. */
	public boolean initWithTarget(Object pTarget, UpdateCallback pfnCallback, float fSeconds, int nRepeat, float fDelay) {
		m_pTarget = pTarget;
		m_pfnSelector = null;
		callback = pfnCallback;
		m_fElapsed = -1;
		m_fInterval = fSeconds;
		m_fDelay = fDelay;
		m_bUseDelay = (fDelay > 0.0f) ? true : false;
		m_uRepeat = nRepeat;
		m_bRunForever = (nRepeat == ccMacros.kCCRepeatForever) ? true : false;
		return true;
	}

	/** Initializes a timer with a script callback function and an interval in seconds. */
	public boolean initWithScriptHandler(int nHandler, float fSeconds) {
		m_nScriptHandler = nHandler;
		m_fElapsed = -1;
		m_fInterval = fSeconds;

		return true;
	}

	/** triggers the timer */
	public void update(float dt) {
		if(m_fElapsed == -1) {
			m_fElapsed = 0;
			m_uTimesExecuted = 0;
		} else {
			if(m_bRunForever && !m_bUseDelay) {
				//standard timer usage
				m_fElapsed += dt;
				if (m_fElapsed >= m_fInterval) {
					goSelector();

					if(m_nScriptHandler != 0) {
// TODO: CCScriptEngineManager						CCScriptEngineManager.sharedManager().getScriptEngine().executeSchedule(m_nScriptHandler, m_fElapsed);
					}
					m_fElapsed = 0;
				}
			} else {
				//advanced usage
				m_fElapsed += dt;
				if(m_bUseDelay) {
					if(m_fElapsed >= m_fDelay) {
						goSelector();

						if(m_nScriptHandler != 0) {
// TODO: CCScriptEngineManager							CCScriptEngineManager.sharedManager().getScriptEngine().executeSchedule(m_nScriptHandler, m_fElapsed);
						}

						m_fElapsed = m_fElapsed - m_fDelay;
						m_uTimesExecuted += 1;
						m_bUseDelay = false;
					}
				} else {
					if (m_fElapsed >= m_fInterval) {
						goSelector();

						if(m_nScriptHandler != 0) {
// TODO: CCScriptEngineManager							CCScriptEngineManager.sharedManager().getScriptEngine().executeSchedule(m_nScriptHandler, m_fElapsed);
						}

						m_fElapsed = 0;
						m_uTimesExecuted += 1;
					}
				}

				if((! m_bRunForever) && (m_uTimesExecuted > m_uRepeat)) {
					//unschedule timer
// TODO: CDirector#getScheduler()					CCDirector.sharedDirector().getScheduler().unscheduleSelector(m_pfnSelector, m_pTarget);
				}
			}
		}
	}

	/** Allocates a timer with a target and a selector. */
	public static CCTimer timerWithTarget(Object pTarget, String pfnSelector) {
		CCTimer pTimer = new CCTimer();

		pTimer.initWithTarget(pTarget, pfnSelector, 0.0f, ccMacros.kCCRepeatForever, 0.0f);

		return pTimer;
	}

	/** Allocates a timer with a target, a selector and an interval in seconds. */
	public static CCTimer timerWithTarget(Object pTarget, String pfnSelector, float fSeconds) {
		CCTimer pTimer = new CCTimer();

		pTimer.initWithTarget(pTarget, pfnSelector, fSeconds, ccMacros.kCCRepeatForever, 0.0f);

		return pTimer;
	}

	/** Allocates a timer with a target and a selector. */
	public static CCTimer timerWithTarget(Object pTarget, UpdateCallback pfnCallback) {
		CCTimer pTimer = new CCTimer();

		pTimer.initWithTarget(pTarget, pfnCallback, 0.0f, ccMacros.kCCRepeatForever, 0.0f);

		return pTimer;
	}

	/** Allocates a timer with a target, a selector and an interval in seconds. */
	public static CCTimer timerWithTarget(Object pTarget, UpdateCallback pfnCallback, float fSeconds) {
		CCTimer pTimer = new CCTimer();

		pTimer.initWithTarget(pTarget, pfnCallback, fSeconds, ccMacros.kCCRepeatForever, 0.0f);

		return pTimer;
	}

	/** Allocates a timer with a script callback function and an interval in seconds. */
	static CCTimer timerWithScriptHandler(int nHandler, float fSeconds) {
		CCTimer pTimer = new CCTimer();

		pTimer.initWithScriptHandler(nHandler, fSeconds);

		return pTimer;
	}

	public int getScriptHandler() {
		return m_nScriptHandler;
	}

	private void goSelector() {
		if(callback != null) {
			callback.update(m_fElapsed);
		} else {
			try {
				Class<?> cls = m_pTarget.getClass();
				Method invocation = cls.getMethod(m_pfnSelector, Float.TYPE);
				invocation.invoke(m_pTarget, m_fElapsed);
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	protected Object m_pTarget;
	protected float m_fElapsed;
	protected boolean m_bRunForever;
	protected boolean m_bUseDelay;
	protected int m_uTimesExecuted;
	protected int m_uRepeat; //0 = once, 1 is 2 x executed
	protected float m_fDelay;
	protected float m_fInterval;
	protected String m_pfnSelector;
	protected UpdateCallback callback;

	protected int m_nScriptHandler;
}

// end of global group
/// @}
