use std::fmt;

/// Types of icon elements that can be decoded as images or masks.
///
/// This type enumerates the kinds of [`IconElement`](struct.IconElement.html)
/// that can be decoded by this library; each `IconType` corresponds to a
/// particular [`OSType`](struct.OSType.html).  The non-mask `IconType` values
/// can also be used with the higher-level
/// [`IconFamily`](struct.IconFamily.html) methods to
/// [encode](struct.IconFamily.html#method.add_icon_with_type) and
/// [decode](struct.IconFamily.html#method.get_icon_with_type) complete icons
/// that consist of multiple `IconElements`.
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum IconType {
    /// 32x32 1-bit icon (without alpha)
    Mono_32x32,
    /// 32x32 1-bit icon with alpha
    MonoA_32x32,
    /// 16x12 1-bit icon with alpha
    MonoA_16x12,
    /// 16x12 4-bit icon (without alpha)
    Palette4_16x12,
    /// 16x12 8-bit icon (without alpha)
    Palette8_16x12,
    /// 16x16 1-bit icon with alpha
    MonoA_16x16,
    /// 16x16 4-bit icon (without alpha)
    Palette4_16x16,
    /// 16x16 8-bit icon (without alpha)
    Palette8_16x16,
    /// 16x16 24-bit icon (without alpha).
    RGB24_16x16,
    /// 16x16 8-bit alpha mask.
    Mask8_16x16,
    /// 32x32 4-bit icon (without alpha)
    Palette4_32x32,
    /// 32x32 8-bit icon (without alpha)
    Palette8_32x32,
    /// 32x32 24-bit icon (without alpha).
    RGB24_32x32,
    /// 32x32 8-bit alpha mask.
    Mask8_32x32,
    /// 48x48 1-bit icon with alpha
    MonoA_48x48,
    /// 48x48 8-bit icon (without alpha)
    Palette4_48x48,
    /// 48x48 8-bit icon (without alpha)
    Palette8_48x48,
    /// 48x48 24-bit icon (without alpha).
    RGB24_48x48,
    /// 48x48 8-bit alpha mask.
    Mask8_48x48,
    /// 128x128 24-bit icon (without alpha).
    RGB24_128x128,
    /// 128x128 8-bit alpha mask.
    Mask8_128x128,
    /// 16x16 32-bit icon.
    RGBA32_16x16,
    /// 16x16 32-bit icon at 2x "retina" density (so, 32 by 32 pixels).
    RGBA32_16x16_2x,
    /// 32x32 32-bit icon.
    RGBA32_32x32,
    /// 32x32 32-bit icon at 2x "retina" density (so, 64 by 64 pixels).
    RGBA32_32x32_2x,
    /// 64x64 32-bit icon.  (For whatever reason, the ICNS format has no
    /// corresponding type for a 64x64 icon at 2x "retina" density.)
    RGBA32_64x64,
    /// 128x128 32-bit icon.
    RGBA32_128x128,
    /// 128x128 32-bit icon at 2x "retina" density (so, 256 by 256 pixels).
    RGBA32_128x128_2x,
    /// 256x256 32-bit icon.
    RGBA32_256x256,
    /// 256x256 32-bit icon at 2x "retina" density (so, 512 by 512 pixels).
    RGBA32_256x256_2x,
    /// 512x512 32-bit icon.
    RGBA32_512x512,
    /// 512x512 32-bit icon at 2x "retina" density (so, 1024 by 1024 pixels).
    RGBA32_512x512_2x,
}

