module Graphics.Types where

import qualified Data.ByteString as BS
import Data.Word
import Numeric (showHex)
import Data.Function (on)
import Data.List
import Text.Printf (printf)

-- | An exif value.
-- 
-- If you want a string describing the contents
-- of the value, simply use 'show'.
data ExifValue = ExifNumber !Int
	-- ^ An exif number. Originally it could have been short, int, signed or not.
	| ExifText !String
	-- ^ ASCII text.
	| ExifRational !Int !Int
	-- ^ A rational number (numerator, denominator).
	-- Sometimes we're used to it as rational (exposition time: 1/160),
	-- sometimes as float (exposure compensation, we rather think -0.75)
	-- 'show' will display it as 1/160.
	| ExifNumberList ![Int]
	-- ^ List of numbers. Originally they could have been short, int, signed or not.
	| ExifRationalList ![(Int,Int)]
	-- ^ A list of rational numbers (numerator, denominator).
	-- Sometimes we're used to it as rational (exposition time: 1/160),
	-- sometimes as float (exposure compensation, we rather think -0.75)
	-- 'show' will display it as 1/160.
	| ExifUndefined !BS.ByteString
	-- ^ The undefined type in EXIF means that the contents are not
	-- specified and up to the manufacturer. In effect it's exactly
	-- a bytestring. Sometimes it's text with ASCII or UNICODE at the
	-- beginning, often it's binary in nature.
	| ExifUnknown !Word16 !Int !Int
	-- ^ Unknown exif value type. All EXIF 2.3 types are
	-- supported, it could be a newer file.
	-- The parameters are type, count then value
	deriving (Eq, Ord)

instance Show ExifValue where
	show (ExifNumber v) = show v
	show (ExifText v) = v
	show (ExifRational n d) = show n ++ "/" ++ show d
	show (ExifUnknown t c v) = show "Unknown exif type. Type: " ++ show t 
		++ " count: " ++ show c ++ " value: " ++ show v
	show (ExifNumberList l) = show l
	show (ExifRationalList l) = show l
	show (ExifUndefined bs) = show bs

-- | Location of the tag in the JPG file structure.
-- Normally you don't need to fiddle with this,
-- except maybe if the library doesn't know the particular
-- exif tag you're interested in.
-- Rather check the list of supported exif tags, like
-- 'exposureTime' and so on.
data TagLocation = ExifSubIFD | IFD0 | GpsSubIFD
	deriving (Show, Eq, Ord)

-- | An exif tag. Normally you don't need to fiddle with this,
-- except maybe if the library doesn't know the particular
-- exif tag you're interested in.
-- Rather check the list of supported exif tags, like
-- 'exposureTime' and so on.
data ExifTag = ExifTag
	{
		tagLocation :: TagLocation,
		-- ^ In which part of the JPEG file the exif tag was found
		tagDesc :: Maybe String,
		-- ^ Description of the exif tag (exposureTime, fnumber...) or if unknown, Nothing.
		-- This should be purely for debugging purposes, to compare tags use == on ExifTag
		-- or compare the tagKey.
		tagKey :: Word16,
		-- ^ Exif tag key, the number uniquely identifying this tag.
		prettyPrinter :: ExifValue -> String
		-- ^ A function that'll display nicely an exif value for that exif tag.
		-- For instance for the 'flash' ExifTag, it'll say whether the flash was
		-- fired or not, if there was return light and so on.
	}

instance Show ExifTag where
	show (ExifTag _ (Just d) _ _) = d
	show (ExifTag l _ v _) = "Unknown tag, location: " ++ show l
		++ ", value: 0x" ++ showHex v ""

instance Eq ExifTag where
	t1 == t2 = tagKey t1 == tagKey t2 && tagLocation t1 == tagLocation t2

instance Ord ExifTag where
	compare t1 t2 = if locCmp /= EQ then locCmp else tagCmp
		where
			locCmp = (compare `on` tagLocation) t1 t2 
			tagCmp = (compare `on` tagKey) t1 t2

-- | Format the exif value as floating-point if it makes sense,
-- otherwise use the default 'show' implementation.
-- The first parameter lets you specify how many digits after
-- the comma to format in the result string.
-- The special behaviour applies only for 'ExifRational' and 'ExifRationalList'.
formatAsFloatingPoint :: Int -> ExifValue -> String
formatAsFloatingPoint n (ExifRational num den) = formatNumDenAsString n num den
formatAsFloatingPoint n (ExifRationalList values) = intercalate ", " $ foldl' step [] values
	where step soFar (num,den) = soFar ++ [formatNumDenAsString n num den]
formatAsFloatingPoint _ v = show v

formatNumDenAsString :: Int -> Int -> Int -> String
formatNumDenAsString n num den = printf formatString (fromIntegral num / fromIntegral den :: Double)
	where formatString = "%." ++ show n ++ "f"