package imseProc.gmds;


import imseProc.core.IMSEProc;
import imseProc.core.Img;
import imseProc.core.ByteBufferImage;
import imseProc.core.ImgSource;

import java.awt.Graphics;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import oneLiners.OneLiners;

import otherSupport.bufferControl.DirectBufferControl;

import descriptors.SignalDesc;
import descriptors.gmds.GMDSSignalDesc;
import signals.Signal;
import signals.gmds.GMDSSignal;

import mds.GMDSFetcher;
import mds.MdsPlus;
import mds.MdsPlusException;

/** Loads images from MDS+ source stored as in J.Howard's LabView software */
public class GMDSSource extends GMDSPipe implements ImgSource {
	
	public ByteBufferImage images[];
	       
	private int nImagesLoaded;
	
	private boolean abort = false;
	
	public GMDSSource() { super();	}
	
	public GMDSSource(GMDSFetcher gmds, String experiment, String path, int pulse) {
		super(gmds);
		
		loadTarget(experiment, pulse, path);
	}

	public void load() {
		IMSEProc.ensureFinalUpdate(this, new Runnable() { @Override public void run() { doLoad(); } });
	}

	public void doLoad() {
		nImagesLoaded = 0;

		int pulse = this.pulse;
		String experiment = this.experiment;
		String path = this.path;
		abort = false;		

		//take the controllers that we're loading
		updateAllControllers();
		
		//and hang the experiment, pulse etc on the image series so stuff knows where to write direct data
		setSeriesMetaData("gmds/experiment", experiment);
		setSeriesMetaData("gmds/pulse", pulse);
		
		loadMetaData();
			
		//first see if it's all in one sequence 
		GMDSSignalDesc imgDesc = new GMDSSignalDesc(pulse, experiment, path);
		try{
			System.out.println("GMDSSource: Attempt read of image sequence from signal '" + imgDesc.toString()+ "'.");
			Signal imgSig = gmds.getSig(imgDesc);
			readCombinedSequence(imgSig);
			
			System.out.println("GMDSSource: Load complete from combined sequence.");
			return;
			
		}catch(MdsPlusException err){
			int nImages = 0;
			
			//otherwise try separated images
			try{
				GMDSSignalDesc countDesc = new GMDSSignalDesc(pulse, experiment, path + "_COUNT");
				System.out.println("GMDSSource: No combined sequence, attempting read from signal '" + imgDesc.toString()+ "'.");
				Signal countSig = gmds.getSig(countDesc);
				nImages = ((int[])countSig.getData())[0];
				
			}catch(MdsPlusException e){
				nImages = -1;
			}
						
			try{
				
				//make some pre-emptive attempt to see how many signals there really are
				String sigs[] = gmds.dumpTree(experiment, pulse, "", true);
				int maxImgNo = 0;
				for(String sigName : sigs){
					if(sigName != null && sigName.matches("[/]*" + path + "_[0-9]*.*")){
						int imgNo = OneLiners.mustParseInt(sigName.replaceFirst("[/]*" + path + "_([0-9]*).*", "$1"));
						if(imgNo > maxImgNo)
							maxImgNo = imgNo;
					}
				}
				
				if(maxImgNo > 0 && maxImgNo != nImages-1){
					System.err.println("GMDSSource: WARNING: Last image found had number '"+maxImgNo+"' but _COUNT reported "+nImages+", using max.");
					nImages = maxImgNo+1;
				}
				
				updateAllControllers();
				
				readSeparateImages(nImages);
				
				System.out.println("MDS+ load done");
				
			}catch(MdsPlusException err2){
				System.err.println("Unable to read either combined image sequence or image[0] from MDS+");
				nImagesLoaded = 0;				
				notifyImageSetChanged();
				updateAllControllers();
			}
		}

		loadMetaData();
	}
	
	/** Images saved J Howard's LabView program are saved in a single long signal
	 * and are always short[][][]  */ 
	private void readCombinedSequence(Signal imgSig){
					
		short imageData[][][] = (short[][][])imgSig.getData();
		int nImages = imageData.length;
		int height = imageData[0].length;
		int width = imageData[0][0].length;
		int bitDepth = 12; //err, where is this stored?
		ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; // ... why??

		nImagesLoaded = 0;
		updateAllControllers();
		ByteBufferImage newSet[] = ByteBufferImage.checkBulkAllocation(GMDSSource.this, images, width, height, bitDepth, nImages, byteOrder);
		if(newSet != images){				
			images = newSet;				
			notifyImageSetChanged();
		}else{          
			if(newSet != null){
				for(int j=0; j < newSet.length; j++){
					if(newSet[j] != null){
						newSet[j].invalidate();
					}
				}
			}
		}

		for(int i=0; i < nImages; i++) {           	
			ByteBuffer buf = images[i].getWritableBuffer();
			buf.order(byteOrder);

			ShortBuffer iBuff = buf.asShortBuffer();
			for(int iY=0; iY < height; iY++) {
				iBuff.put(imageData[i][iY]);
			}
			
			System.out.println("GMDSSource: Img["+i+" / "+nImages + "] read complete.");
			
			nImagesLoaded = i+1;
			updateAllControllers();
			
			if(abort)return;
		}
		
	}
	
