package ciffreader;

import java.lang.*;
import java.io.*;
import java.util.Calendar;
import java.util.Date;
import java.sql.Time;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.text.DateFormat;
import java.util.TimeZone;


public class fileProcessor
{
short lshOrder = 0;
RandomAccessFile fh;
long llFlen;
int liHeaderlen;
byte[] lbCamtypeid = new byte[10];  /* "HEAPCCDR"  */
byte[] lbBuff = new byte[2048];
int[] liTagset = new int[32];

int     liFileOK                = -1;

int     liRecordID              = 0x8fffffff;
String  lsTStamp                = "";
int     liNPixHoriz             = 0x8fffffff;
int     liNPixVert              = 0x8fffffff;
String  lsCameraMake            = "";
String  lsCameraModel           = "";
String  lsImageFileName         = "";
int     liBodyID                = 0x8fffffff;
String  lsFirmwareVersion       = "";
float   lfFocalLength           = (float)(-1.0e32);
float   lfTvTarget              = (float)(-1.0e32);
float   lfAvTarget              = (float)(-1.0e32);
float   lfISO                   = (float)(-1.0e32);
float   lfExpBias               = (float)(-1.0e32);
int     liWB                    = 0x8fffffff;
String  lsWB                    = "";
float   lfFlashBias             = (float)(-1.0e32);
float   lfSubjDistMin           = (float)(-1.0e32);
float   lfSubjDist              = (float)(-1.0e32);
float   lfSubjDistMax           = (float)(-1.0e32);
float   lfAvActual              = (float)(-1.0e32);
float   lfTvActual              = (float)(-1.0e32);
int     liReleaseMode           = 0x8fffffff;
String  lsReleaseMode           = "";
int     liFocusMode             = 0x8fffffff;
String  lsFocusMode             = "";
int     liMeterMode             = 0x8fffffff;
String  lsMeterMode             = "";
int     liExposureMode          = 0x8fffffff;
String  lsExposureMode          = "";
int     liColorSpace            = 0x8fffffff;
String  lsColorSpace            = "";
int     liFlashFired            = 0x8fffffff;
int     liFlashMode             = 0x8fffffff;
String  lsFlashMode             = "";
float   lfZoomMin               = (float)(-1.0e32);
float   lfZoomMax               = (float)(-1.0e32);


String[]    lsTagIDset          = new String[256];
int[]       liTagDataFormat     = new int[256];
int[]       liNumElements       = new int[256];
String[]    lsTagValueset       = new String[256];
int     liNumTags               = 0;
int     liMaxNumTags            = 254;



/* public methods begin */

public fileProcessor(File srcfile)                // constructor
{
  liFileOK = jFileProcessor(srcfile);
}

public int processingStatus()
{
  return liFileOK;
}

public int rawImgID()
{
  return liRecordID;
}

public int nPixHoriz()
{
  return liNPixHoriz;
}

public int nPixVert()
{
  return liNPixVert;
}

public int cameraBodyID()
{
  return liBodyID;
}

public int wBValue()
{
  return liWB;
}

public int releaseModeValue()
{
  return liReleaseMode;
}

public int focusModeValue()
{
  return liFocusMode;
}

public int exposureMeterModeValue()
{
  return liMeterMode;
}

public int exposureModeValue()
{
  return liExposureMode;
}

public int colorSpaceValue()
{
  return liColorSpace;
}

public int flashFired()
{
  return liFlashFired;
}

public int flashModeValue()
{
  return liFlashMode;
}

public String timeStamp()
{
  return lsTStamp;
}

public String cameraMake()
{
  return lsCameraMake;
}

public String cameraModel()
{
  return lsCameraModel;
}

public String rawImageFileName()
{
  return lsImageFileName;
}

public String firmwareVersion()
{
  return lsFirmwareVersion;
}

public String whiteBalance()
{
  return lsWB;
}

public String releaseMode()
{
  return lsReleaseMode;
}

public String focusMode()
{
  return lsFocusMode;
}

public String exposureMeterMode()
{
  return lsMeterMode;
}

public String exposureMode()
{
  return lsExposureMode;
}

public String colorSpace()
{
  return lsColorSpace;
}

public String flashMode()
{
  return lsFlashMode;
}

public float focalLength()
{
  return lfFocalLength;
}

public float focalLengthMin()
{
  return lfZoomMin;
}

public float focalLengthMax()
{
  return lfZoomMax;
}

public float targetTv()
{
  return lfTvTarget;
}

public float targetAv()
{
  return lfAvTarget;
}

public float ISO()
{
  return lfISO;
}

public float exposureBias()
{
  return lfExpBias;
}

public float flashBias()
{
  return lfFlashBias;
}

public float subjectDistance()
{
  return lfSubjDist;
}

public float subjDistanceMin()
{
  return lfSubjDistMin;
}

public float subjDistanceMax()
{
  return lfSubjDistMax;
}

public float actualAv()
{
  return lfAvActual;
}

public float actualTv()
{
  return lfTvActual;
}

public int[] rawTagDataFormats()
{
  int[] liRset = new int[liNumTags];
  int liCtr1;

  for (liCtr1 = 0; liCtr1 < liNumTags; liCtr1++)
  {
     liRset[liCtr1] = liTagDataFormat[liCtr1];
  }
  return liRset;
}


public int[] rawTagNumComponents()
{
  int[] liRset = new int[liNumTags];
  int liCtr1;

  for (liCtr1 = 0; liCtr1 < liNumTags; liCtr1++)
  {
     liRset[liCtr1] = liNumElements[liCtr1];
  }
  return liRset;
}


public String[] rawTagIDs()
{
  String[] lsRset = new String[liNumTags];
  int liCtr1;

  for (liCtr1 = 0; liCtr1 < liNumTags; liCtr1++)
  {
     lsRset[liCtr1] = lsTagIDset[liCtr1];
  }
  return lsRset;
}

public String[] rawTagValues()
{
  String[] lsRset = new String[liNumTags];
  int liCtr1;

  for (liCtr1 = 0; liCtr1 < liNumTags; liCtr1++)
  {
     lsRset[liCtr1] = lsTagValueset[liCtr1];
  }
  return lsRset;
}


/* public methods end */




