//
// Copyright (c) 2016 KAMADA Ken'ichi.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
//

use std::fmt;

use crate::error::Error;
use crate::value;
use crate::value::Value;
use crate::util::atou16;

/// A tag of a TIFF/Exif field.
///
/// Some well-known tags are provided as associated constants of
/// this type.  The constant names follow the Exif specification
/// but not the Rust naming conventions.
///
/// A non-predefined tag can also be specified
/// by the context and the number as in `Tag(Context::Tiff, 0x100)`.
//
// This is not an enum to keep safety and API stability, while
// supporting unknown tag numbers.  This comment is based on the
// behavior of Rust 1.12.
// Storing unknown values in a repr(u16) enum is unsafe.  The compiler
// assumes that there is no undefined discriminant even with a C-like
// enum, so the exhaustiveness check of a match expression will break.
// Storing unknown values in a special variant such as Unknown(u16)
// tends to break backward compatibility.  When Tag::VariantFoo is
// defined in a new version of the library, the old codes using
// Tag::Unknown(Foo's tag number) will break.
//
// Use of constants is restricted in patterns.  As of Rust 1.12,
// PartialEq and Eq need to be _automatically derived_ for Tag to
// emulate structural equivalency.
// <https://github.com/rust-lang/rfcs/pull/1445>
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(pub Context, pub u16);

impl Tag {
    /// Returns the context of the tag.
    ///
    /// # Examples
    /// ```
    /// use exif::{Context, Tag};
    /// assert_eq!(Tag::XResolution.context(), Context::Tiff);
    /// assert_eq!(Tag::DateTimeOriginal.context(), Context::Exif);
    /// ```
    #[inline]
    pub fn context(self) -> Context {
        self.0
    }

    /// Returns the tag number.
    ///
    /// # Examples
    /// ```
    /// use exif::Tag;
    /// assert_eq!(Tag::XResolution.number(), 0x11a);
    /// assert_eq!(Tag::DateTimeOriginal.number(), 0x9003);
    /// ```
    #[inline]
    pub fn number(self) -> u16 {
        self.1
    }

    /// Returns the description of the tag.
    #[inline]
    pub fn description(&self) -> Option<&str> {
        get_tag_info(*self).map(|ti| ti.desc)
    }

    /// Returns the default value of the tag.  `None` is returned if
    /// it is not defined in the standard or it depends on another tag.
    #[inline]
    pub fn default_value(&self) -> Option<Value> {
        get_tag_info(*self).and_then(|ti| (&ti.default).into())
    }

    pub(crate) fn unit(self) -> Option<&'static [UnitPiece]> {
        get_tag_info(self).and_then(|ti| ti.unit)
    }
}

impl fmt::Display for Tag {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match get_tag_info(*self) {
            Some(ti) => f.pad(ti.name),
            None => f.pad(&format!("{:?}", self)),
        }
    }
}

/// An enum that indicates how a tag number is interpreted.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Context {
    /// TIFF attributes defined in the TIFF Rev. 6.0 specification.
    Tiff,	// 0th/1st IFD (toplevel)
    /// Exif attributes.
    Exif,	// -- Exif IFD
    /// GPS attributes.
    Gps,	// -- GPS IFD
    /// Interoperability attributes.
    Interop,	// -- Exif IFD -- Interoperability IFD
}

