package imseProc.core;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import mds.GMDSFetcher;

import org.eclipse.swt.widgets.Display;

import otherSupport.SettingsManager;
import otherSupport.bufferControl.DirectBufferControl;

/** Global stuff for IMSEProc */
public class IMSEProc {
	
	private static ExecutorService pool;
	private static LinkedBlockingQueue<Runnable> workQueue;
	
	public static ExecutorService getThreadPool(){
		if(pool == null){
			workQueue = new LinkedBlockingQueue<Runnable>(100);
			pool = new ThreadPoolExecutor(4,
							Math.min(4, 2*Runtime.getRuntime().availableProcessors()),
							1000,
							TimeUnit.SECONDS,
							workQueue);
		}
		return pool;
	}
	
	public static final void queueExec(Runnable runnable){
		getThreadPool().execute(runnable);
	}
		
	public static final void shutdown(){
		if(pool != null)
			pool.shutdownNow();
	}
	
	/** Updates sitting in the queue or in processor of running */
	private static ConcurrentHashMap<Object, ObjectUpdateProc> inProgressUpdates = new ConcurrentHashMap<Object, IMSEProc.ObjectUpdateProc>();
	
	/** Queue something to be run because the given object has updated making sure it is run at least once after the final call to this.
	 * 
	 * The runnable will be run as soon as possible.
	 * 
	 * If an update is already in progress, the new update will be run by that thread, once it's finished.
	 * The last queued update is guaranteed to be run, the first might be run if it starts running before the next one is queued
	 *  but any in the middle will not be run. 
	 *  
	 * @param object	Hashing object to identify updates that are updating the 'same thing'. Usually this is the class being updated.	
	 * @param runnable	What to run.
	 */
	public static void ensureFinalUpdate(Object object, Runnable update){
		ensureFinalUpdate(object, update, null);
	}
	
	public static void ensureFinalUpdate(Object object, Runnable update, Display swtUpdateDisplay){	
		synchronized (inProgressUpdates) {
			ObjectUpdateProc existing = inProgressUpdates.get(object);
			if(existing != null){
				existing.setFinalUpdate(update);
			}else{
				ObjectUpdateProc newUpdate = new ObjectUpdateProc(object, update, (swtUpdateDisplay != null));
				inProgressUpdates.put(object, newUpdate);
				try{
					if(swtUpdateDisplay != null){
						//swtUpdateQueue.add(newUpdate);
						swtUpdateDisplay.asyncExec(newUpdate);
						
					}else{
						queueExec(newUpdate);
					}
				}catch(RejectedExecutionException err){
					if(object != null)
						inProgressUpdates.remove(object);
				}
			}
		}
	}
	
	public static boolean isUpdateInProgress(Object object){
		synchronized (inProgressUpdates) {
			ObjectUpdateProc existing = inProgressUpdates.get(object);
			return (existing != null);
		}
	}
	
	private static class ObjectUpdateProc implements Runnable {
		private Runnable update;
		private Runnable finalUpdate;
		private Object object;
		private boolean isSWTUpdate;
				
		public ObjectUpdateProc(Object object, Runnable update, boolean isSWTUpdate) {
			this.isSWTUpdate = isSWTUpdate;
			this.object = object;
			this.update = update;
		}
		
		public void setFinalUpdate(Runnable finalUpdate) {
			this.finalUpdate = finalUpdate;
		}
		
		@Override
		public void run() {
			try{
				update.run();
			}catch(Throwable err){
				System.err.println("Caught exception/error in final update worker:");
				err.printStackTrace();
			}finally{
				if(finalUpdate != null) {
					//keep trying to run the 'final' one, until it really is the final one
					Runnable newFinal;
					do{
						newFinal = finalUpdate;
						if(newFinal != null){
							try{
								newFinal.run();
							}catch(Throwable err){
								System.err.println("Caught exception/error in final update worker:");
								err.printStackTrace();
							}
						}
					}while(finalUpdate != newFinal);
				}
				inProgressUpdates.remove(object);
			}
		}
	}
	
	private static LinkedList<Class> imgPipeClasses = new LinkedList<Class>();
	public static void addImgPipeClass(Class imageSinkClass){
		imgPipeClasses.add(imageSinkClass);
	}
	
	public static List<Class> getPipeClasses(){
		return imgPipeClasses;
	}
	
	public static List<Class> getSourceClasses(){
		LinkedList<Class> imgSourceClasses = new LinkedList<Class>();
		for(Class c : imgPipeClasses)
			if(ImgSource.class.isAssignableFrom(c))
				imgSourceClasses.add(c);
		return imgSourceClasses;
	}
	
	public static List<Class> getSinkClasses(){
		LinkedList<Class> imgSinkClasses = new LinkedList<Class>();
		for(Class c : imgPipeClasses)
			if(ImgSink.class.isAssignableFrom(c))
				imgSinkClasses.add(c);
		return imgSinkClasses;
	}	
	
