import ij.plugin.*;
import ij.*;
import ij.io.*;
import ij.process.*;
import java.io.*;

/** This plugin opens images in the IPLab/Windows (.IPL)  format. */
public class IPLab_Reader extends ImagePlus implements PlugIn {

    boolean littleEndian;
    RandomAccessStream f;
    boolean isColor48;
    int nextBlock;
    int itag;
 
    public void run(String arg) {
        OpenDialog od = new OpenDialog("Open IPLab...", arg);
        String directory = od.getDirectory();
        String name = od.getFileName();
        if (name==null)
            return;
        IJ.showStatus("Opening: " + directory + name);
        open(directory, name, arg);
    }

    void open(String directory, String name, String arg) {
        FileInfo fi = null;
        try {
            fi = getFileInfo(directory, name);
        } catch (IOException e) {
            IJ.showStatus("");
            String error = e.getMessage();
            if (error==null || error.equals(""))
                error = ""+e;
            IJ.showMessage("IPLab Reader", ""+error);
            return;
         }
         if (fi!=null) {
            FileOpener fo = new FileOpener(fi);
            ImagePlus imp = fo.open(false);
            if (imp==null)
                return;
            if (isColor48 && imp.getBitDepth()==16 && imp.getStackSize()==3) 
                handleColor48Image(imp);
            setStack(name,  imp.getStack());
            setCalibration(imp.getCalibration());
            if (arg.equals("")) show();
        }
    }

    void handleColor48Image(ImagePlus imp) {
            ImageStack stack  =  imp.getStack();
            stack.setSliceLabel( "Red", 1);
            stack.setSliceLabel( "Green", 2);
            stack.setSliceLabel( "Blue", 3);
            convertColor48ToRGB(imp);
    }

    void convertColor48ToRGB(ImagePlus imp) {
        int width = imp.getWidth();
        int height = imp.getHeight();
        ImageProcessor ip;
        ImageStack stack1 = imp.getStack();
        ImageStack stack2 = new ImageStack(width, height);
        for (int i=1; i<=stack1.getSize(); i++) {
            ip = stack1.getProcessor(i);
            ImageProcessor ip2 = ip.duplicate();
            stack2.addSlice(null, ip2);
        }
        ImagePlus imp2 = imp.createImagePlus();
        imp2.setStack("RGB of "+imp.getTitle(), stack2);
        ImageProcessor ip2 = imp2.getProcessor();
        StackConverter sc = new StackConverter(imp2);
        sc.convertToGray8();
        ImageConverter ic = new ImageConverter(imp2);
        ic.convertRGBStackToRGB();
        imp2.show();
    }

    FileInfo getFileInfo(String directory, String name) throws IOException {
        FileInfo fi = new FileInfo();
        f = new RandomAccessStream(new RandomAccessFile(directory + name, "r"));
        //f = new BufferedInputStream(new FileInputStream(directory+name));
        int b0=getByte(), b1=getByte(), b2=getByte(), b3=getByte();
        if (b0==105&&b1==105&&b2==105&&b3==105) // "iiii"
            littleEndian = true;
        else if (b0==109&&b1==109&&b2==109&&b3==109) // "mmmm"
            littleEndian = false;
        else
              throw new IOException("This does not appear to be an IPLab/Windows (.IPL) file.");
        f.skip(8);
        if (!(getByte()==100&&getByte()==97&&getByte()==116&&getByte()==97)) // "data"
            throw new IOException("This does not appear to be an IPLab/Windows (.IPL) file.");
        int blockSize = getInt();
        nextBlock = 16 + blockSize;
        fi.width = getInt();
        fi.height = getInt();
        int nChannels = getInt();
        int zPlanes = getInt();
        int tVolumes = getInt();
        int dataType = getInt();
        fi.fileFormat = fi.RAW;
        fi.fileName = name;
        fi.directory = directory;
        fi.nImages = zPlanes*tVolumes;
        fi.intelByteOrder = littleEndian;
        fi.offset = 44;
        switch (dataType) {
            case 0:
                if (nChannels==3)
                    fi.fileType = FileInfo.RGB_PLANAR;
                else
                    fi.fileType = FileInfo.GRAY8;
                break;
            case 1:
                fi.fileType = FileInfo.GRAY16_SIGNED; 
                break;
            case 2:
                fi.fileType = FileInfo.GRAY16_UNSIGNED;
                if (fi.nImages==1)
                    fi.nImages = nChannels;
                isColor48 = nChannels==3;
                break;
            case 3:
                fi.fileType = FileInfo.GRAY32_INT;
                break;
            case 4:
                 fi.fileType = FileInfo.GRAY32_FLOAT; 
                 break;
        }
        String tag = null;
        do {
            tag = getNextBlock();
            if (tag==null) break;
            if (tag.equals("unit"))
                getUnits(fi);
            else if (itag==0x2d45c4f6)
               decodeAcquireTag(fi);
        } while (tag!=null);
        f.close();
        return fi;
    }

    String getNextBlock() throws IOException {
        f.seek(nextBlock);
        String tag = getString();
        int blockSize = getInt();
        //IJ.log(blockSize+"  " + nextBlock+"  \""+tag+"\"" + "  " +Integer.toHexString(itag));
        nextBlock = nextBlock + 8 + blockSize;
        if (blockSize==0)
            return null;
        else
            return tag;
    }