#[derive(Debug)]
pub enum UnitPiece {
    Value,
    Str(&'static str),
    Tag(Tag),
}

macro_rules! unit {
    () => ( None );
    ( $str:literal ) => ( unit![V, concat!(" ", $str)] );
    ( Tag::$tag:ident ) => ( unit![V, " ", Tag::$tag] );
    ( $($tokens:tt)* ) => ( Some(unit_expand!( ; $($tokens)* , )) );
}

macro_rules! unit_expand {
    ( $($built:expr),* ; ) => ( &[$($built),*] );
    ( $($built:expr),* ; , ) => ( &[$($built),*] );
    ( $($built:expr),* ; V, $($rest:tt)* ) => (
        unit_expand!($($built,)* UnitPiece::Value ; $($rest)*) );
    ( $($built:expr),* ; $str:literal, $($rest:tt)* ) => (
        unit_expand!($($built,)* UnitPiece::Str($str) ; $($rest)*) );
    ( $($built:expr),* ; concat!($($strs:literal),*), $($rest:tt)* ) => (
        unit_expand!($($built,)* UnitPiece::Str(concat!($($strs),*)) ; $($rest)*) );
    ( $($built:expr),* ; Tag::$tag:ident, $($rest:tt)* ) => (
        unit_expand!($($built,)* UnitPiece::Tag(Tag::$tag) ; $($rest)*) );
}

macro_rules! generate_well_known_tag_constants {
    (
        $( |$ctx:path| $(
            // Copy the doc attribute to the actual definition.
            $( #[$attr:meta] )*
            ($name:ident, $num:expr, $defval:expr, $dispval:ident, $unit:expr,
             $desc:expr)
        ),+, )+
    ) => (
        // This is not relevant for associated constants, because
        // they cannot be imported even with "uniform paths".
        // /// It is not recommended to import the constants directly into
        // /// your namespace; import the module and use with the module name
        // /// like `tag::DateTime`.  The constant names follow the Exif
        // /// specification but not the Rust naming conventions, and a user
        // /// of the constants will get the non_upper_case_globals warning
        // /// if a bare constant is used in a match arm.
        // // This is discussed in
        // // <https://github.com/rust-lang/rust/issues/25207>.
        impl Tag {
            $($(
                $( #[$attr] )*
                #[allow(non_upper_case_globals)]
                pub const $name: Tag = Tag($ctx, $num);
            )+)+
        }

        mod tag_info {
            use std::fmt;
            use crate::value::Value;
            use crate::value::DefaultValue;
            use super::{Tag, UnitPiece};

            pub struct TagInfo {
                pub name: &'static str,
                pub desc: &'static str,
                pub default: DefaultValue,
                pub dispval: fn(&mut dyn fmt::Write, &Value) -> fmt::Result,
                pub unit: Option<&'static [UnitPiece]>,
            }

            $($(
                #[allow(non_upper_case_globals)]
                pub static $name: TagInfo = TagInfo {
                    name: stringify!($name),
                    desc: $desc,
                    default: $defval,
                    dispval: super::$dispval,
                    unit: $unit,
                };
            )+)+
        }

        fn get_tag_info(tag: Tag) -> Option<&'static tag_info::TagInfo> {
            match tag {
                $($(
                    Tag::$name => Some(&tag_info::$name),
                )+)+
                _ => None,
            }
        }
    )
}

// Tag constant names do not follow the Rust naming conventions but
// the Exif field names: camel cases and all-capital acronyms.
generate_well_known_tag_constants!(
    // Exif-specific IFDs [EXIF23 4.6.3].
    |Context::Tiff|

    /// A pointer to the Exif IFD.  This is used for the internal structure
    /// of Exif data and will not be returned to the user.
    #[doc(hidden)]
    (ExifIFDPointer, 0x8769, DefaultValue::None, d_default,
     unit![],
     "Exif IFD pointer"),
    /// A pointer to the GPS IFD.  This is used for the internal structure
    /// of Exif data and will not be returned to the user.
    #[doc(hidden)]
    (GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default,
     unit![],
     "GPS Info IFD pointer"),

    |Context::Exif|

    /// A pointer to the interoperability IFD.  This is used for the internal
    /// structure of Exif data and will not be returned to the user.
    #[doc(hidden)]
    (InteropIFDPointer, 0xa005, DefaultValue::None, d_default,
     unit![],
     "Interoperability IFD pointer"),

    // TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4,
    // 4.6.8 Table 17, and 4.6.8 Table 21].
    |Context::Tiff|

    (ImageWidth, 0x100, DefaultValue::None, d_default,
     unit!["pixels"],
     "Image width"),
    (ImageLength, 0x101, DefaultValue::None, d_default,
     unit!["pixels"],
     "Image height"),
    (BitsPerSample, 0x102, DefaultValue::Short(&[8, 8, 8]), d_default,
     unit![],
     "Number of bits per component"),
    (Compression, 0x103, DefaultValue::None, d_compression,
     unit![],
     "Compression scheme"),
    (PhotometricInterpretation, 0x106, DefaultValue::None, d_photointp,
     unit![],
     "Pixel composition"),
    (ImageDescription, 0x10e, DefaultValue::None, d_default,
     unit![],
     "Image title"),
    /// Manufacturer of the image input equipment.
    (Make, 0x10f, DefaultValue::None, d_default,
     unit![],
     "Manufacturer of image input equipment"),
    /// Model name or number of the image input equipment.
    (Model, 0x110, DefaultValue::None, d_default,
     unit![],
     "Model of image input equipment"),
    (StripOffsets, 0x111, DefaultValue::None, d_default,
     unit![],
     "Image data location"),
    (Orientation, 0x112, DefaultValue::Short(&[1]), d_orientation,
     unit![],
     "Orientation of image"),
    (SamplesPerPixel, 0x115, DefaultValue::Short(&[3]), d_default,
     unit![],
     "Number of components"),
    (RowsPerStrip, 0x116, DefaultValue::None, d_default,
     unit![],
     "Number of rows per strip"),
    (StripByteCounts, 0x117, DefaultValue::None, d_default,
     unit![],
     "Bytes per compressed strip"),
    (XResolution, 0x11a, DefaultValue::Rational(&[(72, 1)]), d_decimal,
     unit![V, " pixels per ", Tag::ResolutionUnit],
     "Image resolution in width direction"),
    (YResolution, 0x11b, DefaultValue::Rational(&[(72, 1)]), d_decimal,
     unit![V, " pixels per ", Tag::ResolutionUnit],
     "Image resolution in height direction"),
    (PlanarConfiguration, 0x11c, DefaultValue::Short(&[1]), d_planarcfg,
     unit![],
     "Image data arrangement"),
    /// Unit of XResolution and YResolution fields.
    (ResolutionUnit, 0x128, DefaultValue::Short(&[2]), d_resunit,
     unit![],
     "Unit of X and Y resolution"),
    (TransferFunction, 0x12d, DefaultValue::None, d_default,
     unit![],
     "Transfer function"),
    /// Name and version of the software used to create the image.
    (Software, 0x131, DefaultValue::None, d_default,
     unit![],
     "Software used"),
    /// Date and time when the image file was created or last edited.
    /// For the time when the picture was taken, see DateTimeOriginal field.
    (DateTime, 0x132, DefaultValue::None, d_datetime,
     unit![],
     "File change date and time"),
    (Artist, 0x13b, DefaultValue::None, d_default,
     unit![],
     "Person who created the image"),
    (WhitePoint, 0x13e, DefaultValue::None, d_decimal,
     unit![],
     "White point chromaticity"),
    (PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal,
     unit![],
     "Chromaticities of primaries"),
    // Not referenced in Exif.
    (TileOffsets, 0x144, DefaultValue::None, d_default,
     unit![],
     "Tiled image data location"),
    // Not referenced in Exif.
    (TileByteCounts, 0x145, DefaultValue::None, d_default,
     unit![],
     "Bytes per compressed tile"),
    (JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default,
     unit![],
     "Offset to JPEG SOI"),
    (JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default,
     unit![],
     "Bytes of JPEG data"),
    (YCbCrCoefficients, 0x211, DefaultValue::Unspecified, d_decimal,
     unit![],
     "Color space transformation matrix coefficients"),
    (YCbCrSubSampling, 0x212, DefaultValue::None, d_ycbcrsubsamp,
     unit![],
     "Subsampling ratio of Y to C"),
    (YCbCrPositioning, 0x213, DefaultValue::Short(&[1]), d_ycbcrpos,
     unit![],
     "Y and C positioning"),
    (ReferenceBlackWhite, 0x214, DefaultValue::ContextDependent, d_decimal,
     unit![],
     "Pair of black and white reference values"),
    (Copyright, 0x8298, DefaultValue::None, d_default,
     unit![],
     "Copyright holder"),

    // Exif IFD attributes [EXIF23 4.6.5 Table 7 and 4.6.8 Table 18].
    |Context::Exif|

    (ExposureTime, 0x829a, DefaultValue::None, d_exptime,
     unit!["s"],
     "Exposure time"),
    (FNumber, 0x829d, DefaultValue::None, d_decimal,
     // F-number is dimensionless, but usually prefixed with "F" in Japan,
     // "f/" in the U.S., and so on.
     unit!["f/", V],
     "F number"),
    (ExposureProgram, 0x8822, DefaultValue::None, d_expprog,
     unit![],
     "Exposure program"),
    (SpectralSensitivity, 0x8824, DefaultValue::None, d_default,
     unit![],
     "Spectral sensitivity"),
    /// Sensitivity of the device.
    ///
    /// The value may be represented by standard output sensitivity (SOS),
    /// recommended exposure index (REI), or ISO speed.
    /// What is stored is designated by SensitivityType field.
    ///
    /// This field is 16-bit and may be saturated.  For 32-bit values,
    /// see StandardOutputSensitivity, RecommendedExposureIndex,
    /// ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz fields.
    (PhotographicSensitivity, 0x8827, DefaultValue::None, d_default,
     unit![],
     "Photographic sensitivity"),
    (OECF, 0x8828, DefaultValue::None, d_default,
     unit![],
     "Optoelectric conversion factor"),
    (SensitivityType, 0x8830, DefaultValue::None, d_sensitivitytype,
     unit![],
     "Sensitivity type"),
    (StandardOutputSensitivity, 0x8831, DefaultValue::None, d_default,
     unit![],
     "Standard output sensitivity"),
    (RecommendedExposureIndex, 0x8832, DefaultValue::None, d_default,
     unit![],
     "Recommended exposure index"),
    (ISOSpeed, 0x8833, DefaultValue::None, d_default,
     unit![],
     "ISO speed"),
    (ISOSpeedLatitudeyyy, 0x8834, DefaultValue::None, d_default,
     unit![],
     "ISO speed latitude yyy"),
    (ISOSpeedLatitudezzz, 0x8835, DefaultValue::None, d_default,
     unit![],
     "ISO speed latitude zzz"),
    // The absence of this field means non-conformance to Exif, so the default
    // value specified in the standard (e.g., "0231") should not apply.
    (ExifVersion, 0x9000, DefaultValue::None, d_exifver,
     unit![],
     "Exif version"),
    /// Date and time when the original image was generated (e.g.,
    /// the picture was taken by a camera).
    (DateTimeOriginal, 0x9003, DefaultValue::None, d_datetime,
     unit![],
     "Date and time of original data generation"),
    /// Date and time when the image was stored as digital data.
    /// If a picture is taken by a film camera and then digitized later,
    /// this value will be different from DateTimeOriginal field.
    (DateTimeDigitized, 0x9004, DefaultValue::None, d_datetime,
     unit![],
     "Date and time of digital data generation"),
    /// Timezone offset for DateTime field.
    (OffsetTime, 0x9010, DefaultValue::None, d_default,
     unit![],
     "Offset data of DateTime"),
    /// Timezone offset for DateTimeOriginal field.
    (OffsetTimeOriginal, 0x9011, DefaultValue::None, d_default,
     unit![],
     "Offset data of DateTimeOriginal"),
    /// Timezone offset for DateTimeDigitized field.
    (OffsetTimeDigitized, 0x9012, DefaultValue::None, d_default,
     unit![],
     "Offset data of DateTimeDigitized"),
    (ComponentsConfiguration, 0x9101, DefaultValue::ContextDependent, d_cpntcfg,
     unit![],
     "Meaning of each component"),
    (CompressedBitsPerPixel, 0x9102, DefaultValue::None, d_decimal,
     unit![],
     "Image compression mode"),
    (ShutterSpeedValue, 0x9201, DefaultValue::None, d_decimal,
     unit!["EV"],
     "Shutter speed"),
    (ApertureValue, 0x9202, DefaultValue::None, d_decimal,
     unit!["EV"],
     "Aperture"),
    (BrightnessValue, 0x9203, DefaultValue::None, d_optdecimal,
     unit!["EV"],
     "Brightness"),
    (ExposureBiasValue, 0x9204, DefaultValue::None, d_decimal,
     unit!["EV"],
     "Exposure bias"),
    (MaxApertureValue, 0x9205, DefaultValue::None, d_decimal,
     unit!["EV"],
     "Maximum lens aperture"),
    (SubjectDistance, 0x9206, DefaultValue::None, d_subjdist,
     unit!["m"],
     "Subject distance"),
    (MeteringMode, 0x9207, DefaultValue::Short(&[0]), d_metering,
     unit![],
     "Metering mode"),
    (LightSource, 0x9208, DefaultValue::Short(&[0]), d_lightsrc,
     unit![],
     "Light source"),
    (Flash, 0x9209, DefaultValue::Unspecified, d_flash,
     unit![],
     "Flash"),
    (FocalLength, 0x920a, DefaultValue::None, d_decimal,
     unit!["mm"],
     "Lens focal length"),
    (SubjectArea, 0x9214, DefaultValue::None, d_subjarea,
     unit![],
     "Subject area"),
    (MakerNote, 0x927c, DefaultValue::None, d_default,
     unit![],
     "Manufacturer notes"),
    (UserComment, 0x9286, DefaultValue::None, d_default,
     unit![],
     "User comments"),
    /// Subseconds for DateTime field.
    (SubSecTime, 0x9290, DefaultValue::None, d_default,
     unit![],
     "DateTime subseconds"),
    /// Subseconds for DateTimeOriginal field.
    (SubSecTimeOriginal, 0x9291, DefaultValue::None, d_default,
     unit![],
     "DateTimeOriginal subseconds"),
    /// Subseconds for DateTimeDigitized field.
    (SubSecTimeDigitized, 0x9292, DefaultValue::None, d_default,
     unit![],
     "DateTimeDigitized subseconds"),
    (Temperature, 0x9400, DefaultValue::None, d_optdecimal,
     unit!["degC"],
     "Temperature"),
    (Humidity, 0x9401, DefaultValue::None, d_optdecimal,
     unit!["%"],
     "Humidity"),
    (Pressure, 0x9402, DefaultValue::None, d_optdecimal,
     unit!["hPa"],
     "Pressure"),
    (WaterDepth, 0x9403, DefaultValue::None, d_optdecimal,
     unit!["m"],
     "Water depth"),
    (Acceleration, 0x9404, DefaultValue::None, d_optdecimal,
     unit!["mGal"],
     "Acceleration"),
    (CameraElevationAngle, 0x9405, DefaultValue::None, d_optdecimal,
     unit!["deg"],
     "Camera elevation angle"),
    (FlashpixVersion, 0xa000, DefaultValue::Undefined(b"0100"), d_exifver,
     unit![],
     "Supported Flashpix version"),
    (ColorSpace, 0xa001, DefaultValue::Unspecified, d_cspace,
     unit![],
     "Color space information"),
    (PixelXDimension, 0xa002, DefaultValue::None, d_default,
     unit!["pixels"],
     "Valid image width"),
    (PixelYDimension, 0xa003, DefaultValue::Unspecified, d_default,
     unit!["pixels"],
     "Valid image height"),
    (RelatedSoundFile, 0xa004, DefaultValue::None, d_default,
     unit![],
     "Related audio file"),
    (FlashEnergy, 0xa20b, DefaultValue::None, d_decimal,
     unit!["BCPS"],
     "Flash energy"),
    (SpatialFrequencyResponse, 0xa20c, DefaultValue::None, d_default,
     unit![],
     "Spatial frequency response"),
    (FocalPlaneXResolution, 0xa20e, DefaultValue::None, d_decimal,
     unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit],
     "Focal plane X resolution"),
    (FocalPlaneYResolution, 0xa20f, DefaultValue::None, d_decimal,
     unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit],
     "Focal plane Y resolution"),
    /// Unit of FocalPlaneXResolution and FocalPlaneYResolution fields.
    (FocalPlaneResolutionUnit, 0xa210, DefaultValue::Short(&[2]), d_resunit,
     unit![],
     "Focal plane resolution unit"),
    (SubjectLocation, 0xa214, DefaultValue::None, d_subjarea,
     unit![],
     "Subject location"),
    (ExposureIndex, 0xa215, DefaultValue::None, d_decimal,
     unit![],
     "Exposure index"),
    (SensingMethod, 0xa217, DefaultValue::None, d_sensingmethod,
     unit![],
     "Sensing method"),
    (FileSource, 0xa300, DefaultValue::Undefined(&[3]), d_filesrc,
     unit![],
     "File source"),
    (SceneType, 0xa301, DefaultValue::Undefined(&[1]), d_scenetype,
     unit![],
     "Scene type"),
    (CFAPattern, 0xa302, DefaultValue::None, d_default,
     unit![],
     "CFA pattern"),
    (CustomRendered, 0xa401, DefaultValue::Short(&[0]), d_customrendered,
     unit![],
     "Custom image processing"),
    (ExposureMode, 0xa402, DefaultValue::None, d_expmode,
     unit![],
     "Exposure mode"),
    (WhiteBalance, 0xa403, DefaultValue::None, d_whitebalance,
     unit![],
     "White balance"),
    (DigitalZoomRatio, 0xa404, DefaultValue::None, d_dzoomratio,
     unit![],
     "Digital zoom ratio"),
    (FocalLengthIn35mmFilm, 0xa405, DefaultValue::None, d_focallen35,
     unit!["mm"],
     "Focal length in 35 mm film"),
    (SceneCaptureType, 0xa406, DefaultValue::Short(&[0]), d_scenecaptype,
     unit![],
     "Scene capture type"),
    (GainControl, 0xa407, DefaultValue::None, d_gainctrl,
     unit![],
     "Gain control"),
    (Contrast, 0xa408, DefaultValue::Short(&[0]), d_contrast,
     unit![],
     "Contrast"),
    (Saturation, 0xa409, DefaultValue::Short(&[0]), d_saturation,
     unit![],
     "Saturation"),
    (Sharpness, 0xa40a, DefaultValue::Short(&[0]), d_sharpness,
     unit![],
     "Sharpness"),
    (DeviceSettingDescription, 0xa40b, DefaultValue::None, d_default,
     unit![],
     "Device settings description"),
    (SubjectDistanceRange, 0xa40c, DefaultValue::None, d_subjdistrange,
     unit![],
     "Subject distance range"),
    (ImageUniqueID, 0xa420, DefaultValue::None, d_default,
     unit![],
     "Unique image ID"),
    (CameraOwnerName, 0xa430, DefaultValue::None, d_default,
     unit![],
     "Camera owner name"),
    (BodySerialNumber, 0xa431, DefaultValue::None, d_default,
     unit![],
     "Body serial number"),
    (LensSpecification, 0xa432, DefaultValue::None, d_lensspec,
     unit![],
     "Lens specification"),
    (LensMake, 0xa433, DefaultValue::None, d_default,
     unit![],
     "Lens make"),
    (LensModel, 0xa434, DefaultValue::None, d_default,
     unit![],
     "Lens model"),
    (LensSerialNumber, 0xa435, DefaultValue::None, d_default,
     unit![],
     "Lens serial number"),
    (CompositeImage, 0xa460, DefaultValue::Short(&[0]), d_cpstimg,
     unit![],
     "Composite image"),
    (SourceImageNumberOfCompositeImage, 0xa461, DefaultValue::None, d_numcpstimg,
     unit![],
     "Source image number of composite image"),
    (SourceExposureTimesOfCompositeImage, 0xa462, DefaultValue::None, d_default,
     unit![],
     "Source exposure times of composite image"),
    (Gamma, 0xa500, DefaultValue::None, d_decimal,
     unit![],
     "Gamma"),

    // GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19].
    |Context::Gps|

    // Depends on the Exif version.
    (GPSVersionID, 0x0, DefaultValue::ContextDependent, d_gpsver,
     unit![],
     "GPS tag version"),
    (GPSLatitudeRef, 0x1, DefaultValue::None, d_gpslatlongref,
     unit![],
     "North or south latitude"),
    (GPSLatitude, 0x2, DefaultValue::None, d_gpsdms,
     unit![Tag::GPSLatitudeRef],
     "Latitude"),
    (GPSLongitudeRef, 0x3, DefaultValue::None, d_gpslatlongref,
     unit![],
     "East or West Longitude"),
    (GPSLongitude, 0x4, DefaultValue::None, d_gpsdms,
     unit![Tag::GPSLongitudeRef],
     "Longitude"),
    (GPSAltitudeRef, 0x5, DefaultValue::Byte(&[0]), d_gpsaltref,
     unit![],
     "Altitude reference"),
    (GPSAltitude, 0x6, DefaultValue::None, d_decimal,
     unit![V, " meters ", Tag::GPSAltitudeRef],
     "Altitude"),
    (GPSTimeStamp, 0x7, DefaultValue::None, d_gpstimestamp,
     unit![],
     "GPS time (atomic clock)"),
    (GPSSatellites, 0x8, DefaultValue::None, d_default,
     unit![],
     "GPS satellites used for measurement"),
    (GPSStatus, 0x9, DefaultValue::None, d_gpsstatus,
     unit![],
     "GPS receiver status"),
    (GPSMeasureMode, 0xa, DefaultValue::None, d_gpsmeasuremode,
     unit![],
     "GPS measurement mode"),
    (GPSDOP, 0xb, DefaultValue::None, d_decimal,
     unit![],
     "Measurement precision"),
    (GPSSpeedRef, 0xc, DefaultValue::Ascii(&[b"K"]), d_gpsspeedref,
     unit![],
     "Speed unit"),
    (GPSSpeed, 0xd, DefaultValue::None, d_decimal,
     unit![Tag::GPSSpeedRef],
     "Speed of GPS receiver"),
    (GPSTrackRef, 0xe, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
     unit![],
     "Reference for direction of movement"),
    (GPSTrack, 0xf, DefaultValue::None, d_decimal,
     unit![V, " degrees in ", Tag::GPSTrackRef],
     "Direction of movement"),
    (GPSImgDirectionRef, 0x10, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
     unit![],
     "Reference for direction of image"),
    (GPSImgDirection, 0x11, DefaultValue::None, d_decimal,
     unit![V, " degrees in ", Tag::GPSImgDirectionRef],
     "Direction of image"),
    (GPSMapDatum, 0x12, DefaultValue::None, d_default,
     unit![],
     "Geodetic survey data used"),
    (GPSDestLatitudeRef, 0x13, DefaultValue::None, d_gpslatlongref,
     unit![],
     "Reference for latitude of destination"),
    (GPSDestLatitude, 0x14, DefaultValue::None, d_gpsdms,
     unit![Tag::GPSDestLatitudeRef],
     "Latitude of destination"),
    (GPSDestLongitudeRef, 0x15, DefaultValue::None, d_gpslatlongref,
     unit![],
     "Reference for longitude of destination"),
    (GPSDestLongitude, 0x16, DefaultValue::None, d_gpsdms,
     unit![Tag::GPSDestLongitudeRef],
     "Longitude of destination"),
    (GPSDestBearingRef, 0x17, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
     unit![],
     "Reference for bearing of destination"),
    (GPSDestBearing, 0x18, DefaultValue::None, d_decimal,
     unit![V, " degrees in ", Tag::GPSDestBearingRef],
     "Bearing of destination"),
    (GPSDestDistanceRef, 0x19, DefaultValue::Ascii(&[b"K"]), d_gpsdistref,
     unit![],
     "Reference for distance to destination"),
    (GPSDestDistance, 0x1a, DefaultValue::None, d_decimal,
     unit![Tag::GPSDestDistanceRef],
     "Distance to destination"),
    (GPSProcessingMethod, 0x1b, DefaultValue::None, d_ascii_in_undef,
     unit![],
     "Name of GPS processing method"),
    (GPSAreaInformation, 0x1c, DefaultValue::None, d_default,
     unit![],
     "Name of GPS area"),
    (GPSDateStamp, 0x1d, DefaultValue::None, d_gpsdatestamp,
     unit![],
     "GPS date"),
    (GPSDifferential, 0x1e, DefaultValue::None, d_gpsdifferential,
     unit![],
     "GPS differential correction"),
    (GPSHPositioningError, 0x1f, DefaultValue::None, d_decimal,
     unit!["m"],
     "Horizontal positioning error"),