impl IconType {
    /// Get the icon type associated with the given OSType, if any.
    pub fn from_ostype(ostype: OSType) -> Option<IconType> {
        let OSType(raw_ostype) = ostype;
        match &raw_ostype {
            b"ICON" => Some(IconType::Mono_32x32),
            b"ICN#" => Some(IconType::MonoA_32x32),
            b"icm#" => Some(IconType::MonoA_16x12),
            b"icm4" => Some(IconType::Palette4_16x12),
            b"icm8" => Some(IconType::Palette8_16x12),
            b"ics#" => Some(IconType::MonoA_16x16),
            b"ics4" => Some(IconType::Palette4_16x16),
            b"ics8" => Some(IconType::Palette8_16x16),
            b"is32" => Some(IconType::RGB24_16x16),
            b"s8mk" => Some(IconType::Mask8_16x16),
            b"icl4" => Some(IconType::Palette4_32x32),
            b"icl8" => Some(IconType::Palette8_32x32),
            b"il32" => Some(IconType::RGB24_32x32),
            b"l8mk" => Some(IconType::Mask8_32x32),
            b"ich#" => Some(IconType::MonoA_48x48),
            b"ich4" => Some(IconType::Palette4_48x48),
            b"ich8" => Some(IconType::Palette8_48x48),
            b"ih32" => Some(IconType::RGB24_48x48),
            b"h8mk" => Some(IconType::Mask8_48x48),
            b"it32" => Some(IconType::RGB24_128x128),
            b"t8mk" => Some(IconType::Mask8_128x128),
            b"icp4" => Some(IconType::RGBA32_16x16),
            b"ic11" => Some(IconType::RGBA32_16x16_2x),
            b"icp5" => Some(IconType::RGBA32_32x32),
            b"ic12" => Some(IconType::RGBA32_32x32_2x),
            b"icp6" => Some(IconType::RGBA32_64x64),
            b"ic07" => Some(IconType::RGBA32_128x128),
            b"ic13" => Some(IconType::RGBA32_128x128_2x),
            b"ic08" => Some(IconType::RGBA32_256x256),
            b"ic14" => Some(IconType::RGBA32_256x256_2x),
            b"ic09" => Some(IconType::RGBA32_512x512),
            b"ic10" => Some(IconType::RGBA32_512x512_2x),
            _ => None,
        }
    }

    /// Return a (non-mask) icon type that has the given pixel width/height, if
    /// any.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::from_pixel_size(48, 48),
    ///            Some(IconType::RGB24_48x48));
    /// assert_eq!(IconType::from_pixel_size(256, 256),
    ///            Some(IconType::RGBA32_256x256));
    /// assert_eq!(IconType::from_pixel_size(1024, 1024),
    ///            Some(IconType::RGBA32_512x512_2x));
    /// ```
    pub fn from_pixel_size(width: u32, height: u32) -> Option<IconType> {
        match (width, height) {
            (16, 12) => Some(IconType::MonoA_16x12),
            (16, 16) => Some(IconType::RGB24_16x16),
            (32, 32) => Some(IconType::RGB24_32x32),
            (48, 48) => Some(IconType::RGB24_48x48),
            (64, 64) => Some(IconType::RGBA32_64x64),
            (128, 128) => Some(IconType::RGB24_128x128),
            (256, 256) => Some(IconType::RGBA32_256x256),
            (512, 512) => Some(IconType::RGBA32_512x512),
            (1024, 1024) => Some(IconType::RGBA32_512x512_2x),
            _ => None,
        }
    }

    /// Return a (non-mask) icon type that has the given pixel width/height and
    /// pixel density, if any.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::from_pixel_size_and_density(48, 48, 1),
    ///            Some(IconType::RGB24_48x48));
    /// assert_eq!(IconType::from_pixel_size_and_density(256, 256, 1),
    ///            Some(IconType::RGBA32_256x256));
    /// assert_eq!(IconType::from_pixel_size_and_density(256, 256, 2),
    ///            Some(IconType::RGBA32_128x128_2x));
    /// ```
    pub fn from_pixel_size_and_density(width: u32,
                                       height: u32,
                                       density: u32)
                                       -> Option<IconType> {
        match (width, height, density) {
            (16, 12, 1) => Some(IconType::MonoA_16x12),
            (16, 16, 1) => Some(IconType::RGB24_16x16),
            (32, 32, 1) => Some(IconType::RGB24_32x32),
            (32, 32, 2) => Some(IconType::RGBA32_16x16_2x),
            (48, 48, 1) => Some(IconType::RGB24_48x48),
            (64, 64, 1) => Some(IconType::RGBA32_64x64),
            (64, 64, 2) => Some(IconType::RGBA32_32x32_2x),
            (128, 128, 1) => Some(IconType::RGB24_128x128),
            (256, 256, 1) => Some(IconType::RGBA32_256x256),
            (256, 256, 2) => Some(IconType::RGBA32_128x128_2x),
            (512, 512, 1) => Some(IconType::RGBA32_512x512),
            (512, 512, 2) => Some(IconType::RGBA32_256x256_2x),
            (1024, 1024, 2) => Some(IconType::RGBA32_512x512_2x),
            _ => None,
        }
    }