 private int jFileProcessor(File srcfile)
 {
    try
    {
      String lsFext;
      lsImageFileName = srcfile.getName().toLowerCase();
      int ix = lsImageFileName.lastIndexOf('.');
      if (ix > 0 &&  ix < lsImageFileName.length() - 1)
      {
        lsFext =  lsImageFileName.substring(ix+1).toLowerCase();
      }
      else
      {
        return -1;                                      // if filename has no extension then it is not a valid raw image file
      }
      if ((lsFext.compareTo("crw") != 0) && (lsFext.compareTo("tif") != 0))
      {
        return -1;                                      // if file is neither *.crw nor *.tif then it is not a valid raw image file
      }
      fh = new RandomAccessFile(srcfile, "r");
      llFlen = fh.length();
      fh.seek(0);
      lshOrder = jGet2(fh);                               /* check byte order */
      if ((lshOrder != 0x4d4d) && (lshOrder != 0x4949))   /* if not a digicam image file */
      {
        return -1;
      }

      fh.seek(6);
      fh.read(lbCamtypeid, 0, 8);
      String lsCamtypeid = jBytearrtoString (lbCamtypeid, 8);
      if (lsCamtypeid.compareTo("HEAPCCDR") == 0 )
      {
        fh.seek(2);
        liHeaderlen = jGet4(fh);
        lsImageFileName = "";
        jParseCIFF((long)liHeaderlen, (llFlen - (long)liHeaderlen), 1);
        fh.close();
        return 0;
      }
      else
      {
        fh.seek(4);
        liHeaderlen = jGet4(fh);
        lsImageFileName = srcfile.getName().toLowerCase();
        jParseEXIF((long)liHeaderlen,  1);
        fh.close();
        return 0;
      }
    }
    catch(Exception e)
    {
      e.printStackTrace();
      return -1;
    }
  }



/* PROCESS *.TIF FILES FROM 1D CAMERA */
  private void jParseEXIF(long llOffset, int liLevel) throws IOException, ArrayIndexOutOfBoundsException
{
  int   liNrecs;
  int   liType, liCtr1;
  long  llPos;
  long  llLen = 0, llRoff = 0;
  long  llPosString = 0;
  int   liDataFormat;
  int   liNumBytes;
  int   liNumComponents;
  long  llData;
  int   liAddress = 0;
  int[] liItemLen = new int[] { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
  int   liAoff = 0;
  int   liNr, liDenom;
  long  llNr, llDenom;
  double ldWc1 = 0.0;

  int   li1 = 0, li2 = 0, li3 = 0, li4 = 0;
  TimeZone loTZ = TimeZone.getTimeZone("GMT");
  Locale bLocale = new Locale("en", "UK");

  SimpleDateFormat loDateFormatter = new SimpleDateFormat("yyyyMMdd'_'kkmmss", bLocale);
  loDateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));