    void getUnits(FileInfo fi) throws IOException {
        float[] unitsPerPixel = new float[5];
        int[] unit = new int[5];
        for (int i=0; i<4; i++) {
            getInt();
            unitsPerPixel[i] = getFloat(); 
            unit[i] = getShort();
            getShort(); // skip 2 filler bytes
            //IJ.log(i+"  "+unitsPerPixel[i]+"    "+unit[i]);
        }
        if (unitsPerPixel[0]!=0.0) {
            fi.pixelWidth = 1.0/unitsPerPixel[0];
            fi.pixelHeight = fi.pixelWidth;
        }
        switch (unit[0]) {
            case 1: fi.unit="um"; break;
            case 2: fi.unit="mm"; break;
            case 3: fi.unit="cm"; break;
            case 4: fi.unit="m"; break;
            case 5: fi.unit="inch"; break;
            case 6: fi.unit="ft"; break;
        }
    }
    
    void decodeAcquireTag(FileInfo fi) throws IOException {
        f.skip(1276);
        double zSpacing = getFloat();
        //IJ.log("decodeAcquireTag: "+zSpacing);
        if (zSpacing>0.0)
            fi.pixelDepth = zSpacing;
    }

    int getByte() throws IOException {
        int b = f.read();
        if (b ==-1) throw new IOException("unexpected EOF");
        return b;
    }

    int getShort() throws IOException {
        int b0 = getByte();
        int b1 = getByte();
        if (littleEndian)
            return ((b1<<8) + b0);
        else
            return ((b0<<8) + b1);
    }
    
    final int getInt() throws IOException {
        int b0 = getByte();
        int b1 = getByte();
        int b2 = getByte();
        int b3 = getByte();
        if (littleEndian)
            return ((b3<<24) + (b2<<16) + (b1<<8) + b0);
        else
            return ((b0<<24) + (b1<<16) + (b2<<8) + b3);
    }

    float getFloat()  throws IOException {
        return Float.intBitsToFloat(getInt());
    }

    String getString() throws IOException {
        //reads next  4 bytes and returns them as a string
        byte[]  list = new byte[4];
        f.read(list);
        if (littleEndian)
            itag = ((list[0]<<24) + (list[1]<<16) + (list[2]<<8) + list[3]);
        else
            itag = ((list[3]<<24) + (list[2]<<16) + (list[1]<<8) + list[0]);
        return new String(list);
    }

}

/*
#define kAcquireTagID		0xF6C5462D//TAG value use this to identify the tag
		
struct AcquireXTag {
	char			cTag[4];					//0xF6C5462D //tag id //check first four bytes for this long hex value
	long			lSize;						// 2016 bytes size of the tag without tag and size (2024 - 8)
	float			fVersion;					//4
	long			lTime;						//4 will cast to time_t structure for time extraction
	char			szExpName[32];			//32 Exp. Name
	char			cExpID[16];				//16 GUID per experiment
	char			cGeneralUnused[512];	//512 (568)
	////////////////////////////////////////////////////////////////////
	//////////////Camera Settings/    //////////////////////////////////
	////////////////////////////////////////////////////////////////////
	char			szCameraName[32];		//32 Camera Name
	long			lXResolution;				//4 CC X Max
	long			lYResolution;				//4 CCD Y Max
	long			lLeft;						//4 CCD Coordinates used
	long			lRight;						//4
	long			lTop;						//4
	long			lBottom;					//4
	long			lBinX;						//4 Binning in X direction
	long			lBinY;						//4 Binning in Y Direction
	float			fExposure;				//4 Exposure
	float			fCalibrationXY;			//4 XY image calibration for current binn
	long			lCalibrationXYUnits;		//4 Units for the XY calibration
	long			lGain;						//4 Camera Gain
	long			lEGain;					//4 Multiplicative/Intensifier Gain
	long			lCameraSpeed;			//4 Speed setting for camera
	char			szCameraSpeed[32];	//32 Speed as string
	long			lCameraOffset;			//4 Camera Offset
	long			lTriggerMode;				//4 Trigger mode
	char			szTriggerMode[32];		//32 Trigger mode as string
	long			lBitDepth;					//4
	char			cCameraUnused[508];	//508 (672)
	////////////////////////////////////////////////////////////////////
	//////////////Microscope Settings///////////////////////////////////
	////////////////////////////////////////////////////////////////////
	//Z Axis Settings
	char			szZAxis[32];				//32 Z Axis Name
	float			fZStepsPerMicron;		//4 Steps per micron for Z Axis 
	float			fZSpacing;				//4 Z Spacing  //1276 (568+672+36)
	float			fZCurPos;					//4 Z Current Position
	//XY Stage Settings
	char			szXYStage[32];			//32 XY Stage Name
	float			fXStepsPerMicron;		//4 X Steps per micron
	float			fYStepsPerMicron;		//4 Y Steps per micron
	float			fXPosition;				//4 X Current position
	float			fYPosition;				//4 Y Current position
	long			lCurRow;					//4 Current array row
	long			lCurCol;					//4 Current array col
	long			lCurSuperRow;			//4 Current super row
	long			lCurSuperCol;			//4 Current super col
	//Shutter Settings
	char			szShutterName[32];		//32 Shutter Name
	float			fShutterDelay;			//4 Shutter Delay
	//Filter Settings
	char			szFilterName[32];		//32 Filter Name
	long			lFilterPos;					//4 Filter Position
	long			lFilterWavelength;		//4 Filter Wavelength
	char			szFilterPosName[32];	//32 Filter Position Name
	//Objective Settings
	float			fNA;						//4 Numerical Aperature
	float			fRefactiveIndex;			//4 Refractive Index
	long			lObjectivePos;			//4 Objective Position
	char			szObjectiveName[32];	//32 Objective Name
	char			szObjectivePosName[32]; //32 Objective Position Name
	//Other Settingsing
	float			fMagFactor;				//4 Magnification factor other than objective

	char			cMicUnused[480];		//480
	////////////////////////////////////////////////////////////////////
};
*/


