package com.informagen;

import java.awt.Image;
import java.awt.image.ImageProducer;
import java.awt.image.MemoryImageSource;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.Toolkit;


import com.apple.mrj.MRJOSType;

import com.apple.mrj.jdirect.GenericHandle;
import com.apple.mrj.jdirect.HandleStruct;
import com.apple.mrj.jdirect.ByteArrayStruct;
import com.apple.mrj.jdirect.PointerStruct;
import com.apple.mrj.jdirect.Struct;


// Package containing the LOCK object
import com.apple.mrj.macos.toolbox.Toolbox;

import  com.apple.mrj.macos.libraries.InterfaceLib;


public class MacClipboard {

	public static boolean isMacintosh() {
		return System.getProperty("os.name").equalsIgnoreCase("Mac OS");
	}


	public static boolean isTEXT() {
		return isType("TEXT");
	}
	
	
	public static boolean isPICT() {
		return isType("PICT");
	}


	public static boolean isType(String inType){
		MRJOSType resType = new MRJOSType(inType);
		return isType(resType);
	}


	public static boolean isType(MRJOSType resType){
			
		int   jDirectHdl = MemoryFunctions.NewHandle(0);
		int[] dontCare = new int[1];
		
		int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, resType.toInt(), dontCare);
		
		MemoryFunctions.DisposeHandle(jDirectHdl);
		