  fh.seek(llOffset);
  liNrecs = (int)jGet2(fh);
  for(liCtr1 = 0; liCtr1<liNrecs; liCtr1++)
  {
    liType = (jGet2(fh) & 0x0000ffff);
    liDataFormat = (jGet2(fh) & 0x0000ffff);
    liNumComponents = (jGet4(fh) & 0x0fffffff);
    liNumBytes = (liItemLen[liDataFormat])*liNumComponents;
    if(liNumBytes > 4)    liAddress = 1;
    else                  liAddress = 0;

    if (liNumTags < liMaxNumTags)
    {
      if ((liType == 0x1)||(liType == 0x3)||(liType == 0x4)||(liType == 0xd)||(liType == 0x94)||(liType == 0xa0))  // defines the stored tagset
      {
        long llPos7 = fh.getFilePointer();                                      // store file pointer
        StringBuffer lsOutstrbuff = new StringBuffer();
        int liCtr7;
         int liWVal = 0;
        if(liAddress != 0)
        {
          liAoff = jGet4(fh);
          fh.seek(liAoff);
        }
        lsTagIDset[liNumTags] = Integer.toHexString(liType);
        liTagDataFormat[liNumTags] = liDataFormat;
        liNumElements[liNumTags] = liNumComponents;
        for (liCtr7 = 0; liCtr7 < liNumComponents; liCtr7++)
        {
          switch (liDataFormat)
          {
            case (3):
              liWVal = (jGet2(fh) & 0x0000ffff);
              break;

            case (4):
              liWVal = (jGet4(fh) & 0xffffffff);
              break;

            default:
              liWVal = (fh.readUnsignedByte() & 0xff);
              break;
          }
          lsOutstrbuff.append(Integer.toHexString(liWVal));
          lsOutstrbuff.append(" ");
        }
        String lsWstr1 = lsOutstrbuff.toString();
        int lislen = (lsWstr1.length() < 255)? lsWstr1.length() : 255;
        lsTagValueset[liNumTags] = lsWstr1.substring(0,lislen);
        liNumTags += 1;
        fh.seek(llPos7);                                                        // recover file pointer
      }
    }

    switch (liType)
    {
      case (0x132):                                             /* time stamp */
            llPos = fh.getFilePointer();
            if(liNumBytes <5)  fh.read(lbBuff, 0, liNumBytes+2);
            else
            {
              liAoff = jGet4(fh);
              fh.seek(liAoff);
              fh.read(lbBuff, 0, liNumBytes+2);   //
            }
            fh.seek(llPos);
            fh.skipBytes(4);
            String  lsWstr1 = jBytearrtoString(lbBuff, liNumBytes+1);
            StringBuffer lsbBuff = new StringBuffer(lsWstr1.substring(0,4));
            lsbBuff.append(lsWstr1.substring(5,7));
            lsbBuff.append(lsWstr1.substring(8,10));
            lsbBuff.append("_");
            lsbBuff.append(lsWstr1.substring(11,13));
            lsbBuff.append(lsWstr1.substring(14,16));
            lsbBuff.append(lsWstr1.substring(17,19));
            lsTStamp = lsbBuff.toString();
            break;

      case (0x10f):                                       /* manufacturer */
            llPos = fh.getFilePointer();
            if(liNumBytes <5)  fh.read(lbBuff, 0, liNumBytes+2);
            else
            {
              liAoff = jGet4(fh);
              fh.seek(liAoff);
              fh.read(lbBuff, 0, liNumBytes+2);   //
            }
            fh.seek(llPos);
            fh.skipBytes(4);
            lsCameraMake = jBytearrtoString(lbBuff, liNumBytes+1);
            break;

      case (0x110):                                       /* model */
            llPos = fh.getFilePointer();
            if(liNumBytes <5)  fh.read(lbBuff, 0, liNumBytes+2);
            else
            {
              liAoff = jGet4(fh);
              fh.seek(liAoff);
              fh.read(lbBuff, 0, liNumBytes+2);   //
            }
            fh.seek(llPos);
            fh.skipBytes(4);
            lsCameraModel = jBytearrtoString(lbBuff, liNumBytes+1);
            break;

     case (0x829a):                                     /* rounded-off Tv */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            liNr = (jGet4(fh) & 0xffffffff);
            liDenom = (jGet4(fh) & 0xffffffff);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

    case (0x829d):                                      /* rounded-off Av */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            liNr = (jGet4(fh) & 0xffffffff);
            liDenom = (jGet4(fh) & 0xffffffff);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

     case (0x8822):                                     /* exposure mode */
            llPos = fh.getFilePointer();
            liExposureMode = (jGet2(fh) & 0x0000ffff);
            switch (liExposureMode & 0x0000ffff)
            {
              case (1):
                lsExposureMode = "manual";
                break;
              case (2):
                lsExposureMode = "P";
                break;
              case (3):
                lsExposureMode = "Av";
                break;
              case (4):
                lsExposureMode = "Tv";
                break;
              case (5):
                lsExposureMode = "DEP";
                break;
              default:
                lsExposureMode = "?";
                break;
            }
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

     case (0x8827):                                     /* ISO speed */
            llPos = fh.getFilePointer();
            lfISO = (float)jGet2(fh);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

    case (0x9202):                                     /* actual  Av */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            liNr = (jGet4(fh) & 0xffffffff);
            liDenom = (jGet4(fh) & 0xffffffff);
            fh.seek(llPos);
            fh.skipBytes(4);
            if (liDenom == 0)
            {
              lfAvActual = -(float)1.0;
              break;
            }
            ldWc1 = (double)liNr / (double)liDenom;
            if (ldWc1 > 32)
            {
              lfAvActual = -(float)1.0;
              break;
            }
            if (ldWc1 < -32)
            {
              lfAvActual = (float)0.0;
              break;
            }
            lfAvActual = (float)Math.pow(Math.sqrt(2), ldWc1);
            break;

    case (0x9201):                                     /* actual  Tv */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            liNr = (jGet4(fh) & 0xffffffff);
            liDenom = (jGet4(fh) & 0xffffffff);
            fh.seek(llPos);
            fh.skipBytes(4);
            if (liDenom == 0)
            {
              lfTvActual = -(float)1.0;
              break;
            }
            ldWc1 = (double)liNr / (double)liDenom;
            if (ldWc1 > 32)
            {
              lfTvActual = (float)0.0;
              break;
            }
            if (ldWc1 < -32)
            {
              lfTvActual = -(float)1.0;
              break;
            }
            lfTvActual  = (float)Math.pow(0.5, ldWc1);
            break;

    case (0x9204):                                     /* exposure bias */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            liNr = jGet4(fh);
            liDenom = jGet4(fh);
            fh.seek(llPos);
            fh.skipBytes(4);
            if (liDenom == 0)
            {
              lfExpBias = -(float)1.e10;
              break;
            }
            lfExpBias = (float)((double)liNr / (double)liDenom);
            break;

    case (0x9207):                                     /* exposure metering mode */
            llPos = fh.getFilePointer();
            liNr = jGet2(fh);
            fh.seek(llPos);
            fh.skipBytes(4);
            liMeterMode = liNr & 0x0fff;
            switch(liMeterMode)
            {
              case (1):
                lsMeterMode = "average";
                break;
              case (2):
                lsMeterMode = "center-weighted";
                break;
              case (3):
                lsMeterMode = "spot";
                break;
              case (4):
                lsMeterMode = "multi-spot";
                break;
              case (5):
                lsMeterMode = "evaluative";
                break;
             case (6):
                lsMeterMode = "partial";
                break;
              default:
                lsMeterMode = "?";
                break;
            }
            break;

    case (0xa002):                                     /* horizontal resolution */
            llPos = fh.getFilePointer();
            liNPixHoriz = jGet2(fh);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

    case (0xa003):                                     /* vertical resolution */
            llPos = fh.getFilePointer();
            liNPixVert = jGet2(fh);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

    case (0x9209):                                     /* flash fire status */
            llPos = fh.getFilePointer();
            liNr = jGet2(fh);
            liFlashFired = liNr & 0x1;
            if (liFlashFired == 0)  liFlashFired = 0x8fffffff;
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

    case (0x920a):                                      /* focal length */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff);
            llNr = (0xffffffff & jGet4(fh));
            llDenom = (0xffffffff & jGet4(fh));
            fh.seek(llPos);
            fh.skipBytes(4);
            if (llDenom == 0)
            {
              lfFocalLength = -(float)1.0;
              break;
            }
            lfFocalLength = (float)((double)llNr / (double)llDenom);
            break;

   case (0x1):
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff + 8);
            liNr = jGet2(fh);
            liNr = jGet2(fh);     // drive mode
            liReleaseMode = liNr;
            switch (liNr & 0xffff)
            {
              case (0):
                lsReleaseMode = "single shot";
                break;
              case (4):
                lsReleaseMode = "continuous L";
                break;
              case (5):
                lsReleaseMode = "continuous H";
                break;
              default:
                lsReleaseMode = "?";
                break;
            }
            liNr = jGet2(fh);
            liNr = jGet2(fh);     // focus mode
            liFocusMode = (liNr & 0xffff);
            switch (liNr & 0xffff)
            {
              case (0):
                lsFocusMode = "one shot AF";
                break;
              case (1):
                lsFocusMode = "AI servo";
              case (2):
                lsFocusMode = "AI servo";
                break;
              case (3):
                lsFocusMode = "manual focus";
              case (6):
                lsFocusMode = "manual focus";
                break;
              default:
                lsFocusMode = "?";
                break;
            }
            fh.seek(liAoff + 46);
            lfZoomMax = (float) (jGet2(fh) & 0xffff);
            lfZoomMin = (float) (jGet2(fh) & 0xffff);
            fh.seek(liAoff + 58);
            liFlashMode =  (jGet2(fh) & 0xffff);
            if (liFlashMode == 16385) lsFlashMode = "M";
            else                      lsFlashMode = "ETTL";
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

