/*
 * @(#)TIFFTag.java  1.0  2010-07-24
 * 
 * Copyright (c) 2010 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package org.monte.media.tiff;

import org.monte.media.math.Rational;

/**
 * A class defining the notion of a TIFF tag. A TIFF tag is a key that may
 * appear in an Image File Directory (IFD). In the IFD each tag has some data
 * associated with it, which may consist of zero or more values of a given data
 * type. The combination of a tag and a value is known as an IFD Entry or TIFF
 * Field.
 * <p>
 * The actual tag values used in the root IFD of a standard ("baseline") tiff
 * stream are defined in the {@link BaselineTagSet} class.
 * <p>
 *
 * @author Werner Randelshofer
 * @version 1.0 2010-07-24 Created.
 */
public class TIFFTag {

    public final static int ASCII_MASK = 1 << IFDDataType.ASCII.getTypeNumber();
    public final static int BYTE_MASK = 1 << IFDDataType.BYTE.getTypeNumber();
    public final static int DOUBLE_MASK = 1 << IFDDataType.DOUBLE.getTypeNumber();
    public final static int FLOAT_MASK = 1 << IFDDataType.FLOAT.getTypeNumber();
    public final static int IFD_MASK = 1 << IFDDataType.IFD.getTypeNumber();
    public final static int LONG_MASK = 1 << IFDDataType.LONG.getTypeNumber();
    public final static int SHORT_MASK = 1 << IFDDataType.SHORT.getTypeNumber();
    public final static int RATIONAL_MASK = 1 << IFDDataType.RATIONAL.getTypeNumber();
    public final static int SBYTE_MASK = 1 << IFDDataType.BYTE.getTypeNumber();
    public final static int SLONG_MASK = 1 << IFDDataType.SLONG.getTypeNumber();
    public final static int SSHORT_MASK = 1 << IFDDataType.SSHORT.getTypeNumber();
    public final static int SRATIONAL_MASK = 1 << IFDDataType.SRATIONAL.getTypeNumber();
    public final static int UNDEFINED_MASK = 1 << IFDDataType.UNDEFINED.getTypeNumber();
    public final static int ALL_MASK = -1;
    private String name;
    private int number;
    private int dataTypes;
    private TagSet tagSet;
    private ValueFormatter formatter;

    /**
     * Constructs a TIFFTag with a given name, tag number, set of legal data types,
     * and TagSet to which it refers. The tagSet parameter will generally be
     * non-null only if this TIFFTag corresponds to a pointer to a TIFF IFD. In this
     * case tagSet will represent the set of TIFFTags which appear in the IFD
     * pointed to. A TIFFTag represents an IFD pointer if and only if tagSet is
     * non-null or the data type TIFF_IFD_POINTER is legal.
     * <p>
     * If there are mnemonic names to be associated with the legal data values for the
     * tag, addValueName() should be called on the new instance for each name.
     * <p>
     * See the documentation for getDataTypes() for an explanation of how the set of data types is to be converted into a bit mask.
     * @param name the name of the tag; may be null.
     * @param number the number used to represent the tag.
     * @param dataTypes a bit mask indicating the set of legal data types for this tag.
     * @param formatter a ValueFormatter for formatting data values.
     */
    public TIFFTag(String name,
            int number,
            int dataTypes,
            ValueFormatter formatter) {
        this.name = name;
        this.number = number;
        this.dataTypes = dataTypes;
        this.formatter = formatter;
    }

    /**
     * Constructs a TIFFTag with a given name, tag number, set of legal data types,
     * and TagSet to which it refers. The tagSet parameter will generally be
     * non-null only if this TIFFTag corresponds to a pointer to a TIFF IFD. In this
     * case tagSet will represent the set of TIFFTags which appear in the IFD
     * pointed to. A TIFFTag represents an IFD pointer if and only if tagSet is
     * non-null or the data type TIFF_IFD_POINTER is legal.
     * <p>
     * If there are mnemonic names to be associated with the legal data values for the
     * tag, addValueName() should be called on the new instance for each name.
     * <p>
     * See the documentation for getDataTypes() for an explanation of how the set of data types is to be converted into a bit mask.
     * @param name the name of the tag; may be null.
     * @param number the number used to represent the tag.
     * @param dataTypes a bit mask indicating the set of legal data types for this tag.
     */
    public TIFFTag(String name,
            int number,
            int dataTypes) {
        this(name, number, dataTypes, null);
    }

    /**
     * @param tagSet the TagSet to which this tag belongs; may be null.
     */
    /* package */ void setTagSet(TagSet tagSet) {
        this.tagSet = tagSet;
    }

    /** Returns the integer used to represent the tag. */
    public int getNumber() {
        return number;
    }

    /** Returns the name of the tag, or null if the name is not known. */
    public String getName() {
        return name;
    }

    public boolean isSynthetic() {
        return number < 0;
    }

    public IFDDataType getType(Object data) {
        int m = dataTypes;

        if (data != null && data.getClass().isArray()) {
            data = ((Object[]) data)[0];
        }

        for (int i = 0; i < 32; i++) {
            if ((m & 1) == 1) {
                switch (IFDDataType.valueOf(i)) {
                    case ASCII:
                        if (data == null
                                || (data instanceof String)) {
                            return IFDDataType.ASCII;
                        }
                        break;
                    case BYTE:
                        if (data == null
                                || (data instanceof Short)) {
                            return IFDDataType.BYTE;
                        }
                        break;
                    case DOUBLE:
                        if (data == null
                                || (data instanceof Double)) {
                            return IFDDataType.DOUBLE;
                        }
                        break;
                    case FLOAT:
                        if (data == null
                                || (data instanceof Float)) {
                            return IFDDataType.FLOAT;
                        }
                        break;
                    case IFD:
                        if (data == null
                                || (data instanceof Long)) {
                            return IFDDataType.IFD;
                        }
                        break;
                    case LONG:
                        if (data == null
                                || (data instanceof Long)) {
                            return IFDDataType.LONG;
                        }
                        break;
                    case RATIONAL:
                        if (data == null
                                || (data instanceof Rational)) {
                            return IFDDataType.RATIONAL;
                        }
                        break;
                    case SBYTE:
                        if (data == null
                                || (data instanceof Byte)) {
                            return IFDDataType.SBYTE;
                        }
                        break;
                    case SHORT:
                        if (data == null
                                || (data instanceof Integer)) {
                            return IFDDataType.SHORT;
                        }
                        break;
                    case SLONG:
                        if (data == null
                                || (data instanceof Integer)) {
                            return IFDDataType.SLONG;
                        }
                        break;
                    case SRATIONAL:
                        if (data == null
                                || (data instanceof Rational)) {
                            return IFDDataType.SRATIONAL;
                        }
                        break;
                    case SSHORT:
                        if (data == null
                                || (data instanceof Short)) {
                            return IFDDataType.SSHORT;
                        }
                        break;
                    case UNDEFINED:
                        if (data == null
                                || (data instanceof Byte)) {
                            return IFDDataType.UNDEFINED;
                        }
                        break;
                }

                return IFDDataType.valueOf(i);
            }
            m >>= 1;
        }
        return IFDDataType.UNDEFINED;
    }

    public Object prettyFormat(Object data) {
        return (formatter == null) ? data : formatter.prettyFormat(data);
    }

    public Object format(Object data) {
        return (formatter == null) ? data : formatter.format(data);
    }

    @Override
    public String toString() {
        return name;
    }

    public String getDescription(Object data) {
        return (formatter == null) ? null : formatter.descriptionFormat(data);
    }
}