    // Interoperability attributes [EXIF23 4.6.7 Table 16 and 4.6.8 Table 20]
    // [DCF20 4.4.5.3, 4.5.4.3, and 4.6.4.3].
    |Context::Interop|

    (InteroperabilityIndex, 0x1, DefaultValue::None, d_default,
     unit![],
     "Interoperability identification"),
    (InteroperabilityVersion, 0x2, DefaultValue::None, d_interopver,
     unit![],
     "Interoperability version"),
    (RelatedImageFileFormat, 0x1000, DefaultValue::None, d_default,
     unit![],
     "Related image file format"),
    (RelatedImageWidth, 0x1001, DefaultValue::None, d_default,
     unit!["pixels"],
     "Related image width"),
    (RelatedImageLength, 0x1002, DefaultValue::None, d_default,
     unit!["pixels"],
     "Related image height"),
);

// For Value::display_as().
pub fn display_value_as<'a>(value: &'a Value, tag: Tag) -> value::Display<'a> {
    match get_tag_info(tag) {
        Some(ti) => value::Display { fmt: ti.dispval, value: value },
        None => value::Display { fmt: d_default, value: value },
    }
}

// Compression (TIFF 0x103)
fn d_compression(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "uncompressed",
        Some(2) => "Modified Huffman",
        Some(6) => "JPEG",
        Some(32773) => "PackBits",
        _ => return d_reserved(w, value, "compression"),
    };
    w.write_str(s)
}