		return scrapReturn != ErrorConstants.noTypeErr;
	}



	// The current MRJ virtual machine supports this type of scrap, ie
	//
	//		clipboard = getToolkit().getSystemClipboard();
	//		Transferable contents = clipboard.getContents(this);
	//		String string = (String) contents.getTransferData(DataFlavor.stringFlavor);
	//
	// However, this will work as well and is included for completeness

	public static String getText() {
	
		// Must be scrap type of 'TEXT'
		
		MRJOSType TEXTtype = new MRJOSType("TEXT");
		
		short osErr;

		int jDirectHdl = MemoryFunctions.NewHandle(0);
		int[] dontCare = new int[1];
						
		int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, TEXTtype.toInt(), dontCare);
		
		String returnString = null;
		
		if ( scrapReturn != ErrorConstants.noTypeErr ) {
					
			int size = MemoryFunctions.GetHandleSize(jDirectHdl);
			GenericHandle scrapHdl = new GenericHandle(jDirectHdl);
			
			byte[] bytes = scrapHdl.getBytesAt(0, size);
			
			returnString =  new String(bytes);
		}
		
		MemoryFunctions.DisposeHandle(jDirectHdl);
		
		return returnString;

	}



	public static Image getImage() {
	
		// These variables set within the synchronized block but are 
		//   referenced outside of it
			
		int pixelSize = 0;
		int pixelType = 0; 
		int cmpSize = 0; 
		int colorTable = 0; 
		short rowBytes = 0;
		byte[] pixels = null;
			
		
		short osErr;
		
		int  jDirectHdl = MemoryFunctions.NewHandle(0);
		int[] dontCare = new int[1];
				

		// Test for PICT scrap type
		
		int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, new MRJOSType("PICT").toInt(), dontCare);
		
		if ( scrapReturn == ErrorConstants.noTypeErr ) {
			MemoryFunctions.DisposeHandle(jDirectHdl);
			return null;
		}
			

		// OK, we have a 'PICT' from clipboard, turn it into a "java.awt.Image" //////////////////////
		
		// Get and lock the scrap handle in order to obtain the PICT bounding rectangle
				
		GenericHandle hDest = new GenericHandle(jDirectHdl);
		
		MemoryFunctions.HLock(jDirectHdl);
		
		short height = (short)(hDest.getShortAt(6) - hDest.getShortAt(2));
		short width = (short)(hDest.getShortAt(8) - hDest.getShortAt(4));
		
		MemoryFunctions.HUnlock(jDirectHdl);


		// Don't let JVM AWT peers at the Mac Toolbox while we are working.

		synchronized( Toolbox.LOCK ) {
			
			// Save the current GWorld, which is the Monitor's Desktop
			// JDirect uses single element arrays used to retrieve Toolbox values
						
			int [] refPort = new int[1];
			int [] refGDhdl = new int[1];

			QDOffscreenFunctions.GetGWorld(refPort, refGDhdl);
			

			// Dereference the first value of each array
			
			int currPort = refPort[0];
			GDeviceStruct gds = new GDeviceStruct(refGDhdl[0]);
			
			
			// Create an offscreen GWorld in which to render this PICT scrap
			// Use a single element array to get the reference to the offscreenGWorld
			
			int[] refInt = new int[1];
			
			short PixelDepth = 0;
			
			RectStruct boundsRect = new RectStruct();
			boundsRect.setTop((short)0);
			boundsRect.setLeft((short)0);
			boundsRect.setBottom(height);
			boundsRect.setRight(width);
						
			osErr = QDOffscreenFunctions.NewGWorld(refInt, PixelDepth, boundsRect, null, null, 0);
			
			int offscreenGWorld = refInt[0];

			
			// Make the offscreen GWorld the one in which to do the drawing
			// Create a PictureStruct from the 'PICT' scrap handle, pass it
			//  to DrawPicture to be rendered.
			
			QDOffscreenFunctions.SetGWorld(offscreenGWorld, gds);
			
			PictureStruct aPict = new PictureStruct(jDirectHdl);
			
			QuickdrawFunctions.DrawPicture(aPict, boundsRect);


			// Now that we have the 'PICT' drawn for us, we will convert it's pixels into
			//  an array of Java ints in order to make an Image.

			PixMapStruct pm = new PixMapStruct(QDOffscreenFunctions.GetGWorldPixMap(offscreenGWorld));
					
			int packType   = pm.getPackType();
			rowBytes   = (short)(pm.getRowBytes() & 0x3fff);
			pixelType  = pm.getPixelType();
			pixelSize  = pm.getPixelSize();
			colorTable = pm.getPmTable();
			cmpSize    = pm.getCmpSize();
			
			int baseAddr = QDOffscreenFunctions.GetPixBaseAddr(pm);
			
			QDOffscreenFunctions.LockPixels(pm);
			
			ImageStruct pix = new ImageStruct(baseAddr, rowBytes, height);
			
			pixels = pix.getBytes();
			
			QDOffscreenFunctions.UnlockPixels(pm);
		

			// Restore the GWorld to the main screen.

			QDOffscreenFunctions.SetGWorld(currPort, gds);
			

			// Dispose of structures and handles
							
			//QuickdrawFunctions.DisposePixMap(pm);

			QDOffscreenFunctions.DisposeGWorld(offscreenGWorld);
						
			MemoryFunctions.DisposeHandle(jDirectHdl);
		
		}	
			
		// Using the raw pixel data obtained from the Toolbox create an ImageProducer
		//   and then an image.
				
		ImageProducer pixelSource = null;

		if ( pixelType == QuickdrawConstants.RGBDirect )
			pixelSource = fromRGBDirect(pixelSize, cmpSize, height, width, rowBytes, pixels);	
		else if ( pixelType == QuickdrawConstants.clutType )
			pixelSource = fromCLUT(colorTable, cmpSize, height, width, rowBytes, pixels);


		// Create and return the java.awt.Image

		if (pixelSource != null)
			return Toolkit.getDefaultToolkit().createImage(pixelSource);
		else
			return null;

	}
	
	private static ImageProducer fromRGBDirect(int pixelSize, int cmpSize, short height, short width, int rowBytes, byte[] pixels) {	

		int i = 0;
		int ii = 0;
		int row = 0;
		
		int bytesPerPixel = pixelSize/8;

		int[] imagePixels = new int[(rowBytes/bytesPerPixel) * height];
		
		while ( row++ < height ) {
						
			for (int j=0; j<rowBytes; j=j+bytesPerPixel) {
			
				// Build the Quickdraw pixel from raw bytes
				
				int pixel = 0;
			
				for (int jj=0; jj<bytesPerPixel; jj++) {
					pixel <<= 8;
					pixel += ((int)pixels[i++]) & 0xFF;
				}

				// Extract out the r,g,b values
				
				int mask = 1;
				mask <<= cmpSize;
				mask--;

				int b = pixel & mask;
				pixel >>= cmpSize; 
				int g = pixel & mask;
				pixel >>= cmpSize;
				int r = pixel & mask;

				r <<= 8 - cmpSize;
				g <<= 8 - cmpSize;
				b <<= 8 - cmpSize;
				
										
				// Repack the r,g,b values into an java.awt.Image int array
				
				imagePixels[ii++] = 0xFF <<24 | r << 16 | g << 8 | b;

			}
		}
		
		return new MemoryImageSource(width, height, imagePixels, 0, rowBytes/bytesPerPixel);
		
	}


	//
	// As of MRJ 2.1.2 "DirectColorModel" did not work properly for all color depths
	//

	private static ImageProducer fromRGBDirect2(int pixelSize, int cmpSize, short height, short width, int rowBytes, byte[] pixels) {	

		int bytesPerPixel = pixelSize/8;

		// Create a mask cmpSize bits wide
		int mask = 1;
		mask <<= cmpSize;
		mask--;

		int redMask = mask << (cmpSize * 2);
		int greenMask = mask << cmpSize;
		int blueMask = mask;
		
		ColorModel cm = new DirectColorModel(pixelSize, redMask, greenMask, blueMask);
		
		int i = 0;
		int ii = 0;
		int row = 0;
		
		int[] imagePixels = new int[(rowBytes/bytesPerPixel) * height];
		
		while ( row++ < height ) {
									
			for (int j=0; j<rowBytes; j=j+bytesPerPixel) {
			
				// Build the Quickdraw pixel from raw bytes
				
				int pixel = 0;
			
				for (int jj=0; jj<bytesPerPixel; jj++) {
					pixel <<= 8;
					pixel |= ((int)pixels[i++]) & 0xFF;
				}
				
										
				// Copy this value into an java.awt.Image int array
				
				imagePixels[ii++] = pixel;
			}
		}
		
		return new MemoryImageSource(width, height, cm, imagePixels, 0, rowBytes/bytesPerPixel);
	}


	
	private static ImageProducer fromCLUT(int colorTable, int cmpSize, short height, short width, int rowBytes, byte[] pixels) {	
	
		ColorTableStruct cTab = new ColorTableStruct(colorTable);
		
		int ctSize = cTab.getCtSize();
		
		RGBColorStruct rgbColor = new RGBColorStruct();

		// Build an indexed color model
		
		byte[] reds = new byte[ctSize];
		byte[] greens = new byte[ctSize];
		byte[] blues = new byte[ctSize];
		
		for (int index=0; index<ctSize; index++) {
			QuickdrawFunctions.Index2Color(index, rgbColor);
			reds[index] = (byte)(((int)rgbColor.getRed()) & 0xFF);
			greens[index] = (byte)(((int)rgbColor.getGreen()) & 0xFF);
			blues[index] = (byte)(((int)rgbColor.getBlue()) & 0xFF);
		}
		
		ColorModel cm = new IndexColorModel(cmpSize, ctSize, reds, greens, blues);


		// Move the PICT index values (bytes) into an int array
		
		int i = 0;
		int ii = 0;
		int row = 0;

		int[] imagePixels = new int[rowBytes * height];

		while ( row++ < height )
			for (int j=0; j<rowBytes; j++)
				imagePixels[ii++] = ((int)pixels[i++]) & 0xFF;
				
		return new MemoryImageSource(width, height, cm, imagePixels, 0, rowBytes);

	}	
	
	

}

