/**
 * JHOVE2 - Next-generation architecture for format-aware characterization
 * <p>
 * Copyright (c) 2010 by The Regents of the University of California. All rights reserved.
 * </p>
 * <p>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * </p>
 * <ul>
 * <li>Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.</li>
 * <li>Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.</li>
 * <li>Neither the name of the University of California/California Digital
 * Library, Ithaka Harbors/Portico, or Stanford University, nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.</li>
 * </ul>
 * <p>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * </p>
 */

package org.jhove2.module.format.tiff;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.jhove2.annotation.ReportableProperty;
import org.jhove2.annotation.ReportableProperty.PropertyType;
import org.jhove2.core.I8R;
import org.jhove2.core.JHOVE2;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.Message;
import org.jhove2.core.Message.Context;
import org.jhove2.core.Message.Severity;
import org.jhove2.core.format.Format;
import org.jhove2.core.format.FormatIdentification;
import org.jhove2.core.format.FormatIdentification.Confidence;
import org.jhove2.core.io.Input;
import org.jhove2.core.reportable.AbstractReportable;
import org.jhove2.core.source.ByteStreamSource;
import org.jhove2.core.source.MeasurableSource;
import org.jhove2.core.source.Source;
import org.jhove2.module.format.Validator.Validity;
import org.jhove2.module.format.tiff.type.Ascii;
import org.jhove2.module.format.tiff.type.AsciiArray;
import org.jhove2.module.format.tiff.type.Byte;
import org.jhove2.module.format.tiff.type.ByteArray;
import org.jhove2.module.format.tiff.type.Double;
import org.jhove2.module.format.tiff.type.DoubleArray;
import org.jhove2.module.format.tiff.type.FloatObject;
import org.jhove2.module.format.tiff.type.Long;
import org.jhove2.module.format.tiff.type.LongArray;
import org.jhove2.module.format.tiff.type.Rational;
import org.jhove2.module.format.tiff.type.RationalArray;
import org.jhove2.module.format.tiff.type.SByte;
import org.jhove2.module.format.tiff.type.SByteArray;
import org.jhove2.module.format.tiff.type.SLong;
import org.jhove2.module.format.tiff.type.SLongArray;
import org.jhove2.module.format.tiff.type.SShort;
import org.jhove2.module.format.tiff.type.SShortArray;
import org.jhove2.module.format.tiff.type.Short;
import org.jhove2.module.format.tiff.type.ShortArray;
import org.jhove2.module.format.tiff.type.desc.Compression;

import com.sleepycat.persist.model.Persistent;

/** TIFF IFD entry.
 * 
 * @author mstrong
 *
 */