    /// Get the OSType that represents this icon type.
    pub fn ostype(self) -> OSType {
        match self {
            IconType::Mono_32x32 => OSType(*b"ICON"),
            IconType::MonoA_32x32 => OSType(*b"ICN#"),
            IconType::MonoA_16x12 => OSType(*b"icm#"),
            IconType::Palette4_16x12 => OSType(*b"icm4"),
            IconType::Palette8_16x12 => OSType(*b"icm8"),
            IconType::MonoA_16x16 => OSType(*b"ics#"),
            IconType::Palette4_16x16 => OSType(*b"ics4"),
            IconType::Palette8_16x16 => OSType(*b"ics8"),
            IconType::RGB24_16x16 => OSType(*b"is32"),
            IconType::Mask8_16x16 => OSType(*b"s8mk"),
            IconType::Palette4_32x32 => OSType(*b"icl4"),
            IconType::Palette8_32x32 => OSType(*b"icl8"),
            IconType::RGB24_32x32 => OSType(*b"il32"),
            IconType::Mask8_32x32 => OSType(*b"l8mk"),
            IconType::MonoA_48x48 => OSType(*b"ich#"),
            IconType::Palette4_48x48 => OSType(*b"ich4"),
            IconType::Palette8_48x48 => OSType(*b"ich8"),
            IconType::RGB24_48x48 => OSType(*b"ih32"),
            IconType::Mask8_48x48 => OSType(*b"h8mk"),
            IconType::RGB24_128x128 => OSType(*b"it32"),
            IconType::Mask8_128x128 => OSType(*b"t8mk"),
            IconType::RGBA32_16x16 => OSType(*b"icp4"),
            IconType::RGBA32_16x16_2x => OSType(*b"ic11"),
            IconType::RGBA32_32x32 => OSType(*b"icp5"),
            IconType::RGBA32_32x32_2x => OSType(*b"ic12"),
            IconType::RGBA32_64x64 => OSType(*b"icp6"),
            IconType::RGBA32_128x128 => OSType(*b"ic07"),
            IconType::RGBA32_128x128_2x => OSType(*b"ic13"),
            IconType::RGBA32_256x256 => OSType(*b"ic08"),
            IconType::RGBA32_256x256_2x => OSType(*b"ic14"),
            IconType::RGBA32_512x512 => OSType(*b"ic09"),
            IconType::RGBA32_512x512_2x => OSType(*b"ic10"),
        }
    }

    /// Returns true if this is icon type is a mask for some other icon type.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert!(!IconType::RGB24_16x16.is_mask());
    /// assert!(IconType::Mask8_16x16.is_mask());
    /// assert!(!IconType::RGBA32_16x16.is_mask());
    /// ```
    pub fn is_mask(self) -> bool {
        matches!(self,
                 IconType::MonoA_16x12 |
                 IconType::MonoA_16x16 |
                 IconType::MonoA_32x32 |
                 IconType::MonoA_48x48 |
                 IconType::Mask8_16x16 |
                 IconType::Mask8_32x32 |
                 IconType::Mask8_48x48 |
                 IconType::Mask8_128x128)
    }

