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)
import Data.Text (Text)

-- | 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 -> Text
        -- ^ 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"