// PhotometricInterpretation (TIFF 0x106)
fn d_photointp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "white is zero",
        Some(1) => "black is zero",
        Some(2) => "RGB",
        Some(3) => "palette color",
        Some(4) => "transparency mask",
        Some(6) => "YCbCr",
        _ => return d_reserved(w, value, "photometric interpretation"),
    };
    w.write_str(s)
}

// Orientation (TIFF 0x112)
fn d_orientation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "row 0 at top and column 0 at left",
        Some(2) => "row 0 at top and column 0 at right",
        Some(3) => "row 0 at bottom and column 0 at right",
        Some(4) => "row 0 at bottom and column 0 at left",
        Some(5) => "row 0 at left and column 0 at top",
        Some(6) => "row 0 at right and column 0 at top",
        Some(7) => "row 0 at right and column 0 at bottom",
        Some(8) => "row 0 at left and column 0 at bottom",
        _ => return d_reserved(w, value, "orientation"),
    };
    w.write_str(s)
}

// PlanarConfiguration (TIFF 0x11c)
fn d_planarcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "chunky",
        Some(2) => "planar",
        _ => return d_reserved(w, value, "planar configuration"),
    };
    w.write_str(s)
}

// ResolutionUnit (TIFF 0x128)
// FocalPlaneResolutionUnit (Exif 0xa210)
fn d_resunit(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "no absolute unit",
        Some(2) => "inch",
        Some(3) => "cm",
        _ => return d_reserved(w, value, "resolution unit"),
    };
    w.write_str(s)
}

// DateTime (TIFF 0x132), DateTimeOriginal (Exif 0x9003), and
// DateTimeDigitized (Exif 0x9004)
fn d_datetime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(dt) = value.ascii().and_then(|x| x.first()) {
        match crate::tiff::DateTime::from_ascii(dt) {
            Ok(dt) => return write!(w, "{}", dt),
            Err(Error::BlankValue(_)) => return w.write_str("unknown"),
            _ => {},
        }
    }
    d_default(w, value)
}

// YCbCrSubSampling (TIFF 0x212)
fn d_ycbcrsubsamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    // 0 is used to go to d_default in the match below.
    let horiz = value.get_uint(0).unwrap_or(0);
    let vert = value.get_uint(1).unwrap_or(0);
    let s = match (horiz, vert) {
        (1, 1) => "full horizontally, full vertically (4:4:4)",
        (2, 1) => "half horizontally, full vertically (4:2:2)",
        (2, 2) => "half horizontally, half vertically (4:2:0)",
        (4, 1) => "quarter horizontally, full vertically (4:1:1)",
        (4, 2) => "quarter horizontally, half vertically",
        (4, 4) => "quarter horizontally, quarter vertically",
        _ => return d_default(w, value),
    };
    w.write_str(s)
}

// YCbCrPositioning (TIFF 0x213)
fn d_ycbcrpos(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "centered",
        Some(2) => "co-sited",
        _ => return d_reserved(w, value, "YCbCr positioning"),
    };
    w.write_str(s)
}

// ExposureTime (Exif 0x829a)
fn d_exptime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(et) = value.rational().and_then(|x| x.first()) {
        if et.num >= et.denom {
            return write!(w, "{}", et.to_f64());
        } else if et.num != 0 {
            return write!(w, "1/{}", et.denom as f64 / et.num as f64);
        }
    }
    d_default(w, value)
}

// ExposureProgram (Exif 0x8822)
fn d_expprog(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "not defined",
        Some(1) => "manual",
        Some(2) => "normal program",
        Some(3) => "aperture priority",
        Some(4) => "shutter priority",
        Some(5) => "creative program",
        Some(6) => "action program",
        Some(7) => "portrait mode",
        Some(8) => "landscape mode",
        _ => return d_reserved(w, value, "exposure program"),
    };
    w.write_str(s)
}

// SensitivityType (Exif 0x8830)
fn d_sensitivitytype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "unknown",
        Some(1) => "SOS",
        Some(2) => "REI",
        Some(3) => "ISO speed",
        Some(4) => "SOS/REI",
        Some(5) => "SOS/ISO speed",
        Some(6) => "REI/ISO speed",
        Some(7) => "SOS/REI/ISO speed",
        _ => return d_reserved(w, value, "sensitivity type"),
    };
    w.write_str(s)
}

// ExifVersion (Exif 0x9000), FlashpixVersion (Exif 0xa000)
fn d_exifver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(s) = value.undefined().filter(|s| s.len() == 4) {
        if let Ok(major) = atou16(&s[0..2]) {
            if let Ok(minor) = atou16(&s[2..4]) {
                if minor % 10 == 0 {
                    return write!(w, "{}.{}", major, minor / 10);
                } else {
                    return write!(w, "{}.{:02}", major, minor);
                }
            }
        }
    }
    d_default(w, value)
}