    /// If this icon type has an associated mask type, returns that mask type;
    /// if this is a mask icon type, or a non-mask icon type that has no
    /// associated mask type, returns `None`.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::RGB24_16x16.mask_type(),
    ///            Some(IconType::Mask8_16x16));
    /// assert_eq!(IconType::Mask8_16x16.mask_type(), None);
    /// assert_eq!(IconType::RGBA32_16x16.mask_type(), None);
    /// ```
    pub fn mask_type(self) -> Option<IconType> {
        match self {
            IconType::Palette4_16x12 | IconType::Palette8_16x12 => {
                Some(IconType::MonoA_16x12)
            }
            IconType::Palette4_16x16 | IconType::Palette8_16x16 => {
                Some(IconType::MonoA_16x16)
            }
            IconType::RGB24_16x16 => Some(IconType::Mask8_16x16),
            IconType::Palette4_32x32 | IconType::Palette8_32x32 => {
                Some(IconType::MonoA_32x32)
            }
            IconType::RGB24_32x32 => Some(IconType::Mask8_32x32),
            IconType::Palette4_48x48 | IconType::Palette8_48x48 => {
                Some(IconType::MonoA_48x48)
            }
            IconType::RGB24_48x48 => Some(IconType::Mask8_48x48),
            IconType::RGB24_128x128 => Some(IconType::Mask8_128x128),
            _ => None,
        }
    }