@Persistent
public class IFDEntry 
    extends AbstractReportable
    implements Comparable<Object>
{
    /** The number of values, Count of the indicated Type */
    protected long count;

    /** compression in descriptive form */
    protected String compression_d;

    /** compression raw */
    protected int compression;

    /**  Name of the TIFF tag */
    protected String name;

    /** the tag that identifies the field */
    protected int tag;

    /** Contains the offset to the value field */
    protected long offsetOfValue;

    /** the previous tag read */
    public static int prevTag;

    /** the field type */
    protected int type;

    /** the enumerated field type */
    protected TiffType tiffType;

    /** contains either offset of the valueOffset field or the value stored in the Valueoffset field
     *  used to read the value into the proper value type object */
    protected long savedValueOffset;

    /** offset to the IFDEntry being parsed */
    protected long tagOffset;
    
    /** Contains the value iff the value is 4 or less bytes.  
     * Otherwise it is the offset of value */
    protected long valueOffset;

    /** TIFF Version - some field types define the TIFF version */
    protected int version = 4;

    /** Tag Sort order error message */
    private Message TagSortOrderErrorMessage;

    /** Byte offset not word aligned message */
    private Message ByteOffsetNotWordAlignedMessage;

    /** Value Offset reference location invalid message */
    private Message ValueOffsetReferenceLocationFileMessage;

    /** Unknown Type Message */
    private List<Message> unknownTypeMessages;

    /** type mismatch message */
    private Message TypeMismatchMessage;

    /** unknown tag message */
    private Message UnknownTagMessage;

    /** invalid count value message */
    private Message InvalidCountValueMessage;

    private Validity isValid = Validity.Undetermined;

    /** Possible TIFF values
     * Each TIFF value can be of a different type so it needs to be stored in a
     * separate object
     */
    /** the value if the value is more than 4 btyes */
    boolean isArray = false;

    protected Ascii asciiValue;

    protected AsciiArray asciiArrayValue;

    protected Byte byteValue;

    protected SByte sByteValue;

    protected ByteArray byteArrayValue;

    protected SByteArray sbyteArrayValue;

    /** 32-bit (4-byte) unsigned integer 
     * @see org.jhove2.module.format.tiff.type.Long */
    protected Long longValue;

    protected LongArray longArrayValue;

    protected SLong sLongValue;

    protected SLongArray sLongArrayValue;

    protected FloatObject floatValue;

    protected Double doubleValue;

    private DoubleArray doubleArrayValue;

    protected Rational rationalValue;

    protected RationalArray rationalArrayValue;

    protected Rational sRationalValue;

    protected RationalArray sRationalArrayValue;

    protected Short shortValue;

    protected ShortArray shortArrayValue;

    protected SShort sShortValue;

    protected SShortArray sShortArrayValue;

    /* Tiff Tag definition */
    protected TiffTag tagDefinition;

    /** invalid date/time value message */
    private Message invalidDateTimeMessage;

    /** invalid date time format message */
    private Message invalidDateTimeFormatMessage;

    /** invalid tilewidth not multiple of 16 message */
    private Message tileWidthNotMultipleOf16Message;

    /** invalid tile length not multiple of 16 message */
    private Message tileLengthNotMultipleOf16Message;

    @ReportableProperty(order=6, value = "Entry value/offset.")
    public long getValueOffset() {
        return this.valueOffset;
    }

    public int getVersion() {
        return this.version;
    }


    /** no arg constructor */
    public IFDEntry() {
        super();
        this.unknownTypeMessages = new ArrayList<Message>();
    }

    public IFDEntry(int tag, int type, long count, long valueOffset) {
        this.tag = tag;
        this.tiffType = TiffType.getType(type);
        this.count = count;
        this.valueOffset = valueOffset;
    }


    /**
     * parse the IFD Entry 
     * @param tiff2FormatMapper Factory to map tiff id to Format
     * @throws IOException, JHOVE2Exception 
     */
    public void parse(JHOVE2 jhove2, Source source, Input input, 
    		Tiff2FormatMapFactory tiff2FormatMapper)  
        throws IOException, JHOVE2Exception
    {
        this.isValid = Validity.True;
        this.tagOffset = input.getPosition();
        this.tag = input.readUnsignedShort();
        if (this.tag > prevTag)
            prevTag = this.tag;
        else {
            this.isValid = Validity.False;
            Object[]messageArgs = new Object[]{tag, this.tagOffset};
            this.TagSortOrderErrorMessage = (new Message(Severity.ERROR,
                    Context.OBJECT,
                    "org.jhove2.module.format.tiff.IFD.TagSortOrderErrorMessage",
                    messageArgs, jhove2.getConfigInfo()));
        }               

        this.type = input.readUnsignedShort();
        this.tiffType = TiffType.getType(this.type);

        /* Skip over tags with unknown type; those outside of defined range. */
        if (this.type < TiffType.BYTE.num()|| this.type > TiffType.IFD.num()) {
            Object[]messageArgs = new Object[] {this.type, this.tagOffset };
            this.unknownTypeMessages.add(new Message(Severity.WARNING,
                    Context.OBJECT,
                    "org.jhove2.module.format.tiff.IFD.UnknownTypeMessage",
                    messageArgs, jhove2.getConfigInfo()));               
        }
        else {
            /* set the version */
            if (this.type <= TiffType.SBYTE.num() && this.type <= TiffType.IFD.num()) {
                this.version = 6;
            }

            this.count = (int) input.readUnsignedInt();
            this.savedValueOffset = input.getPosition();        // save the offset of the ValueOffset field 
            this.valueOffset = input.readUnsignedInt();    // read in the value stored in the ValueOffset field
            long value = this.valueOffset;
            
            if (calcValueSize(this.type, this.count) > 4) {
                /* the value read is the offset to the value */
                long size = ((MeasurableSource) source).getSize();

                /* test that the value offset is within the file */
                if (value > size) {
                    this.isValid = Validity.False;
                    Object[]messageArgs = new Object[]{tag, value, size};
                    this.ValueOffsetReferenceLocationFileMessage = (new Message(Severity.ERROR,
                            Context.OBJECT,
                            "org.jhove2.module.format.tiff.IFD.ValueOffsetReferenceLocationFileMessage",
                            messageArgs, jhove2.getConfigInfo()));  
                    return;
                }

                /* test offset is word aligned */
                if ((value & 1) != 0){
                    this.isValid = Validity.False;
                    Object[]messageArgs = new Object[] {value};
                    this.ByteOffsetNotWordAlignedMessage = (new Message(Severity.ERROR,
                            Context.OBJECT,
                            "org.jhove2.module.format.tiff.IFD.ValueByteOffsetNotWordAlignedMessage",
                            messageArgs, jhove2.getConfigInfo()));
                    return;
                }
            }


            if (isValidTag(jhove2)) {
                /* Handle tags which require unique processing of their values */

                /* Parse the ICCProfile or XMP tag */
                 if (this.tag == TiffIFD.ICCPROFILE ||
                    this.tag == TiffIFD.XMP) {
                    ByteStreamSource bss =
                        jhove2.getSourceFactory().getByteStreamSource(jhove2, source,
                                this.valueOffset, this.count,
                                (this.tag == TiffIFD.ICCPROFILE) ? ".icc" : ".xml");
                    Format format = tiff2FormatMapper.getFormat(this.tag);
                    I8R identifier = format.getIdentifier();
                    FormatIdentification presumptiveFormat = new FormatIdentification(identifier, Confidence.PositiveSpecific); 
                    bss.addPresumptiveFormat(presumptiveFormat);
                    jhove2.characterize(bss, input);
                }
                else {
                    readValues(input);
                    validate(jhove2, input); 
                }
            }
            /* reset the input position so that the offset is set up correctly since when you read values the
             * input position gets changed from where you want to be in the IFD 
             * the offset of the Value field + 4 bytes will get you to the next Tag field
             */
            input.setPosition(this.savedValueOffset + 4);
        }
    }

    /**
     * 
     * isValidTag 
     * 
     *  1) that the type matches expected type values for that tag definition
     *  2) that count expected matches the count read in
     *  
     * @param jhove2
     * @return boolean 
     * @throws JHOVE2Exception
     * @throws IOException 
     */
    protected boolean isValidTag(JHOVE2 jhove2) throws JHOVE2Exception, IOException {

        boolean isValid = true;
        /* retrieve the definition for the tag read in */
        this.tagDefinition = TiffTag.getTag(this.tag);

        /* validate that the type and count read in matches what is expected for this tag */
        if (this.tagDefinition != null) {
            this.name = this.tagDefinition.getName();
            checkType(jhove2, this.tiffType, this.tagDefinition.getType());
            checkCount(jhove2, this.count, this.tagDefinition.getCardinality());
        }
        else {
            Object[]messageArgs = new Object[]{this.tag, tagOffset};
            this.UnknownTagMessage = (new Message(Severity.WARNING,
                    Context.OBJECT,
                    "org.jhove2.module.format.tiff.IFDEntry.UnknownTagMessage",
                    messageArgs, jhove2.getConfigInfo()));  
        }
        return isValid;
    }

    /**
     * 
     * perform validations and post-processing for specific tags 
     * 
     * @param jhove2
     * @throws JHOVE2Exception
     * @throws IOException 
     */
    protected void validate(JHOVE2 jhove2, Input input) throws JHOVE2Exception, IOException {

        /* Validate specific tags and set version when applicable */

        /* version 5 tags */
        if (this.tag == TiffIFD.ARTIST) {
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.COLORMAP) {
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.COMPRESSION) {
            // get the scheme to determine version
            int scheme = ((Short) this.getValue()).getValue();
            if (scheme == 5 && version < 5) {
                this.version = 5;
            }
            else if (scheme == 6 && version < 6) {
                this.version = 6;
            }
            /* set the descriptive format for the Compression Scheme */
            Compression compression =
                Compression.getCompressionValue(scheme, jhove2);
            if (compression != null) {
                this.compression_d = compression.getDescription();
            }
        }

        /* validate Date format is YYYY:MM:DD HH:MM:SS */
        else if (this.tag == TiffIFD.DATETIME) {
            if (this.version < 5) {
                this.version = 5;
            }
            SimpleDateFormat date = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
            String dateTime = (String)((AsciiArray) this.getValue()).toString();
            try {
                date.parse(dateTime);
            }
            catch (ParseException e) {
                this.isValid = Validity.False;
                Object[]messageArgs = new Object[]{dateTime};
                this.invalidDateTimeFormatMessage = new Message(Severity.ERROR,
                        Context.OBJECT,
                        "org.jhove2.module.format.tiff.IFDEntry.invalidDateTimeFormatMessage",
                        messageArgs, jhove2.getConfigInfo());
            }
            /* check that date is a valid date/time */
            date.setLenient(false);
            try {
                date.parse(dateTime);
            }
            catch (ParseException e) {
                this.isValid = Validity.False;
                Object[]messageArgs = new Object[]{dateTime};
                this.invalidDateTimeMessage = new Message(Severity.ERROR,
                        Context.OBJECT,
                        "org.jhove2.module.format.tiff.IFDEntry.invalidDateTimeMessage",
                        messageArgs, jhove2.getConfigInfo());
            }
        }        
        else if (this.tag == TiffIFD.HOSTCOMPUTER) {
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.NEWSUBFILETYPE) {
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.PREDICTOR) {
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.PRIMARY_CHROMATACITIES){
            if (this.version < 5) {
                this.version = 5;
            }
        }
        else if (this.tag == TiffIFD.SOFTWARE) {
            if (this.version < 5) {
                this.version = 5;
            }
        }        
        else if (this.tag == TiffIFD.WHITEPOINT) {
            if (this.version < 5) {
                this.version = 5;
            }
        }

        /* version 6 tags */
        else if (this.tag == TiffIFD.COPYRIGHT) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.DOTRANGE) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.EXTRASAMPLES) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.HALFTONEHINTS) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.INKNAMES) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.INKSET) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGACTABLES) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGDCTABLES) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGINTERCHANGEFORMAT) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGINTERCHANGEFORMATLENGTH) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGLOSSLESSPREDICTORS) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGPOINTTRANSFORMS) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEGPROC) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEG_RESTART_INTERVAL) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.JPEG_QTABLES) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.NUMBEROFINKS) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.PHOTMETRIC_INTERPRETATION) {
            int photometricInterpretation = ((Short) this.getValue()).getValue();
            if (photometricInterpretation == 5 ||  //(CMYK)
                    photometricInterpretation == 6 ||  //(YCbCr)
                    photometricInterpretation == 8) {  //(CIE L*a*b*)
                if (this.version < 6)
                    this.version = 6;
            }   
        }
        else if (this.tag == TiffIFD.REFERENCEBLACKWHITE) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.SAMPLEFORMAT) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.SMINSAMPLEVALUE) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.SMAXSAMPLEVALUE) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.TARGETPRINTER) {
            if (this.version < 6)
                this.version = 6;
        }
        /* Validate TILELENGTH  value is integral multiple of 16  */
        else if (this.tag == TiffIFD.TILELENGTH ) {
            if (this.version < 6) 
                this.version = 6;
            long tileLength = ((Long) this.getValue()).getValue();
            if (tileLength%16 > 0) {
                this.isValid = Validity.False;
                Object[]messageArgs = new Object[] {this.tag, this.valueOffset};
                this.tileLengthNotMultipleOf16Message = (new Message(Severity.WARNING,
                        Context.OBJECT,
                        "org.jhove2.module.format.tiff.IFDEntry.tileLengthNotMultipleof16Message",
                        messageArgs, jhove2.getConfigInfo()));  
                return;
            }
        }
        /* Validate TILEWIDTH value is integral multiple of 16  */
        else if (this.tag == TiffIFD.TILEWIDTH ) {
            if (this.version < 6) 
                this.version = 6;
            long tileWidth = ((Long) this.getValue()).getValue();

            if (tileWidth%16 > 0) {
                this.isValid = Validity.False;
                Object[]messageArgs = new Object[] {this.tag, this.valueOffset};
                this.tileWidthNotMultipleOf16Message = (new Message(Severity.WARNING,
                        Context.OBJECT,
                        "org.jhove2.module.format.tiff.IFDEntry.tileWidthNotMultipleof16Message",
                        messageArgs, jhove2.getConfigInfo()));  
            }
        }
        else if (this.tag == TiffIFD.TILEBYTECOUNTS ) {
            if (this.version < 6) 
                this.version = 6;
        }
        else if (this.tag == TiffIFD.TILEOFFSETS ) {
            if (this.version < 6) 
                this.version = 6;
        }
        else if (this.tag == TiffIFD.TRANSFERRANGE) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.YCBCRCOEFFICIENTS) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.YCBCRPOSITIONING) {
            if (this.version < 6)
                this.version = 6;
        }
        else if (this.tag == TiffIFD.YCBCRSUBSAMPLING) {
            if (this.version < 6)
                this.version = 6;
        }
    }

    /**
     * checks that the count value read in matches the expected count value
     * 
     * @param jhove2
     * @param count
     * @param expectedCount
     * @throws JHOVE2Exception
     */
    private void checkCount(JHOVE2 jhove2, long count, String expectedCount)
        throws JHOVE2Exception
    {
        if (expectedCount != null) {
            int expected = Integer.parseInt(expectedCount);
            if (count < expected) {
                this.isValid = Validity.False;
                Object[]messageArgs = new Object[] {this.tag, count, expectedCount};
                this.InvalidCountValueMessage = (new Message(Severity.WARNING,
                        Context.OBJECT,
                        "org.jhove2.module.format.tiff.IFDEntry.InvalidCountValueMessage",
                        messageArgs, jhove2.getConfigInfo()));               
            }

        }        
    }

    /**
     * compares the type that is read in with the type that is defined for the tag
     * For unsigned integers, readers accept BYTE, SHORT, LONG or IFD types
     * 
     * @param jhove2 - JHOVE2 framework
     * @param list - the list of expected types defined for this tag
     * @param string - the type read in 
     * @throws JHOVE2Exception
     */
    private void checkType(JHOVE2 jhove2, TiffType type, List<String> expectedTypes) throws JHOVE2Exception {

        int typeNum = type.num();
        String typeReadIn = type.name();
        /* type values of 6-12 were defined in TIFF 6.0 */
        if (typeNum > TiffType.SBYTE.num()) {
            this.version = 6;
        }

        boolean typeAccepted = false;
        /* Readers are supposed to accept BYTE, SHORT or LONG for any
         * unsigned integer field. */
        if ((type.equals(TiffType.BYTE) || (type.equals(TiffType.SHORT)) ||
                (type.equals(TiffType.LONG)) || (type.equals(TiffType.IFD)))) {
            for (String expectedEntry:expectedTypes){
                if (expectedEntry.equalsIgnoreCase("BYTE") || expectedEntry.equalsIgnoreCase("SHORT") || 
                        expectedEntry.equalsIgnoreCase("LONG") || expectedEntry.equalsIgnoreCase("IFD"))             
                    typeAccepted = true;  // type is valid
            }
        }

        // assign LONG type to tag if it can be SHORT or LONG
        if (typeAccepted) {
            /*
            if (expectedTypes.contains("SHORT") && expectedTypes.contains("LONG")) {
                this.tiffType = TiffType.LONG;
            }
             */
            return;
        }


        boolean match = false;
        for (String expectedEntry:expectedTypes){
            if (expectedEntry.equalsIgnoreCase(typeReadIn)) {
                match = true;
            }
        }
        if (!match) { 
            this.isValid = Validity.False;
            Object[]messageArgs = new Object[] {typeReadIn, this.tag, expectedTypes};
            this.TypeMismatchMessage = (new Message(Severity.ERROR,
                    Context.OBJECT,
                    "org.jhove2.module.format.tiff.IFDEntry.TypeMismatchMessage",
                    messageArgs, jhove2.getConfigInfo()));
        }            
    }

    /**
     * read the value based on type
     * If the size of the value is greater than 4 bytes
     * read the value directly from the valueOffset field (pointed
     * to by the savedValueOffset).
     * Else read the value from where the valueOffset points to.
     * 
     * 
     * Each value can be of a different type which is stored in a different
     * object.  Based on the tag's type and if the value is an array or not,
     * it will be stored in the appropriate object.  
     * 
     * @param input
     * @throws IOException
     */
    public void readValues(Input input) throws IOException {

        TiffType type = this.tiffType;

        /* if the value is stored in the field, read it from the field
         * otherwise use the valueOffset to go to where the value is
         */
        if (calcValueSize(this.type, this.count) > 4) 
            input.setPosition(this.valueOffset);            

        else
            input.setPosition(this.savedValueOffset);            
        
        if (this.count <= 1 ) {
            /* store a single value */
            if (type.equals(TiffType.BYTE) || type.equals(TiffType.UNDEFINED)) {
                this.byteValue = new Byte(input.readUnsignedByte());
            }
            else if (type.equals(TiffType.ASCII)){
                this.asciiValue = new Ascii();
                this.asciiValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.SHORT)) {
                this.shortValue = new Short(input.readUnsignedShort());
                // store in Long type if it can be SHORT or LONG 
                if (this.tagDefinition != null && this.tagDefinition.getType().contains("LONG")) 
                    this.longValue = new Long (shortValue.getValue());
            }
            else if (type.equals(TiffType.LONG)) {
                this.longValue = new Long(input.readUnsignedInt());
            }
            else if (type.equals(TiffType.FLOAT)) {
                this.floatValue = new FloatObject(input.readFloat());
            }
            else if (type.equals(TiffType.DOUBLE)) {
                this.doubleValue = new Double(input.readDouble());
            }
            else if (type.equals(TiffType.RATIONAL)) {
                long num = input.readUnsignedInt();
                long denom = input.readUnsignedInt();
                this.rationalValue = new Rational(num, denom);
            }
            else if (type.equals(TiffType.SBYTE)) {
                this.sByteValue = new SByte(input.readSignedByte());
            }
            else if (type.equals(TiffType.SSHORT)) {
                this.sShortValue = new SShort(input.readSignedShort());
            }
            else if (type.equals(TiffType.SLONG)) {
                this.sLongValue = new SLong(input.readSignedLong());
            }
            else if (type.equals(TiffType.SRATIONAL)) {
                long num = input.readSignedInt();
                long demon = input.readSignedInt();
                this.sRationalValue = new Rational(num, demon);
            }
        }
        else {
            this.isArray = true;
            /* read into an array */
            if (type.equals(TiffType.BYTE) || type.equals(TiffType.UNDEFINED)) {
                this.byteArrayValue = new ByteArray();
                this.byteArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.ASCII)){
                this.asciiArrayValue = new AsciiArray();
                this.asciiArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.SHORT)) {
                this.shortArrayValue = new ShortArray();
                this.shortArrayValue.setValue(input, this.count);
                // store in Long type if it can be SHORT or LONG 
                if (this.tagDefinition != null && this.tagDefinition.getType().contains("LONG")) {
                    this.longArrayValue = new LongArray (shortArrayValue.getShortArrayValue());
                }
            }
            else if (type.equals(TiffType.LONG)) {
                this.longArrayValue = new LongArray();
                this.longArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.DOUBLE)) {
                doubleArrayValue = new DoubleArray();
                doubleArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.RATIONAL)) {
                long num = input.readUnsignedInt();
                long denom = input.readUnsignedInt();
                this.rationalValue = new Rational(num, denom);
            }
            else if (type.equals(TiffType.SBYTE)) { 
                this.sbyteArrayValue = new SByteArray();
                this.sbyteArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.SSHORT)) {
                this.sShortArrayValue = new SShortArray();
                this.sShortArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.SLONG)) {
                this.sLongArrayValue = new SLongArray();
                this.sLongArrayValue.setValue(input, this.count);
            }
            else if (type.equals(TiffType.SRATIONAL)) {
                long num = input.readSignedLong();
                long demon = input.readSignedLong();
                this.rationalValue = new Rational(num, demon);
            }

        }
    }

    /**
     * getValue - returns the Type Object based on its' TiffType
     * 
     * @return Object containing the value
     */
    @ReportableProperty(order=7, value = "Tiff Tag Value.")
    public Object getValue(){
        TiffType type = this.tiffType;
        if (this.isArray) {
            if (type.equals(TiffType.BYTE) || (type.equals(TiffType.UNDEFINED))) {
                return this.byteArrayValue;
            }
            else if (type.equals(TiffType.ASCII)) {
                return this.asciiArrayValue;
            }
            else if (type.equals(TiffType.SHORT)) {
                // if value can be LONG or SHORT return the value stored in Long 
                if (this.tagDefinition != null && this.tagDefinition.getType().contains("LONG")) 
                    return this.longArrayValue;
                else
                    return this.shortArrayValue;
            }
            else if (type.equals(TiffType.LONG)) {
                return this.longArrayValue;
            }
            else if (type.equals(TiffType.RATIONAL)) {
                return this.rationalArrayValue;
            }
            else if (type.equals(TiffType.SSHORT)) {
                return this.sShortArrayValue;
            }
            else if (type.equals(TiffType.SLONG)) {
                return this.sLongArrayValue;
            }
            else if (type.equals(TiffType.SRATIONAL)) {
                return this.sRationalArrayValue;
            }
        }
        else  {
            if (type.equals(TiffType.BYTE) || (type.equals(TiffType.UNDEFINED))) {
                return this.byteValue;
            }
            else if (type.equals(TiffType.ASCII)) {
                return this.asciiValue;
            }
            else if (type.equals(TiffType.SHORT)) {
                // if value can be LONG or SHORT return the value stored in Long 
                if (this.tagDefinition != null && this.tagDefinition.getType().contains("LONG")) {
                    return this.longValue;
                }
                else {
                    return this.shortValue;
                }
            }
            else if (type.equals(TiffType.LONG)) {
                return this.longValue;
            }
            else if (type.equals(TiffType.RATIONAL)) {
                return this.rationalValue;
            }
            else if (type.equals(TiffType.SBYTE)) {
                return this.sByteValue;
            }
            else if (type.equals(TiffType.SLONG)) {
                return this.sLongValue;
            }
            else if (type.equals(TiffType.SRATIONAL)) {
                return this.sRationalValue;
            }
        }
        return null;
    }


    /**
     * Calculate how many bytes a given number of fields of a given
     * type will require.
     * @param type Field type
     * @param count Field count
     */
    public static long calcValueSize (int type, long count)
    {
        int fieldSize = 0;
        fieldSize = TiffType.getType(type).size();
        return  count*fieldSize;
    }

    /**
     * reset the previous tag value 
     */
    public static void resetPrevTag(int value) {
        prevTag = value;
    }
    /**
     * The cardinality (number of values) for this TIFF tag  
     * @return long
     */
    @ReportableProperty (order=5, value="Entry cardinality, number of values.")
    public long getCount(){
        return this.count;
    }

    /**
     * The name of the tag entry
     * @return String 
     */
    @ReportableProperty (order=1, value = "Entry tag name.")
    public String getName(){
        return this.name;
    }

    /**
     * The TIFF tag number
     * @return int
     */
    @ReportableProperty(order=2, value="Entry tag.")
    public int getTag(){
        return this.tag;
    }

    /**
     * Get the unknown tag message
     * @return the unknownTagMessage
     */
    @ReportableProperty(order=9, value = "unknown tag message.")
    public Message getUnknownTagMessage() {
        return this.UnknownTagMessage;
    }

    /**
     * The field type of the value for this tag
     * @return TiffType
     */
   @ReportableProperty(order=4, value="Tag type.")
    public TiffType getTiffType(){
        return this.tiffType;
    }
    
    /**
     * The value read from the type field for this tag
     * @return long
     */
    @ReportableProperty(order=3, value="Tag type.")
    public long getType(){
        return this.type;
    }



    /**
     * Get the tag sort order error message
     * 
     * @return the tagSortOrderErrorMessage
     */
    @ReportableProperty(order=7, value = "tag sort order error message.")
    public Message getTagSortOrderErrorMessage() {
        return this.TagSortOrderErrorMessage;
    }

    /**
     * Get the unknown type message
     * 
     * @return the unknownTypeMessage
     */
    @ReportableProperty(order=8, value = "unknown type message.")
    public List<Message> getUnknownTypeMessage() {
        return this.unknownTypeMessages;
    }
    /**
     * Get byte offset not word aligned message
     * 
     * @return the byteOffsetNotWordAlignedMessage
     */
    @ReportableProperty(order=9, value = "Byte offset not word aligned message.")
    public Message getByteOffsetNotWordAlignedMessage() {
        return this.ByteOffsetNotWordAlignedMessage;
    }

    /**
     * Get the value offset reference location file message
     * 
     * @return the valueOffsetReferenceLocationFileMessage
     */
    @ReportableProperty(order=10, value = "value offset reference location file message.")
    public Message getValueOffsetReferenceLocationFileMessage() {
        return this.ValueOffsetReferenceLocationFileMessage;
    }

    /**
     * @return the typeMismatchMessage
     */
    @ReportableProperty(order=11, value="type mismatch message.")
    public Message getTypeMismatchMessage() {
        return this.TypeMismatchMessage;
    }

    /**
     * @return the invalidCountValueMessage
     */
    @ReportableProperty(order=12, value="Invalid Count Value Message.")
    public Message getInvalidCountValueMessage() {
        return this.InvalidCountValueMessage;
    }

    /**
     * @return the unknownTypeMessages
     */
    @ReportableProperty(order=13, value="Unknown Type Message.")
    public List<Message> getUnknownTypeMessages() {
        return this.unknownTypeMessages;
    }

    /**
     * @return the invalidDateTimeMessage
     */
    @ReportableProperty(order=14, value="Invalid Date Time Message.")
    public Message getInvalidDateTimeMessage() {
        return this.invalidDateTimeMessage;
    }

    /**
     * @return the invalidDateTimeFormatMessage
     */
    @ReportableProperty(order=15, value="Invalid Date Time Format Message.")
    public Message getInvalidDateTimeFormatMessage() {
        return this.invalidDateTimeFormatMessage;
    }

    /**
     * @return the tileWidthNotMultipleOf16Message
     */
    @ReportableProperty(order = 16, value = "TileWidth not multiple of 16 message.")
    public Message getTileWidthNotMultipleOf16Message() {
        return this.tileWidthNotMultipleOf16Message;
    }

    /**
     * @return the tileLengthNotMultipleOf16Message
     */
    @ReportableProperty(order = 17, value = "TileLength not multiple of 16 message.")
    public Message getTileLengthNotMultipleOf16Message() {
        return this.tileLengthNotMultipleOf16Message;
    }

    @ReportableProperty(order=7, value="Compression Scheme in descriptive form.",
            type=PropertyType.Descriptive)
    public String getCompression_descriptive() {
        return this.compression_d;
    }

    /** Get IFDEntry validity.
     * @return IFDEntry validity
     */
    @ReportableProperty(order=18, value="IFDEntry validity.")
    public Validity isValid() {
        return this.isValid;
    }

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}