// ComponentsConfiguration (Exif 0x9101)
fn d_cpntcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.undefined() {
        Some(s) => s.iter().try_for_each(|x| match x {
            0 => w.write_char('_'),
            1 => w.write_char('Y'),
            2 => w.write_str("Cb"),
            3 => w.write_str("Cr"),
            4 => w.write_char('R'),
            5 => w.write_char('G'),
            6 => w.write_char('B'),
            _ => w.write_char('?'),
        }),
        None => d_default(w, value),
    }
}

// SubjectDistance (Exif 0x9206)
fn d_subjdist(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(dist) = value.rational().and_then(|x| x.first()) {
        if dist.num == 0 {
            return w.write_str("unknown");
        } else if dist.num == 0xffffffff {
            return w.write_str("infinity");
        }
    }
    d_decimal(w, value)
}

// MeteringMode (Exif 0x9207)
fn d_metering(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "unknown",
        Some(1) => "average",
        Some(2) => "center-weighted average",
        Some(3) => "spot",
        Some(4) => "multi-spot",
        Some(5) => "pattern",
        Some(6) => "partial",
        Some(255) => "other",
        _ => return d_reserved(w, value, "metering mode"),
    };
    w.write_str(s)
}

// LightSource (Exif 0x9208)
fn d_lightsrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "unknown",
        Some(1) => "daylight",
        Some(2) => "fluorescent",
        Some(3) => "tungsten",
        Some(4) => "flash",
        Some(9) => "fine weather",
        Some(10) => "cloudy weather",
        Some(11) => "shade",
        Some(12) => "daylight fluorescent (D 5700-7100K)",
        Some(13) => "day white fluorescent (N 4600-5500K)",
        Some(14) => "cool white fluorescent (W 3800-4500K)",
        Some(15) => "white fluorescent (WW 3250-3800K)",
        Some(16) => "warm white fluorescent (L 2600-3250K)",
        Some(17) => "standard light A",
        Some(18) => "standard light B",
        Some(19) => "standard light C",
        Some(20) => "D55",
        Some(21) => "D65",
        Some(22) => "D75",
        Some(23) => "D50",
        Some(24) => "ISO studio tungsten",
        Some(255) => "other",
        _ => return d_reserved(w, value, "light source"),
    };
    w.write_str(s)
}

// Flash (Exif 0x9209)
fn d_flash(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    const FIRED: &[&str] = &["not fired", "fired"];
    const RETURN: &[&str] = &[
        ", no return light detection function",
        ", return light status 1 (reserved)",
        ", return light not detected",
        ", return light detected",
    ];
    const AUTO: &[&str] = &[
        ", auto mode 0 (unknown)", ", forced", ", suppressed", ", auto"];
    const FUNCTION: &[&str] = &["", ", no function present"];
    const RED_EYE: &[&str] = &["", ", red-eye reduction"];

    if let Some(v) = value.get_uint(0) {
        write!(w, "{}{}{}{}{}{}",
               FIRED[v as usize & 1],
               RETURN[v as usize >> 1 & 3],
               AUTO[v as usize >> 3 & 3],
               FUNCTION[v as usize >> 5 & 1],
               RED_EYE[v as usize >> 6 & 1],
               if v >> 7 != 0 { ", unknown MSB bits" } else { "" })
    } else {
        d_default(w, value)
    }
}

// SubjectArea (Exif 0x9214), SubjectLocation (Exif 0xa214)
// Only (x, y) case is valid for SubjectLocation.
fn d_subjarea(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(x) = value.get_uint(0) {
        if let Some(y) = value.get_uint(1) {
            if let Some(d) = value.get_uint(2) {
                if let Some(h) = value.get_uint(3) {
                    return write!(w, "rectangle (x={}, y={}, w={}, h={})",
                                  x, y, d, h);
                }
                return write!(w, "circle (x={}, y={}, d={})", x, y, d);
            }
            return write!(w, "point (x={}, y={})", x, y);
        }
    }
    d_default(w, value)
}

// Rational/SRational with 0xffffffff being unknown.
// BrightnessValue (Exif 0x9203),
// Temperature (Exif 0x9400), Humidity (Exif 0x9401),
// Pressure (Exif 0x9402), WaterDepth (Exif 0x9403),
// Acceleration (Exif 0x9404), CameraElevationAngle (Exif 0x9405)
fn d_optdecimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match *value {
        Value::Rational(ref v) if v.len() > 0 =>
            if v[0].denom != 0xffffffff {
                write!(w, "{}", v[0].to_f64())
            } else {
                w.write_str("unknown")
            },
        Value::SRational(ref v) if v.len() > 0 =>
            if v[0].denom != -1 {
                write!(w, "{}", v[0].to_f64())
            } else {
                w.write_str("unknown")
            },
        _ => d_decimal(w, value),
    }
}

// ColorSpace (Exif 0xa001)
fn d_cspace(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "sRGB",
        Some(0xffff) => "uncalibrated",
        _ => return d_reserved(w, value, "color space"),
    };
    w.write_str(s)
}

// SensingMethod (Exif 0xa217)
fn d_sensingmethod(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(1) => "not defined",
        Some(2) => "one-chip color area sensor",
        Some(3) => "two-chip color area sensor",
        Some(4) => "three-chip color area sensor",
        Some(5) => "color sequential area sensor",
        Some(7) => "trilinear sensor",
        Some(8) => "color sequential linear sensor",
        _ => return d_reserved(w, value, "sensing method"),
    };
    w.write_str(s)
}

// FileSource (Exif 0xa300)
fn d_filesrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.undefined().and_then(|x| x.first().copied()) {
        Some(0) => "others",
        Some(1) => "transparency scanner",
        Some(2) => "reflective scanner",
        Some(3) => "digital still camera",
        _ => return d_reserved(w, value, "file source"),
    };
    w.write_str(s)
}

// SceneType (Exif 0xa301)
fn d_scenetype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.undefined().and_then(|x| x.first().copied()) {
        Some(1) => "directly photographed image",
        _ => return d_reserved(w, value, "scene type"),
    };
    w.write_str(s)
}

// CustomRendered (Exif 0xa401)
fn d_customrendered(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "normal process",
        Some(1) => "custom process",
        _ => return d_reserved(w, value, "custom rendered"),
    };
    w.write_str(s)
}

// ExposureMode (Exif 0xa402)
fn d_expmode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "auto exposure",
        Some(1) => "manual exposure",
        Some(2) => "auto bracket",
        _ => return d_reserved(w, value, "exposure mode"),
    };
    w.write_str(s)
}

// WhiteBalance (Exif 0xa403)
fn d_whitebalance(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "auto white balance",
        Some(1) => "manual white balance",
        _ => return d_reserved(w, value, "white balance mode"),
    };
    w.write_str(s)
}