    /// Returns the pixel data width of this icon type.  Normally this is the
    /// same as the screen width, but for 2x "retina" density icons, this will
    /// be twice that value.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::Mask8_128x128.pixel_width(), 128);
    /// assert_eq!(IconType::RGBA32_256x256.pixel_width(), 256);
    /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_width(), 512);
    /// ```
    pub fn pixel_width(self) -> u32 {
        self.screen_width() * self.pixel_density()
    }

    /// Returns the pixel data height of this icon type.  Normally this is the
    /// same as the screen height, but for 2x "retina" density icons, this will
    /// be twice that value.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::Mask8_128x128.pixel_height(), 128);
    /// assert_eq!(IconType::RGBA32_256x256.pixel_height(), 256);
    /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_height(), 512);
    /// ```
    pub fn pixel_height(self) -> u32 {
        self.screen_height() * self.pixel_density()
    }

    /// Returns the pixel density for this icon type -- that is, 2 for 2x
    /// "retina" density icons, or 1 for other icon types.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::Mask8_128x128.pixel_density(), 1);
    /// assert_eq!(IconType::RGBA32_256x256.pixel_density(), 1);
    /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_density(), 2);
    /// ```
    pub fn pixel_density(self) -> u32 {
        match self {
            IconType::RGBA32_16x16_2x |
            IconType::RGBA32_32x32_2x |
            IconType::RGBA32_128x128_2x |
            IconType::RGBA32_256x256_2x |
            IconType::RGBA32_512x512_2x => 2,
            _ => 1,
        }
    }

    /// Returns the screen width of this icon type.  Normally this is the same
    /// as the pixel width, but for 2x "retina" density icons, this will be
    /// half that value.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::Mask8_128x128.screen_width(), 128);
    /// assert_eq!(IconType::RGBA32_256x256.screen_width(), 256);
    /// assert_eq!(IconType::RGBA32_256x256_2x.screen_width(), 256);
    /// ```
    pub fn screen_width(self) -> u32 {
        match self {
            IconType::Mono_32x32 => 32,
            IconType::MonoA_32x32 => 32,
            IconType::MonoA_16x12 => 16,
            IconType::Palette4_16x12 => 16,
            IconType::Palette8_16x12 => 16,
            IconType::MonoA_16x16 => 16,
            IconType::Palette4_16x16 => 16,
            IconType::Palette8_16x16 => 16,
            IconType::RGB24_16x16 => 16,
            IconType::Mask8_16x16 => 16,
            IconType::Palette4_32x32 => 32,
            IconType::Palette8_32x32 => 32,
            IconType::RGB24_32x32 => 32,
            IconType::Mask8_32x32 => 32,
            IconType::MonoA_48x48 => 48,
            IconType::Palette4_48x48 => 48,
            IconType::Palette8_48x48 => 48,
            IconType::RGB24_48x48 => 48,
            IconType::Mask8_48x48 => 48,
            IconType::RGB24_128x128 => 128,
            IconType::Mask8_128x128 => 128,
            IconType::RGBA32_16x16 => 16,
            IconType::RGBA32_16x16_2x => 16,
            IconType::RGBA32_32x32 => 32,
            IconType::RGBA32_32x32_2x => 32,
            IconType::RGBA32_64x64 => 64,
            IconType::RGBA32_128x128 => 128,
            IconType::RGBA32_128x128_2x => 128,
            IconType::RGBA32_256x256 => 256,
            IconType::RGBA32_256x256_2x => 256,
            IconType::RGBA32_512x512 => 512,
            IconType::RGBA32_512x512_2x => 512,
        }
    }

    /// Returns the screen height of this icon type.  Normally this is the same
    /// as the pixel height, but for 2x "retina" density icons, this will be
    /// half that value.
    ///
    /// # Examples
    /// ```
    /// use icns::IconType;
    /// assert_eq!(IconType::Mask8_128x128.screen_height(), 128);
    /// assert_eq!(IconType::RGBA32_256x256.screen_height(), 256);
    /// assert_eq!(IconType::RGBA32_256x256_2x.screen_height(), 256);
    /// ```
    pub fn screen_height(self) -> u32 {
        match self {
            IconType::Mono_32x32 => 32,
            IconType::MonoA_32x32 => 32,
            IconType::MonoA_16x12 => 12,
            IconType::Palette4_16x12 => 12,
            IconType::Palette8_16x12 => 12,
            IconType::MonoA_16x16 => 16,
            IconType::Palette4_16x16 => 16,
            IconType::Palette8_16x16 => 16,
            IconType::RGB24_16x16 => 16,
            IconType::Mask8_16x16 => 16,
            IconType::Palette4_32x32 => 32,
            IconType::Palette8_32x32 => 32,
            IconType::RGB24_32x32 => 32,
            IconType::Mask8_32x32 => 32,
            IconType::Palette4_48x48 => 48,
            IconType::Palette8_48x48 => 48,
            IconType::MonoA_48x48 => 48,
            IconType::RGB24_48x48 => 48,
            IconType::Mask8_48x48 => 48,
            IconType::RGB24_128x128 => 128,
            IconType::Mask8_128x128 => 128,
            IconType::RGBA32_16x16 => 16,
            IconType::RGBA32_16x16_2x => 16,
            IconType::RGBA32_32x32 => 32,
            IconType::RGBA32_32x32_2x => 32,
            IconType::RGBA32_64x64 => 64,
            IconType::RGBA32_128x128 => 128,
            IconType::RGBA32_128x128_2x => 128,
            IconType::RGBA32_256x256 => 256,
            IconType::RGBA32_256x256_2x => 256,
            IconType::RGBA32_512x512 => 512,
            IconType::RGBA32_512x512_2x => 512,
        }
    }

    /// Returns the encoding used within an ICNS file for this icon type.
    pub fn encoding(self) -> Encoding {
        match self {
            IconType::Mono_32x32 => Encoding::Mono,
            IconType::MonoA_32x32 |
            IconType::MonoA_16x12 |
            IconType::MonoA_16x16 |
            IconType::MonoA_48x48 => Encoding::MonoA,
            IconType::Palette4_16x12 |
            IconType::Palette4_16x16 |
            IconType::Palette4_32x32 |
            IconType::Palette4_48x48 => Encoding::Palette4,
            IconType::Palette8_16x12 |
            IconType::Palette8_16x16 |
            IconType::Palette8_32x32 |
            IconType::Palette8_48x48 => Encoding::Palette8,
            IconType::RGB24_16x16 |
            IconType::RGB24_32x32 |
            IconType::RGB24_48x48 |
            IconType::RGB24_128x128 => Encoding::RLE24,
            IconType::Mask8_16x16 |
            IconType::Mask8_32x32 |
            IconType::Mask8_48x48 |
            IconType::Mask8_128x128 => Encoding::Mask8,
            IconType::RGBA32_16x16 |
            IconType::RGBA32_16x16_2x |
            IconType::RGBA32_32x32 |
            IconType::RGBA32_32x32_2x |
            IconType::RGBA32_64x64 |
            IconType::RGBA32_128x128 |
            IconType::RGBA32_128x128_2x |
            IconType::RGBA32_256x256 |
            IconType::RGBA32_256x256_2x |
            IconType::RGBA32_512x512 |
            IconType::RGBA32_512x512_2x => Encoding::JP2PNG,
        }
    }
}