  case (0x93):
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff + 4);
            liRecordID = (jGet2(fh) & 0xffff);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

   case (0x4):
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff + 8);
            liNr = (jGet2(fh) & 0xffff);
            if (liNr < 640)   lfAvTarget = (float)Math.pow(Math.sqrt(2), ((double)liNr) / 32.0);  // target f-stop
            else              lfAvTarget = -(float)1.0;
            if (liNr < -800)
            {
              lfAvTarget = (float)0.0;
            }
            liNr = (jGet2(fh) & 0xffff);
            if (liNr > -640)  lfTvTarget  = (float)Math.pow(0.5, ((double)liNr) / 32.0);          // target sxposure time
            else              lfTvTarget = -(float)1.0;
            if (liNr > 800)
            {
              lfTvTarget = (float)0.0;
            }
            fh.seek(liAoff + 30);
            liNr = (jGet2(fh) & 0xffff);
            if (liNr > 32768)    liNr -= 65536;
            lfFlashBias = (float)(((double)liNr)/32.0);
            fh.seek(liAoff + 14); // Wb
            liWB = (jGet2(fh) & 0xffff);
            switch (liWB)
            {
              case (0):
                lsWB = "auto";
                break;
              case (1):
                lsWB = "daylight";
                break;
              case (2):
                lsWB = "cloudy";
                break;
              case (3):
                lsWB = "tungsten";
                break;
              case (4):
                lsWB = "fluorescent";
                break;
              case (5):
                lsWB = "flash";
                break;
              case (6):
                lsWB = "custom";
                break;
              case (8):
                lsWB = "shade";
                break;
              case (9):
                lsWB = "color_temp";
                break;
              default:
                lsWB = "?";
                break;
            }
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

   case (0x7):                                  /* firmware version */
            llPos = fh.getFilePointer();
            if(liNumBytes <5)  fh.read(lbBuff, 0, liNumBytes+2);
            else
            {
              liAoff = jGet4(fh);
              fh.seek(liAoff);
              fh.read(lbBuff, 0, liNumBytes+2);
            }
            lsFirmwareVersion = jBytearrtoString(lbBuff, liNumBytes+1);
            fh.seek(llPos);
            fh.skipBytes(4);
            break;

  case (0xc):                                       /* body ID */
            liBodyID = jGet4(fh);
            break;

  case (0xd):                                       /* colorspace */
            llPos = fh.getFilePointer();
            liAoff = jGet4(fh);
            fh.seek(liAoff + 74);
            liColorSpace = (jGet2(fh) & 0xff);
            lsColorSpace = Integer.toString(liColorSpace);
            if (liColorSpace == 4)  lsColorSpace = "AdobeRGB";
            fh.seek(llPos + 4);
            break;

      default:
            if (((liType & 0x0000ffff) == 0x8769)||((liType & 0x0000ffff) == 0x927c))
            {
              llPos = fh.getFilePointer();
              llData = (int)jGet4(fh);
              jParseEXIF(llData, (liLevel + 1));
              fh.seek(llPos);
            }
            fh.skipBytes(4);
            break;
    }
  }
  if (liFlashFired == 0)
  {
    liFlashMode             = 0x8fffffff;
    lsFlashMode             = "";
  }
}