// Could not find a similar structure wrapper in the JDirect sample code.

class ImageStruct extends PointerStruct {

	short rowBytes;
	short numRows;


	public ImageStruct(int inPtr, short inRowBytes, short inNumRows) {
	
		super(inPtr);
		
		rowBytes = inRowBytes;
		numRows = inNumRows;
	}


	public int getSize() { return rowBytes * numRows; }

}


// Macintosh Toolbox JDirect ///////////////////////////////////////////////////
//
//  Copy from MRJ JDirect Sample Code files only what is needed.
//
//  NB: During Development include everything by using the files, Quickdraw.java,
//      QDOffscreen.java, MacTypes.java, and PictUtils.java.  Although these files
//      could be left in the final build they add over 80K to the application. By
//      extracting out only those structures and methods needed to support this class
//      this space overhead can be minimized.  By using package names this technique
//      can be used in other places without name conflicts.
//

class ScrapFunctions implements InterfaceLib {

	private ScrapFunctions() {}

	public native static int GetScrap(int hDest, int theType, int [] offset);

}



interface ErrorConstants {
	public final short 	noErr     = (short)0;     // No error
	public final short 	noTypeErr = (short)-102;  // No object of that type in scrap
}




class MemoryFunctions implements InterfaceLib {

	private MemoryFunctions() {};

	public native static int GetHandleSize(int h);
	public native static int NewHandle(int h);
	
	public native static void HLock(int h);
	public native static void HUnlock(int h);
	