/// A Macintosh OSType (also known as a ResType), used in ICNS files to
/// identify the type of each icon element.
///
/// An OSType is a four-byte identifier used throughout Mac OS.  In an ICNS
/// file, it indicates the type of data stored in an
/// [`IconElement`](struct.IconElement.html) data block.  For example, OSType
/// `is32` represents 24-bit color data for a 16x16 icon, while OSType `s8mk`
/// represents the 8-bit alpha mask for that same icon.
///
/// See the [`IconType`](enum.IconType.html) enum for an easier-to-use
/// representation of icon data types.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OSType(pub [u8; 4]);

impl fmt::Display for OSType {
    fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let &OSType(raw) = self;
        for &byte in &raw {
            let character = std::char::from_u32(u32::from(byte)).unwrap();
            write!(out, "{}", character)?;
        }
        Ok(())
    }
}

impl std::str::FromStr for OSType {
    type Err = String;

    fn from_str(input: &str) -> Result<OSType, String> {
        let chars: Vec<char> = input.chars().collect();
        if chars.len() != 4 {
            return Err(format!("OSType string must be 4 chars (was {})",
                               chars.len()));
        }
        let mut bytes = [0u8; 4];
        for (i, &ch) in chars.iter().enumerate() {
            let value = ch as u32;
            if value > u8::MAX as u32 {
                return Err(format!("OSType chars must have value of at \
                                    most 0x{:X} (found 0x{:X})",
                                   u8::MAX,
                                   value));
            }
            bytes[i] = value as u8;
        }
        Ok(OSType(bytes))
    }
}