/* PROCESS *.CRW FILES FROM D30 AND D60 CAMERAS */
private void jParseCIFF(long llOffset, long llLength, int liLevel) throws IOException
{
  long  llTboff;
  int   liNrecs;
  int   liCtr1, liType, liCtr2, liTempLen;
  long  llPos;
  long  llLen = 0, llAoff = 0, llRoff = 0;
  long  llPosString = 0;
  int   li1 = 0, li2 = 0, li3 = 0, li4 = 0;
  int   liCaptureTime           = 0x8fffffff;

  TimeZone loTZ = TimeZone.getTimeZone("GMT");
  Locale bLocale = new Locale("en", "UK");

  SimpleDateFormat loDateFormatter = new SimpleDateFormat("yyyyMMdd'_'kkmmss", bLocale);
  loDateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));

  fh.seek(llOffset + llLength - 4);
  llTboff = (long)(jGet4(fh) & 0xffffffff) + llOffset;
  fh.seek(llTboff);
  liNrecs =(jGet2(fh) & 0xffff);
  for (liCtr1 = 0; liCtr1<liNrecs; liCtr1++)
  {
    liType = (jGet2(fh) & 0xffff);

    liTagset[liLevel-1] = liType;
    if (liType < 0x4000)
    {
      llLen = (jGet4(fh) & 0xffffffff);
      llRoff = (jGet4(fh) & 0xffffffff);
      llAoff = llOffset + llRoff;
    }
    else
    {
      if ((liType >> 8) == 0x48)  /* in-table string */
      {
        llPosString = fh.getFilePointer();
        fh.skipBytes(8);
      }
      if ((liType >> 8) == 0x50)  /* in-table word */
      {
        li1 = jGet2(fh);
        li2 = jGet2(fh);
        li3 = jGet2(fh);
        li4 = jGet2(fh);
      }
      if ((liType >> 8) == 0x58)  /* in-table dword */
      {
        li1 = jGet4(fh);
        li2 = jGet4(fh);
      }
      if(((liType >> 8) == 0x48)&&((liType >> 8) == 0x50)&&((liType >> 8) == 0x58))
      {
        fh.skipBytes(8);
      }
    }
    llPos = fh.getFilePointer();

    /*   begin tags processing  */

    switch ((liType & 0xffff))
    {
      case (0x5817):                           /* record ID */
            liRecordID = li1;
            break;

      case (0x180e):                            /* capture time */
            fh.seek(llAoff);
            liCaptureTime = jGet4(fh);
            Date  loDStamp = new Date(1000*((long)liCaptureTime));
            lsTStamp = loDateFormatter.format(loDStamp);
            break;

      case (0x1810):                           /* resolution */
            fh.seek(llAoff);
            liNPixHoriz = jGet4(fh);
            liNPixVert = jGet4(fh);
            break;

      case (0x080a):                            /* body make & model */
            fh.seek(llAoff);
            fh.read(lbBuff, 0, 64);
            for(liCtr2 = 0; liCtr2 < 61; liCtr2++)
            {
              if(lbBuff[liCtr2] == 0) break;
            }
            lsCameraMake = jBytearrtoString(lbBuff, 60);
            lsCameraModel = jByteSubarrtoString(lbBuff, (60 - liCtr2), (liCtr2 + 1));
            break;

      case (0x0816):                            /* original image file name */
            fh.seek(llAoff);
            fh.read(lbBuff, 0, 20);
            lsImageFileName = jBytearrtoString(lbBuff, 18).toLowerCase();
            break;

      case (0x580b):                           /* camera body ID */
            liBodyID = li1;
            break;

      case (0x080b):                            /* firmware version */
            fh.seek(llAoff);
            fh.read(lbBuff, 0, 32);
            lsFirmwareVersion = jBytearrtoString(lbBuff, 30);
            break;

      case (0x5029):                           /* focal length */
            lfFocalLength = (float)(li2 & 0xffff);
            break;

      case (0x102a):                            /* exposure info */
            fh.seek(llAoff + 0x04);
            li1 = jGet2(fh) & 0xffff;          /* raw ISO */
            if (li1 < 800)   lfISO = (float)(50.0 * (Math.pow(2.0, ((double)(li1))/32.0)/16.0));
            else            lfISO = -(float)1.0;
            if (li1 < -800)  lfISO = (float)0.0;
            fh.seek(llAoff + 0x08);
            li1 = jGet2(fh);          /* raw fstop target */
            if (li1 < 1280)   lfAvTarget = (float)Math.pow(2.0, ((double)(li1)/64.0));
            else              lfAvTarget = -(float)1.0;
            if (li1 < -1280)  lfAvTarget = (float)0.0;
            li1 = jGet2(fh);          /* raw Tv target */
            if (li1 > -800)   lfTvTarget = (float)(1.0 / Math.pow(2.0, ((double)(li1)/32.0)));
            else              lfTvTarget = -(float)1.0;
            if (li1 > 800)    lfTvTarget = (float)0.0;
            li1 = jGet2(fh);          /* raw exposure bias */
            lfExpBias = (float)(((double)(li1))/32.0);
            li1 = jGet2(fh);          /* raw white balance */
            liWB = (li1 & 0xffff);
            switch ((li1 & 0xffff))
            {
              case (0):
                lsWB = "auto";
                break;
              case (1):
                lsWB = "daylight";
                break;
              case (2):
                lsWB = "cloudy";
                break;
              case (3):
                lsWB = "tungsten";
                break;
              case (4):
                lsWB = "fluorescent";
                break;
              case (5):
                lsWB = "flash";
                break;
              case (6):
                lsWB = "custom";
                break;
              default:
                lsWB = "?";
                break;
            }
            fh.seek(llAoff + 0x1e);
            li1 = jGet2(fh);          /* raw flash bias */
            lfFlashBias = (float)((double)(li1)/32.0);
            fh.seek(llAoff + 0x26);
            li1 = jGet2(fh);
            lfSubjDistMin = (float)((double)(li1 & 0xffff)/ 100.0);
            li1 = jGet2(fh);
            lfSubjDistMax = (float)((double)(li1 & 0xffff)/ 100.0);
            fh.seek(llAoff + 0x2a);
            li1 = jGet2(fh);          /* raw fstop actual */
            if (li1 < 1280)   lfAvActual = (float)Math.pow(2.0,((double)(li1)/64.0));
            else              lfAvActual = -(float)1.0;
            if (li1 < -1280)  lfAvActual = (float)0.0;
            li1 = jGet2(fh);          /* raw Tv actual */
            if (li1 > -800)   lfTvActual = (float)(1.0 / Math.pow(2.0,((double)(li1)/32.0)));
            else              lfTvActual = -(float)1.0;
            if (li1 > 800)    lfTvActual = (float)0.0;
            fh.seek(llAoff + 0x1a);
            liFlashMode = (jGet2(fh) & 0xffff);
            if (liFlashMode == 0) lsFlashMode = "M";
            else                  lsFlashMode = "ETTL";
            break;

      case (0x102d):                            /* shot info */
            fh.seek(llAoff + 0x0a);
            li1 = jGet2(fh);
            liReleaseMode = (li1 & 0xffff);
            if  ((li1 & 0xffff) == 0)  lsReleaseMode = "single shot";
            else            lsReleaseMode = "continuous";
            fh.seek(llAoff + 0x0e);
            li1 = jGet2(fh);
            liFocusMode = (li1 & 0xffff);
            switch ((li1 & 0xffff))
            {
              case (0):
                lsFocusMode = "one shot AF";
                break;
              case (1):
                lsFocusMode = "AI servo";
                break;
              case (3):
                lsFocusMode = "manual focus";
                break;
              default:
                lsFocusMode = "?";
                break;
            }
            fh.seek(llAoff + 0x22);
            li1 = jGet2(fh);
            liMeterMode = (li1 & 0xffff);
            switch ((li1 & 0xffff))
            {
              case (3):
                lsMeterMode = "eval.";
                break;
              case (4):
                lsMeterMode = "partial";
                break;
              case (5):
                lsMeterMode = "center-weighted";
                break;
              default:
                lsMeterMode = "?";
                break;
            }
            fh.seek(llAoff + 0x28);
            li1 = jGet2(fh);
            liExposureMode = (li1 & 0xffff);
            switch ((li1 & 0xffff))
            {
              case (0):
                lsExposureMode = "auto";
                break;
              case (1):
                lsExposureMode = "P";
                break;
              case (2):
                lsExposureMode = "Tv";
                break;
              case (3):
                lsExposureMode = "Av";
                break;
              case (4):
                lsExposureMode = "manual";
                break;
              case (5):
                lsExposureMode = "A-DEP";
                break;
              default:
                lsExposureMode = "?";
                break;
            }
            fh.seek(llAoff + 0x2e);
            li1 = jGet2(fh);
            lfZoomMax = (float)(li1 & 0xffff);
            li1 = jGet2(fh);
            lfZoomMin = (float)(li1 & 0xffff);
            fh.seek(llAoff + 0x8);
            li1 = (jGet2(fh) & 0x6);
            if (li1 == 0)     liFlashFired = 0;
            else              liFlashFired = 1;
            break;

      default:
            break;
    }

    /*   end tags processing  */

    if ((((liType >> 8) == 0x28) || ((liType >> 8) == 0x30)) && (liLevel < 6))
      jParseCIFF(llAoff, llLen, (liLevel + 1));
    fh.seek(llPos);
  }
  if (liFlashFired ==0)
  {
    liFlashMode             = 0x8fffffff;
    liFlashFired            = 0x8fffffff;
    lsFlashMode             = "";
  }
}