// DigitalZoomRatio (Exif 0xa404)
fn d_dzoomratio(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if value.rational().and_then(|x| x.first()).map(|x| x.num) == Some(0) {
        return w.write_str("unused");
    }
    d_decimal(w, value)
}

// FocalLengthIn35mmFilm (Exif 0xa405)
fn d_focallen35(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.get_uint(0) {
        Some(0) => w.write_str("unknown"),
        _ => d_default(w, value),
    }
}

// SceneCaptureType (Exif 0xa406)
fn d_scenecaptype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "standard",
        Some(1) => "landscape",
        Some(2) => "portrait",
        Some(3) => "night scene",
        _ => return d_reserved(w, value, "scene capture type"),
    };
    w.write_str(s)
}

// GainControl (Exif 0xa407)
fn d_gainctrl(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "none",
        Some(1) => "low gain up",
        Some(2) => "high gain up",
        Some(3) => "low gain down",
        Some(4) => "high gain down",
        _ => return d_reserved(w, value, "gain control"),
    };
    w.write_str(s)
}

// Contrast (Exif 0xa408)
fn d_contrast(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "normal",
        Some(1) => "soft",
        Some(2) => "hard",
        _ => return d_reserved(w, value, "contrast processing"),
    };
    w.write_str(s)
}

// Saturation (Exif 0xa409)
fn d_saturation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "normal",
        Some(1) => "low saturation",
        Some(2) => "high saturation",
        _ => return d_reserved(w, value, "saturation processing"),
    };
    w.write_str(s)
}

// Sharpness (Exif 0xa40a)
fn d_sharpness(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "normal",
        Some(1) => "soft",
        Some(2) => "hard",
        _ => return d_reserved(w, value, "sharpness processing"),
    };
    w.write_str(s)
}

// SubjectDistanceRange (Exif 0xa40c)
fn d_subjdistrange(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "unknown",
        Some(1) => "macro",
        Some(2) => "close view",
        Some(3) => "distant view",
        _ => return d_reserved(w, value, "subject distance range"),
    };
    w.write_str(s)
}

// LensSpecification (Exif 0xa432)
fn d_lensspec(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.rational().and_then(|x| x.get(..4)) {
        // There are several notations: "F1.4" in Japan, "f/1.4"
        // in the U.S., and so on.
        Some(s) => write!(w, "{}-{} mm, f/{}-{}",
                          s[0].to_f64(), s[1].to_f64(),
                          s[2].to_f64(), s[3].to_f64()),
        _ => d_default(w, value),
    }
}

// CompositeImage (Exif 0xa460)
fn d_cpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "unknown",
        Some(1) => "non-composite",
        Some(2) => "composite (general)",
        Some(3) => "composite (at the moment of shooting)",
        _ => return d_reserved(w, value, "composite image"),
    };
    w.write_str(s)
}

// SourceImageNumberOfCompositeImage (Exif 0xa461)
fn d_numcpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match (value.get_uint(0), value.get_uint(1)) {
        (Some(t), Some(u)) => write!(w, "total {}, used {}", t, u),
        _ => d_default(w, value),
    }
}

// GPSVersionID (GPS 0x0)
fn d_gpsver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.byte().and_then(|x| x.get(..4)) {
        Some(s) => write!(w, "{}.{}.{}.{}", s[0], s[1], s[2], s[3]),
        _ => d_default(w, value),
    }
}

// GPSLatitudeRef (GPS 0x1), GPSLongitudeRef (GPS 0x3)
// GPSDestLatitudeRef (GPS 0x13), GPSDestLongitudeRef (GPS 0x15)
fn d_gpslatlongref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.ascii().and_then(|x| x.first()) {
        Some([c]) if c.is_ascii_uppercase() => w.write_char(*c as char),
        _ => d_default(w, value),
    }
}

// GPSLatitude (GPS 0x2), GPSLongitude (GPS 0x4),
// GPSDestLatitude (GPS 0x14), GPSDestLongitude (GPS 0x16)
fn d_gpsdms(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.rational().and_then(|x| x.get(..3)) {
        Some(s) => write!(w, "{} deg {} min {} sec",
                          s[0].to_f64(), s[1].to_f64(), s[2].to_f64()),
        _ => d_default(w, value),
    }
}

// GPSAltitudeRef (GPS 0x5)
fn d_gpsaltref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "above sea level",
        Some(1) => "below sea level",
        _ => return d_reserved(w, value, "GPS altitude ref"),
    };
    w.write_str(s)
}

// GPSTimeStamp (GPS 0x7)
fn d_gpstimestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match value.rational().and_then(|x| x.get(..3)) {
        Some(s) => {
            let (h, m, s) = (s[0].to_f64(), s[1].to_f64(), s[2].to_f64());
            write!(w, "{}{}:{}{}:{}{}",
                   if h < 10.0 { "0" } else { "" }, h,
                   if m < 10.0 { "0" } else { "" }, m,
                   if s < 10.0 { "0" } else { "" }, s)
        },
        _ => d_default(w, value),
    }
}

// GPSStatus (GPS 0x9)
fn d_gpsstatus(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.ascii().and_then(|x| x.first()) {
        Some(b"A") => "measurement in progress",
        Some(b"V") => "measurement interrupted",
        _ => return d_reserved(w, value, "GPS status"),
    };
    w.write_str(s)
}

// GPSMeasureMode (GPS 0xa)
fn d_gpsmeasuremode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.ascii().and_then(|x| x.first()) {
        Some(b"2") => "2-dimensional measurement",
        Some(b"3") => "3-dimensional measurement",
        _ => return d_reserved(w, value, "GPS measurement mode"),
    };
    w.write_str(s)
}

// GPSSpeedRef (GPS 0xc)
fn d_gpsspeedref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.ascii().and_then(|x| x.first()) {
        Some(b"K") => "km/h",
        Some(b"M") => "mph",
        Some(b"N") => "knots",
        _ => return d_reserved(w, value, "GPS speed ref"),
    };
    w.write_str(s)
}

// GPSTrackRef (GPS 0xe), GPSImgDirectionRef (GPS 0x10),
// GPSDestBearingRef (GPS 0x17)
fn d_gpsdirref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.ascii().and_then(|x| x.first()) {
        Some(b"T") => "true direction",
        Some(b"M") => "magnetic direction",
        _ => return d_reserved(w, value, "GPS direction ref"),
    };
    w.write_str(s)
}

// GPSDestDistanceRef (GPS 0x19)
fn d_gpsdistref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.ascii().and_then(|x| x.first()) {
        Some(b"K") => "km",
        Some(b"M") => "miles",
        Some(b"N") => "nautical miles",
        _ => return d_reserved(w, value, "GPS distance ref"),
    };
    w.write_str(s)
}

// GPSDateStamp (GPS 0x1d)
fn d_gpsdatestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(data) = value.ascii().and_then(|x| x.first()) {
        if data.len() >= 10 && data[4] == b':' && data[7] == b':' {
            if let Ok(year) = atou16(&data[0..4]) {
                if let Ok(month) = atou16(&data[5..7]) {
                    if let Ok(day) = atou16(&data[8..10]) {
                        return write!(w, "{:04}-{:02}-{:02}",
                                      year, month, day);
                    }
                }
            }
        }
    }
    d_default(w, value)
}