/// Methods of encoding an image within an icon element.
///
/// Each [`IconType`](enum.IconType.html) uses a particular encoding within
/// an ICNS file; this type enumerates those encodings.
///
/// (This type is used internally by the library, but is irrelvant to most
/// library users; if you're not sure whether you need to use it, you probably
/// don't.)
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Encoding {
    /// Icon element data payload is an uncompressed one bit image
    Mono,
    /// Icon element data payload is an uncompressed one bit image with a one bit mask
    MonoA,
    /// Icon element data payload is an uncompressed image with a fixed 4-bit color palette
    Palette4,
    /// Icon element data payload is an uncompressed image with a fixed 8-bit color palette
    Palette8,
    /// Icon element data payload is an uncompressed 8-bit alpha mask.
    Mask8,
    /// Icon element data payload is an RLE-compressed 24-bit RGB image.
    RLE24,
    /// Icon element data payload is a JPEG 2000 or PNG file.
    JP2PNG,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::FromStr;

    const ALL_ICON_TYPES: [IconType; 32] = [
        IconType::Mono_32x32,
        IconType::MonoA_32x32,
        IconType::MonoA_16x12,
        IconType::Palette4_16x12,
        IconType::Palette8_16x12,
        IconType::MonoA_16x16,
        IconType::Palette4_16x16,
        IconType::Palette8_16x16,
        IconType::RGB24_16x16,
        IconType::Mask8_16x16,
        IconType::Palette4_32x32,
        IconType::Palette8_32x32,
        IconType::RGB24_32x32,
        IconType::Mask8_32x32,
        IconType::MonoA_48x48,
        IconType::Palette4_48x48,
        IconType::Palette8_48x48,
        IconType::RGB24_48x48,
        IconType::Mask8_48x48,
        IconType::RGB24_128x128,
        IconType::Mask8_128x128,
        IconType::RGBA32_16x16,
        IconType::RGBA32_16x16_2x,
        IconType::RGBA32_32x32,
        IconType::RGBA32_32x32_2x,
        IconType::RGBA32_64x64,
        IconType::RGBA32_128x128,
        IconType::RGBA32_128x128_2x,
        IconType::RGBA32_256x256,
        IconType::RGBA32_256x256_2x,
        IconType::RGBA32_512x512,
        IconType::RGBA32_512x512_2x,
    ];

    #[test]
    fn icon_type_ostype_round_trip() {
        for icon_type in &ALL_ICON_TYPES {
            let ostype = icon_type.ostype();
            let from = IconType::from_ostype(ostype);
            assert_eq!(Some(*icon_type), from);
        }
    }

    #[test]
    fn icon_type_size_round_trip() {
        for icon_type in &ALL_ICON_TYPES {
            let width = icon_type.pixel_width();
            let height = icon_type.pixel_height();
            let from = IconType::from_pixel_size(width, height).unwrap();
            assert_eq!(from.pixel_width(), width);
            assert_eq!(from.pixel_height(), height);
        }
    }

    #[test]
    fn icon_type_size_and_density_round_trip() {
        for icon_type in &ALL_ICON_TYPES {
            let width = icon_type.pixel_width();
            let height = icon_type.pixel_height();
            let density = icon_type.pixel_density();
            let from =
                IconType::from_pixel_size_and_density(width, height, density)
                    .unwrap();
            assert_eq!(from.pixel_width(), width);
            assert_eq!(from.pixel_height(), height);
            assert_eq!(from.pixel_density(), density);
        }
    }

    #[test]
    fn icon_type_mask_type() {
        for icon_type in &ALL_ICON_TYPES {
            match icon_type.encoding() {
                Encoding::MonoA | Encoding::Mask8 => {
                    assert!(icon_type.is_mask());
                    assert_eq!(icon_type.mask_type(), None);
                }
                Encoding::RLE24 => {
                    assert!(!icon_type.is_mask());
                    if let Some(mask_type) = icon_type.mask_type() {
                        assert_eq!(mask_type.encoding(), Encoding::Mask8);
                        assert_eq!(icon_type.pixel_width(),
                                   mask_type.pixel_width());
                        assert_eq!(icon_type.pixel_height(),
                                   mask_type.pixel_height());
                    } else {
                        panic!("{:?} is missing a mask type", icon_type);
                    }
                }
                Encoding::Mono | Encoding::JP2PNG => {
                    assert!(!icon_type.is_mask());
                    assert_eq!(icon_type.mask_type(), None);
                }
                Encoding::Palette4 | Encoding::Palette8 => {
                    assert!(!icon_type.is_mask());
                    if let Some(mask_type) = icon_type.mask_type() {
                        assert_eq!(mask_type.encoding(), Encoding::MonoA);
                        assert_eq!(
                            icon_type.pixel_width(),
                            mask_type.pixel_width()
                        );
                        assert_eq!(
                            icon_type.pixel_height(),
                            mask_type.pixel_height()
                        );
                    } else {
                        panic!("{:?} is missing a mask type", icon_type);
                    }
                }
            }
        }
    }

    #[test]
    fn ostype_to_and_from_str() {
        let ostype = OSType::from_str("abcd").expect("failed to parse OSType");
        assert_eq!(ostype.to_string(), "abcd".to_string());
    }

    #[test]
    fn ostype_to_and_from_str_non_ascii() {
        let ostype = OSType(*b"sp\xf6b");
        let string = ostype.to_string();
        assert_eq!(string, "sp\u{f6}b".to_string());
        assert_eq!(OSType::from_str(&string), Ok(ostype));
    }

    #[test]
    fn ostype_from_str_failure() {
        assert_eq!(OSType::from_str("abc"),
                   Err("OSType string must be 4 chars (was 3)".to_string()));
        assert_eq!(OSType::from_str("abcde"),
                   Err("OSType string must be 4 chars (was 5)".to_string()));
        assert_eq!(OSType::from_str("ab\u{2603}d"),
                   Err("OSType chars must have value of at most 0xFF \
                        (found 0x2603)"
                       .to_string()));
    }
}