private short jGet2 (RandomAccessFile  fptr) throws IOException
{
  int liWc1;
  int liWc2;
  short lshResult;
  liWc1 = fptr.readUnsignedByte();
  liWc2 = fptr.readUnsignedByte();

  if (lshOrder == 0x4d4d)
  {
    return ((short)((liWc1 << 8) + liWc2));
  }
  else
  {
    lshResult = (short)(liWc1 + (liWc2 << 8));
    return lshResult;
  }
}


private int jGet4 (RandomAccessFile  fptr) throws IOException
{
  int liWc1;
  int liWc2;
  int liWc3;
  int liWc4;
  int liResult;
  liWc1 = fptr.readUnsignedByte();
  liWc2 = fptr.readUnsignedByte();
  liWc3 = fptr.readUnsignedByte();
  liWc4 = fptr.readUnsignedByte();
  if (lshOrder == 0x4d4d)
  {
    return ((int)((liWc1 << 24) + (liWc2 << 16) + (liWc3 << 8) + liWc4));
  }
  else
  {
    liResult = (int)(liWc1 + (liWc2 << 8) + (liWc3 << 16) + (liWc4 << 24));
    return liResult;
  }
}



private String jBytearrtoString (byte[] bInArr, int liLen)
{
  StringBuffer lsOutstrbuff = new StringBuffer();
  int liCtr1;
  for (liCtr1 = 0; liCtr1<liLen; liCtr1++)
  {
    if(bInArr[liCtr1] == 0) break;
    lsOutstrbuff.append((char)bInArr[liCtr1]);
  }
  String lsOutstr = lsOutstrbuff.toString();
  return lsOutstr;
}

private String jByteSubarrtoString (byte[] bInArr, int liLen, int liStart)
{
  StringBuffer lsOutstrbuff = new StringBuffer();
  int liCtr1;
  for (liCtr1 = 0; liCtr1<liLen; liCtr1++)
  {
    if(bInArr[liCtr1 + liStart] == 0) break;
    lsOutstrbuff.append((char)bInArr[liCtr1 + liStart]);
  }
  String lsOutstr = lsOutstrbuff.toString();
  return lsOutstr;
}
/* end of class */
}