	/** Images saved by GMDSSink have a _COUNT signal, and then _### signals for the images.
	 * They can in principal be any type.
	 * 
	 * @param nImages */
	private void readSeparateImages(int nImages){
		ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN;

		int width = -1, height = -1;
	
		for(int i=0; i < nImages; i++){
			
			SignalDesc imgDesc = new GMDSSignalDesc(pulse, experiment, path + "_" + i);				
			Signal imgSig;
			try{
				imgSig = gmds.getSig(imgDesc);
			}catch(MdsPlusException err){
				System.err.println("GMDSSource: Error reading image "+i+" from signal '" + imgDesc + "'.");
				if(images != null && images.length > i && images[i] != null)
					images[i].invalidate();
				continue;
			}
			
			if(imgSig.getRank() < 2)
				throw new RuntimeException("Data in signal '" + imgDesc + "' is not 2D.");

			Object imageData[] = (Object[])imgSig.getData();	
			int w = Array.getLength(imageData[0]);
			int h = imageData.length;
			
			if(width > 0){
				if(height != h || width != w){
					System.err.println("Image in signal " + imgDesc + " is " + w + "x" + h + " but first was " + width + "x" + height);
					images[i].invalidate();
					continue;
				}
				
			}else{
				//now we have width/height info, do bulk allocation
				height = h;
				width = w;
			
				int bitDepth = 0;
				if(imageData instanceof short[][]){                                     
					bitDepth = 16;
				}else if(imageData instanceof int[][]){
					bitDepth = 32;                                  
				}else if(imageData instanceof double[][]){
					bitDepth = -64;
				}
				ByteBufferImage newSet[] = ByteBufferImage.checkBulkAllocation(GMDSSource.this, images, width, height, bitDepth, nImages, byteOrder);
				if(newSet != images){						
					images = newSet;
					notifyImageSetChanged();
				}else{
					if(newSet != null){
						for(int j=0; j < newSet.length; j++){
							if(newSet[j] != null){
								newSet[j].invalidate();
							}
						}
					}
				}
				
			}


			try {
				images[i].startWriting();
			}catch (InterruptedException e) {
				images[i].invalidate();
				continue;
			}

			try{
				ByteBuffer buf = images[i].getWritableBuffer();

				if(imageData instanceof short[][]){		
					ShortBuffer iBuff = buf.asShortBuffer();
					for(int iY=0; iY < height; iY++) {
						iBuff.put(((short[][])imageData)[iY]);
					}

				}else if(imageData instanceof int[][]){
					IntBuffer iBuff = buf.asIntBuffer();
					for(int iY=0; iY < height; iY++) {
						iBuff.put(((int[][])imageData)[iY]);
					}

				}else if(imageData instanceof double[][]){
					DoubleBuffer iBuff = buf.asDoubleBuffer();
					for(int iY=0; iY < height; iY++) {
						iBuff.put(((double[][])imageData)[iY]);
					}

				}else
					throw new RuntimeException("Unknown object format from signal '"+imgDesc+"': " + imageData.getClass().getName());
			}finally{
				images[i].endWriting();
			}

			images[i].imageChanged(false);
		
			System.out.println("GMDSSource: Img["+i+" / "+images.length+ "] read complete.");

			nImagesLoaded = i+1;
			updateAllControllers();
			
			if(abort)return;
		}

		updateAllControllers();
			
	}
	
	private void loadMetaData(){
		seriesMetaData.clear();
		
		loadMetaData(true); //the old sequence data (pre Nov2013)
		loadMetaData(false);			
		
		//hackery
		String notes = getNotes(experiment, pulse);
		Pattern p = Pattern.compile("AUGPULSE=([0-9]*)", Pattern.CASE_INSENSITIVE);

		Matcher matcher = p.matcher(notes);
		if(matcher.find()){
			String pulseStr = matcher.group(1);			
			try{
				int pulse = Integer.parseInt(pulseStr);
				seriesMetaData.put("/aug/pulse", pulse);
			}catch (NumberFormatException e) { }
		}
		
	}
	
	private void loadMetaData(boolean seqData) {
		//look for the new one first
		String containingPath = ("/" + path).replaceFirst("/[^/]+$","");
		String rootPath = containingPath + (seqData ? "/seqData" : "/seriesData");
		String sigsPaths[] = gmds.dumpTree(experiment, pulse, rootPath, true);
		
		for(String signalPath : sigsPaths){
			GMDSSignalDesc sigDesc = new GMDSSignalDesc(pulse, experiment, rootPath + "/" + signalPath);
			
			try{
				GMDSSignal sig = (GMDSSignal)gmds.getSig(sigDesc);
				
				Object data = sig.getData();
				
				if(seqData){		
					//we put the old sequence data in under seqXXX incase it clashes with a series (I don't think it ever did)
					seriesMetaData.put("/seq" + signalPath, data);					
				}else{
					seriesMetaData.put(signalPath, data);
				}
				
			}catch(RuntimeException err){
				System.err.println("Couldn't read MetaData signal '" + sigDesc + "': " + err.getMessage());				
			}
		}
	}
			
	public void loadTarget(String experiment, int pulse, String path) {
		
		nImagesLoaded = 0;
		this.experiment = experiment;
		this.pulse = pulse;
		this.path = path;
		this.abort = true; //cancel any existing run
		 
		load();
		
	}
	
	@Override
	public int getNumImages() { return (images == null) ? 0 : images.length; }

	@Override
	public Img getImage(int imgIdx) {
		return (images != null && imgIdx >= 0 && imgIdx < images.length) ? images[imgIdx] : null;
	}
	
	@Override
	public Img[] getImageSet() {
		Img imageSet[] = new Img[nImagesLoaded];
		for(int i=0; i < imageSet.length; i++)
			imageSet[i] =  getImage(i);
		return imageSet;
	}
	
	@Override
	public GMDSSource clone() {
		return new GMDSSource(gmds, experiment, path, pulse);
	}
	
	public boolean isLoading(){ 
		return !isIdle(); 
	}
	
	@Override
	public int[] getLoadSaveStatus() {
		return new int[]{
				isLoading() ? nImagesLoaded : -1, 
				(images == null) ? -1 : images.length
		};
	}
	

}