	public native static void DisposeHandle(int h);

}



class QDOffscreenFunctions implements InterfaceLib {


	private QDOffscreenFunctions() {};

	public static short NewGWorld(int [] offscreenGWorld, short PixelDepth, RectStruct boundsRect, ColorTableStruct cTable, GDeviceStruct aGDevice, int flags) {
		 return NewGWorld(offscreenGWorld, PixelDepth, boundsRect.getByteArray(), (cTable != null) ? cTable.getHandle() : 0, (aGDevice != null) ? aGDevice.getHandle() : 0, flags);
	}
	public native static short NewGWorld(int [] offscreenGWorld, short PixelDepth, byte[] boundsRect, int cTable, int aGDevice, int flags);
	
	
	public static boolean LockPixels(PixMapStruct pm) { return LockPixels(pm.getHandle()); }
	public native static boolean LockPixels(int pm);


	public static void UnlockPixels(PixMapStruct pm) { UnlockPixels(pm.getHandle()); }
	public native static void UnlockPixels(int pm);

	public native static void GetGWorld(int [] port, int [] gdh);


	public static void SetGWorld(int port, GDeviceStruct gdh) { SetGWorld(port, gdh.getHandle()); }
	public native static void SetGWorld(int port, int gdh);

	public native static int GetGWorldPixMap(int offscreenGWorld);

	public static int GetPixBaseAddr(PixMapStruct pm) { return GetPixBaseAddr(pm.getHandle()); }
	public native static int GetPixBaseAddr(int pm);

	public native static void DisposeGWorld(int offscreenGWorld);
	
}

public interface QuickdrawConstants {

	public final int RGBDirect = 16;
	public final int clutType  = 0;

}

class QuickdrawFunctions implements InterfaceLib {

	private QuickdrawFunctions() {};


	public static void DrawPicture(PictureStruct myPicture, RectStruct dstRect) {
		DrawPicture(myPicture.getHandle(), dstRect.getByteArray());
	}
	public native static void DrawPicture(int myPicture, byte[] dstRect);


	public static void Index2Color(int index, RGBColorStruct aColor) {
		Index2Color(index, aColor.getByteArray());
	}
	public native static void Index2Color(int index, byte[] aColor);

}



// From MacType.java ////////////////////////////////////////////////////////////////////////

class RectStruct extends ByteArrayStruct {

	public final static int sizeOfRect = 8;
	
	public RectStruct() { super(sizeOfRect); }

	public final void setTop(short top) { setShortAt(0, top); }
	public final void setLeft(short left) { setShortAt(2, left); }
	public final void setBottom(short bottom) {	setShortAt(4, bottom); }
	public final void setRight(short right) { setShortAt(6, right); }

}



// From QuickDraw.java ////////////////////////////////////////////////////////////////////////


class ColorTableStruct extends HandleStruct {
	public final static int sizeOfColorTable = 16;
	public ColorTableStruct(int handle) { super(handle); }
	public final short getCtSize() { return getShortAt(6); }
	public int getSize() { return sizeOfColorTable; }
}


class PixMapStruct extends HandleStruct {
	public final static int sizeOfPixMap = 50;
	public PixMapStruct(int handle) { super(handle); }
	public final short getRowBytes() { return getShortAt(4); }
	public final short getPixelType() { return getShortAt(30); }
	public final int   getPmTable() { return getIntAt(42);}
	public final short getPackType() { return getShortAt(16); }
	public final short getCmpCount() { return getShortAt(34); }
	public final short getCmpSize() { return getShortAt(36); }
	public final short getPixelSize() { return getShortAt(32); }
	public int getSize() { return sizeOfPixMap; }
}


class GDeviceStruct extends HandleStruct {
	public GDeviceStruct(int handle) { super(handle); }
	public final static int sizeOfGDevice = 62;
	public int getSize() { return sizeOfGDevice; }
}


class PictureStruct extends HandleStruct {
	public final static int sizeOfPicture = 10;
	public PictureStruct(int handle) {super(handle); }
	public int getSize() { return sizeOfPicture; }
}


public class RGBColorStruct extends ByteArrayStruct {

	public RGBColorStruct() {
		super(sizeOfRGBColor);
	}

	public final short getRed() { return getShortAt(0); }
	public final short getGreen() { return getShortAt(2); }
	public final short getBlue() { return getShortAt(4); }

	public final static int sizeOfRGBColor = 6;
}