// GPSDifferential (GPS 0x1e)
fn d_gpsdifferential(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    let s = match value.get_uint(0) {
        Some(0) => "no differential correction",
        Some(1) => "differential correction applied",
        _ => return d_reserved(w, value, "GPS differential correction"),
    };
    w.write_str(s)
}

// InteroperabilityVersion (Interoperability 0x2)
fn d_interopver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    if let Some(s) = value.undefined().filter(|s| s.len() == 4) {
        if let Ok(major) = atou16(&s[0..2]) {
            if let Ok(minor) = atou16(&s[2..4]) {
                return write!(w, "{}.{:02}", major, minor);
            }
        }
    }
    d_default(w, value)
}

fn d_ascii_in_undef(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match *value {
        Value::Undefined(ref v, _) => d_sub_ascii(w, v),
        _ => d_default(w, value),
    }
}

fn d_decimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match *value {
        Value::Rational(ref v) =>
            d_sub_comma(w, v.iter().map(|x| x.to_f64())),
        Value::SRational(ref v) =>
            d_sub_comma(w, v.iter().map(|x| x.to_f64())),
        _ => d_default(w, value),
    }
}

#[inline(never)]
fn d_reserved(w: &mut dyn fmt::Write, value: &Value, name: &str)
              -> fmt::Result {
    write!(w, "[reserved {} ", name)?;
    d_default(w, value)?;
    w.write_char(']')
}

fn d_default(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
    match *value {
        Value::Byte(ref v) => d_sub_comma(w, v),
        Value::Ascii(ref v) =>
            d_sub_comma(w, v.iter().map(|x| AsciiDisplay(x))),
        Value::Short(ref v) => d_sub_comma(w, v),
        Value::Long(ref v) => d_sub_comma(w, v),
        Value::Rational(ref v) => d_sub_comma(w, v),
        Value::SByte(ref v) => d_sub_comma(w, v),
        Value::Undefined(ref v, _) => d_sub_hex(w, v),
        Value::SShort(ref v) => d_sub_comma(w, v),
        Value::SLong(ref v) => d_sub_comma(w, v),
        Value::SRational(ref v) => d_sub_comma(w, v),
        Value::Float(ref v) => d_sub_comma(w, v),
        Value::Double(ref v) => d_sub_comma(w, v),
        Value::Unknown(t, c, o) =>
            write!(w, "unknown value (type={}, count={}, offset={:#x})",
                   t, c, o),
    }
}

fn d_sub_comma<I, T>(w: &mut dyn fmt::Write, itit: I) -> fmt::Result
where I: IntoIterator<Item = T>, T: fmt::Display {
    let mut first = true;
    for x in itit {
        match first {
            true => write!(w, "{}", x),
            false => write!(w, ", {}", x),
        }?;
        first = false;
    }
    Ok(())
}

struct AsciiDisplay<'a>(&'a [u8]);

impl<'a> fmt::Display for AsciiDisplay<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        d_sub_ascii(f, self.0)
    }
}

fn d_sub_hex(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result {
    w.write_str("0x")?;
    for x in bytes {
        write!(w, "{:02x}", x)?;
    }
    Ok(())
}

fn d_sub_ascii(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result {
    w.write_char('"')?;
    for &c in bytes {
        match c {
            b'\\' | b'"' => {
                w.write_char('\\')?;
                w.write_char(c as char)?;
            },
            0x20..=0x7e => w.write_char(c as char)?,
            _ => write!(w, "\\x{:02x}", c)?,
        }
    }
    w.write_char('"')
}

#[cfg(test)]
mod tests {
    use value::Rational;
    use super::*;

    // This test checks if Tag constants can be used in patterns.
    #[test]
    fn tag_constant_in_pattern() {
        // Destructuring, which will always work.
        match Tag(Context::Tiff, 0x132) {
            Tag(Context::Tiff, 0x132) => {},
            _ => panic!("failed to match Tag"),
        }
        // Matching against a constant.  Test if this compiles.
        match Tag(Context::Tiff, 0x132) {
            Tag::DateTime => {},
            _ => panic!("failed to match Tag"),
        }
    }

    #[test]
    fn default_value() {
        assert_pat!(Tag::DateTime.default_value(), None);
        match Tag::BitsPerSample.default_value() {
            Some(Value::Short(v)) => assert_eq!(v, &[8, 8, 8]),
            _ => panic!(),
        }
        match Tag::XResolution.default_value() {
            Some(Value::Rational(v)) => {
                assert_eq!(v.len(), 1);
                assert_eq!(v[0].num, 72);
                assert_eq!(v[0].denom, 1);
            },
            _ => panic!(),
        }
        match Tag::FileSource.default_value() {
            Some(Value::Undefined(v, _)) => assert_eq!(v, &[3]),
            _ => panic!(),
        }
        match Tag::GPSAltitudeRef.default_value() {
            Some(Value::Byte(v)) => assert_eq!(v, &[0]),
            _ => panic!(),
        }
        match Tag::GPSSpeedRef.default_value() {
            Some(Value::Ascii(v)) => assert_eq!(v, &[b"K"]),
            _ => panic!(),
        }
    }

    #[test]
    fn tag_fmt_display() {
        let tag1 = Tag(Context::Tiff, 0x132);
        assert_eq!(format!("{:15}", tag1), "DateTime       ");
        assert_eq!(format!("{:>15}", tag1), "       DateTime");
        assert_eq!(format!("{:5.6}", tag1), "DateTi");
        let tag2 = Tag(Context::Exif, 0);
        assert_eq!(format!("{:15}", tag2), "Tag(Exif, 0)   ");
        assert_eq!(format!("{:>15}", tag2), "   Tag(Exif, 0)");
        assert_eq!(format!("{:5.6}", tag2), "Tag(Ex");
    }

    #[test]
    fn disp_val_sub() {
        let mut buf = String::new();
        d_sub_comma(&mut buf, &[0u16, 1, 2]).unwrap();
        assert_eq!(buf, "0, 1, 2");

        let mut buf = String::new();
        d_sub_comma(&mut buf, &[Rational::from((3, 5))]).unwrap();
        assert_eq!(buf, "3/5");

        let mut buf = String::new();
        let list = &[Rational::from((1, 2))];
        d_sub_comma(&mut buf, list.iter().map(|x| x.to_f64())).unwrap();
        assert_eq!(buf, "0.5");

        let mut buf = String::new();
        d_sub_hex(&mut buf, b"abc\x00\xff").unwrap();
        assert_eq!(buf, "0x61626300ff");

        let mut buf = String::new();
        d_sub_ascii(&mut buf, b"a \"\\b\"\n").unwrap();
        assert_eq!(buf, r#""a \"\\b\"\x0a""#);
    }
}
