module Graphics.Image.PixelMap where

import Data.Colour
import Data.Colour.Names (black)
import qualified Data.Colour.SRGB as SRGB
import qualified Data.Colour.SRGB.Linear as SRGBLinear
import qualified Data.Colour.CIE as CIE
import qualified Data.Colour.RGBSpace as RGB
import qualified Data.Array.Storable as A
import qualified Data.Map as M
import Foreign.C

-- | type of channels
type Channel = A.StorableArray (Int,Int) CFloat

-- | A pixel map, stored as separate 2D mutable, C-compatible arrays for each channel in w,h order
data PixelMap = PixelMap {
     tags :: [(String,String)]                                  -- ^ tags for TIFF files
  ,  pixels :: M.Map String (A.StorableArray (Int,Int) CFloat)  -- ^ the actual pixels.  if the image is an CIE one then channels will be \"X\", \"Y\", \"Z\" (and possibly more).  If it's RGB, then it's \"R\" \"G\" \"B\" (and possibly more)
  ,  width :: Int                                               -- ^ the width of the image
  ,  height :: Int                                              -- ^ the height of the image
  ,  colorspace :: ColourSpace                                  -- ^ the color space of the image (see "Data.Colour" for more details)
}

-- | Converting to and from this portable PixelMap format
class ImageData a where
    toPixelMap :: a -> PixelMap         -- ^ Go to a pixel map from an arbitrary type.
    fromPixelMap :: PixelMap -> a       -- ^ Go from a pixel map to an arbitrary type.

-- | Colourspace monikers representing different spaces in Data.Colour
data ColourSpace = 
    Ciea                            -- ^ CIE XYZa format.  Don't worry if you don't have an alpha channel, it will be filled in if you don't.
  | Rgba (RGB.RGBSpace CFloat)      -- ^ RGBa with a defined colourspace
  | Srgba                           -- ^ SRGBa
  | SrgbaLinear                     -- ^ Linear SRGBa colourspace

ixHelper pxs chan0 chan1 chan2 cofun ix = do
    a <- (pxs M.! chan0) `A.readArray` ix 
    b <- (pxs M.! chan1) `A.readArray` ix    
    c <- (pxs M.! chan2) `A.readArray` ix
    alpha <- maybe (return 1) (`A.readArray` ix) (M.lookup "A" pxs)
    return $ cofun a b c `withOpacity` alpha      

-- | Read an arbitrary pixel
(!!) :: PixelMap -> (Int, Int) -> IO (AlphaColour CFloat)
(PixelMap _ pxs _ _ Ciea) !! ix = ixHelper pxs "X" "Y" "Z" CIE.cieXYZ ix 
(PixelMap _ pxs _ _ (Rgba space)) !! ix = ixHelper pxs "R" "G" "B" (RGB.rgbUsingSpace space) ix
(PixelMap _ pxs _ _ Srgba) !! ix = ixHelper pxs "R" "G" "B" SRGB.sRGB ix
(PixelMap _ pxs _ _ SrgbaLinear) !! ix = ixHelper pxs "R" "G" "B" SRGBLinear.rgb ix

-- | Read one channel from an arbitrary pixel
(!/) :: PixelMap -> (Int,Int,String) -> IO CFloat
(PixelMap _ pxs _ _ _) !/ (r,c,ch) = maybe (return 1) (`A.readArray` (r,c)) (M.lookup ch pxs)    

-- | Write one channel of an arbitrary pixel
(!/=) :: (CFloat -> IO ()) -> CFloat -> IO ()
a !/= b = a b

-- | Usage: refChan image (0,0) \"R\" !\/= 1.0
refChan :: PixelMap -> (Int,Int) -> String -> CFloat -> IO ()
refChan mp ix ch = A.writeArray (pixels mp M.! ch) ix

-- | Write a colour to an arbitrary pixel
(!=) :: (AlphaColour CFloat -> IO ()) -> AlphaColour CFloat -> IO ()
a != b = a b

-- | Usage: refPixel image (0,0) != opaque black
refPixel :: PixelMap -> (Int,Int) -> AlphaColour CFloat -> IO ()
refPixel (PixelMap _ pxs _ _ Ciea) ix c = do
    let (x,y,z) = CIE.toCIEXYZ $ (1/a) `darken` (c `Data.Colour.over` black)
        a = alphaChannel c
    A.writeArray (pxs M.! "X") ix $ x
    A.writeArray (pxs M.! "Y") ix $ y
    A.writeArray (pxs M.! "Z") ix $ z
    A.writeArray (pxs M.! "A") ix $ a 
    
refPixel (PixelMap _ pxs _ _ (Rgba space)) ix c = do
    let (RGB.RGB r g b) = RGB.toRGBUsingSpace space $ (1/a) `darken` (c `Data.Colour.over` black)
        a = alphaChannel c
    A.writeArray (pxs M.! "R") ix r
    A.writeArray (pxs M.! "G") ix g        
    A.writeArray (pxs M.! "B") ix b
    A.writeArray (pxs M.! "A") ix a
    
refPixel (PixelMap _ pxs _ _ Srgba) ix c =  do
    let (SRGB.RGB r g b) = SRGB.toSRGB $ (1/a) `darken` (c `Data.Colour.over` black)
        a = alphaChannel c
    A.writeArray (pxs M.! "R") ix r
    A.writeArray (pxs M.! "G") ix g        
    A.writeArray (pxs M.! "B") ix b
    A.writeArray (pxs M.! "A") ix a
    
refPixel (PixelMap _ pxs _ _ SrgbaLinear) ix c =  do
    let (SRGBLinear.RGB r g b) = SRGBLinear.toRGB $ (1/a) `darken` (c `Data.Colour.over` black)
        a = alphaChannel c
    A.writeArray (pxs M.! "R") ix r
    A.writeArray (pxs M.! "G") ix g        
    A.writeArray (pxs M.! "B") ix b
    A.writeArray (pxs M.! "A") ix a