	private static LinkedList<WeakReference<ImgPipe>> imgPipes = new LinkedList<WeakReference<ImgPipe>>();
	public static void addImgPipeInstance(ImgPipe imgPipe){
		imgPipes.add(new WeakReference<ImgPipe>(imgPipe));
	}
	
	public static LinkedList<WeakReference<ImgPipe>> getAllPipes(){
		return imgPipes;
	}
	
	public static LinkedList<ImgSource> getSourceInstances(){
		LinkedList<ImgSource> imgSources = new LinkedList<ImgSource>();
		for(WeakReference<ImgPipe> ref : imgPipes){
			ImgPipe p = ref.get();
			if(p != null && p instanceof ImgSource)
				imgSources.add((ImgSource)p);
		}
		return imgSources;
	}

	public static LinkedList<ImgSink> getSinkInstances(){
		LinkedList<ImgSink> imgSinks = new LinkedList<ImgSink>();
		for(WeakReference<ImgPipe> ref : imgPipes){
			ImgPipe p = ref.get();
			if(p != null && p instanceof ImgSink)
				imgSinks.add((ImgSink)p);
		}
		return imgSinks;
	}


	/*private static class SWTUpdate implements Runnable {
		private Display display;
		private Runnable update;
		public SWTUpdate(Display display, Runnable update) { this.display = display; this.update = update; }
		@Override
		public void run() {
			if(!display.isDisposed())
				System.out.println(update.toString());
				display.syncExec(update);
		}
	}

	// Same as ensureFinalUpdate(Object, Runnable), but runs the update inside a Display.syncExec() 
	public static void ensureFinalSWTUpdate(Display display, Object object, Runnable update){
		ensureFinalUpdate(object, new SWTUpdate(display, update), false);
	}
	//*/
	
	private static ConcurrentHashMap<Object, ObjectUpdateProc> inProgressSWTUpdates = new ConcurrentHashMap<Object, IMSEProc.ObjectUpdateProc>();
	//private static ConcurrentLinkedQueue<ObjectUpdateProc> swtUpdateQueue = new ConcurrentLinkedQueue<IMSEProc.ObjectUpdateProc>();
	
	public static void ensureFinalSWTUpdate(Display display, Object object, Runnable update){
		ensureFinalUpdate(object, update, display);
	}
	
	public static void msgLoop(Display display){
		//long tLast = System.currentTimeMillis();
        //int nDraw = 0;
        while (display.getShells().length > 0) {
        	try{
	          if (!display.readAndDispatch()) {
	        	/*
	        	
	        	long tNow = System.currentTimeMillis();
	        	if((tNow - tLast) > 1000){
	        		System.out.println((1000 * nDraw / (tNow - tLast)) + " fps");
	        		tLast = tNow;
	        		nDraw = 0;
	        	}
	        	  */
	        	//process the swt update queue
	        	/*ObjectUpdateProc updateProc = swtUpdateQueue.poll();
	        	
	        	if(updateProc != null)
	        		updateProc.run();
	        	*/ 
	            display.sleep();
	            
	          }
        	}catch(Throwable err){
        		System.err.println("Caught throwable in SWT main message loop:");
        		err.printStackTrace();
        	}
        }
	}

	public static GMDSFetcher globalGMDS() {
		String serverID = SettingsManager.defaultGlobal().getProperty("imseProc.cis.gmdsServerID", "pccis");
		GMDSFetcher gmds = GMDSFetcher.defaultInstance(serverID);
		gmds.setUseMemoryCache(false); //not for this app, we want to keep memory allocs fast
		return gmds;
	}
	
	public static String getMetaExp(ImgSource imgSrc){
		String experiment = null;
		if(imgSrc != null){
			Object obj = imgSrc.getSeriesMetaData("/gmds/experiment");
			if(obj != null){
				experiment = (obj instanceof String[]) ? ((String[])obj)[0] : (String)obj;
			}
		}
		if(experiment == null){
			return SettingsManager.defaultGlobal().getProperty("imseProc.cis.experiment", "DEFAULT");			
		}
		return experiment;
	}
	
	public static int getMetaPulse(ImgSource imgSrc){
		if(imgSrc == null)
			return 0;
		Object obj = imgSrc.getSeriesMetaData("/gmds/pulse");
		if(obj == null)
			return 0;
		int pulse;		
		if(obj instanceof Integer) 
			pulse = (Integer)obj;
		else if(obj instanceof int[])
			pulse = ((int[])obj)[0];
		else if(obj instanceof Integer[])
			pulse = ((Integer[])obj)[0];
		else{
			System.err.println("MetaData /gmds/pulse is not int or int[]");
			return 0;
		}
		
		return pulse;
	}


}
