{-# OPTIONS_GHC -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Graphcs.Formats.Mtl.Contents
-- Copyright   :  (c) Anygma BVBA & Thomas Davie 2008
-- License     :  BSD3
-- 
-- Maintainer  :  tom.davie@gmail.com
-- Stability   :  experimental
-- 
-- Mtl file content description
----------------------------------------------------------------------
module Graphics.Formats.Mtl.Contents
       (MtlFile(..),Material(..)
       ,setName,setMatFile
       ,setAmbient       ,setDiffuse       ,setSpecular
       ,setAmbientTexName,setDiffuseTexName,setSpecularTexName
       ,loadTextures
       ,emptyMat,whiteMat) where

import           Data.Map            (Map)
import qualified Data.Map         as M
import qualified Data.Traversable as T
import           Data.List

import Foreign hiding (newArray)

import Data.Array.Unboxed

import Graphics.Rendering.OpenGL
import Control.Applicative
import Control.Monad

import Test.QuickCheck
import Test.QuickCheck.Instances
import Test.QuickCheck.Instances.OpenGL ()

import Codec.Image.DevIL

import qualified Data.ByteString.Char8 as CBS

newtype MtlFile = MF (Map CBS.ByteString Material)
                  deriving Show

data Material = Mat {name    :: CBS.ByteString
                    ,matFile :: FilePath
                    ,ambientColour  :: Color4 GLfloat
                    ,diffuseColour  :: Color4 GLfloat
                    ,specularColour :: Color4 GLfloat
                    ,ambientTex     :: Either String TextureObject
                    ,diffuseTex     :: Either String TextureObject
                    ,specularTex    :: Either String TextureObject}
                deriving (Show,Eq,Ord)

instance Arbitrary Material where
  arbitrary = liftM2 setName
                     (liftM2 setMatFile
                             (liftM2 setDiffuse
                                     (liftM2 setAmbient
                                             (liftM2 setSpecular
                                                     (return emptyMat)
                                                     arbitrary)
                                             arbitrary)
                                     arbitrary)
                             (anyList nonSpace))
                     (CBS.pack <$> anyList nonSpace)
  coarbitrary m =   coarbitrary (CBS.unpack $ name m)
                  . coarbitrary (matFile m)
                  . coarbitrary (ambientColour  m)
                  . coarbitrary (diffuseColour  m)
                  . coarbitrary (specularColour m)
                  . coarbitrary (ambientTex     m)
                  . coarbitrary (diffuseTex     m)
                  . coarbitrary (specularTex    m)

loadTextures :: (FilePath -> IO (Maybe FilePath))
             -> MtlFile
             -> IO ([FilePath],MtlFile)
loadTextures f (MF ms) =
  do loaded <- mmapM (loadMtlTextures f) $ ms
     return (filter (/= "") . nub . concat . M.elems . M.map fst $ loaded
            ,MF $ M.map snd loaded)

mmapM :: (Monad m) => (a -> m b) -> Map k a -> m (Map k b)
mmapM f = T.sequence . M.map f 

loadMtlTextures :: (FilePath -> IO (Maybe FilePath))
                -> Material
                -> IO ([FilePath],Material)
loadMtlTextures f m =
  do at <- maybeLoadTex (ambientTex  m)
     dt <- maybeLoadTex (diffuseTex  m)
     st <- maybeLoadTex (specularTex m)
     let missing = missing' at ++ missing' dt ++ missing' st
     return (missing,m {ambientTex = at, diffuseTex = dt, specularTex = st})
  where
    missing' :: Either String TextureObject -> [String]
    missing' (Left x)  = [x]
    missing' (Right _) = []
    maybeLoadTex :: Either String TextureObject
                 -> IO (Either String TextureObject)
    maybeLoadTex mat =
      case mat of
        Left "" -> return $ Left ""
        Left x  -> do fn <- f x
                      (case fn of
                         Just fn' ->
                           do t <- loadTexture fn'
                              return $ Right t
                         Nothing ->
                           return $ Left x)
        Right x -> return $ Right x   

loadTexture :: FilePath -> IO TextureObject
loadTexture f = buildTexture =<< readImage f

{- Andy Gill's hacky texture loading code -}
buildTexture :: UArray (Int,Int,Int) Word8 -> IO TextureObject
buildTexture arr =
  do let (width,height,mindepth) =
           case bounds arr of
             ((mw,mh,md),(w,h,_)) -> (w + 1 - mw,h + 1 - mh, md)
     p <- mallocBytes (width * height * 4)
     sequence_
       [ do pokeElemOff p (off+0) (arr ! (w,h,mindepth  ))
            pokeElemOff p (off+1) (arr ! (w,h,mindepth+1))
            pokeElemOff p (off+2) (arr ! (w,h,mindepth+2))
            pokeElemOff p (off+3) (arr ! (w,h,mindepth+3))
       | (off,(w,h)) <- zip [0,4 ..] [ (w,h) | w <- [ 0 .. width - 1 ]
                                             , h <- [ 0 .. height  - 1 ]]
       ]
      
     texName <- liftM head (genObjectNames 1)
     textureBinding Texture2D $= Just texName
     textureFilter  Texture2D $= ((Linear', Nothing), Linear')
     
     textureWrapMode Texture2D S $= (Repeated, Repeat)
     textureWrapMode Texture2D T $= (Repeated, Repeat) 
     
     let pd = PixelData RGBA UnsignedByte p
     texImage2D Nothing
                NoProxy
                0
                RGBA'
                (TextureSize2D (fromIntegral width) (fromIntegral height))
                0
                pd
     return texName

setName :: Material -> CBS.ByteString -> Material
setName m n = m {name = n}

setMatFile :: Material -> FilePath -> Material
setMatFile m f = m {matFile = f}

setAmbient :: Material -> Color4 GLfloat -> Material
setAmbient m c = m {ambientColour = c}

setDiffuse :: Material -> Color4 GLfloat -> Material
setDiffuse m c = m {diffuseColour = c}

setSpecular :: Material -> Color4 GLfloat -> Material
setSpecular m c = m {specularColour = c}

setAmbientTexName :: Material -> String -> Material
setAmbientTexName m t = m {ambientTex = Left t}

setDiffuseTexName :: Material -> String -> Material
setDiffuseTexName m t = m {diffuseTex = Left t}

setSpecularTexName :: Material -> String -> Material
setSpecularTexName m t = m {specularTex = Left t}

emptyMat :: Material
emptyMat = Mat {name           = CBS.pack ""
               ,matFile        = ""
               ,ambientColour  = Color4 0.0 0.0 0.0 0.0
               ,diffuseColour  = Color4 0.0 0.0 0.0 0.0
               ,specularColour = Color4 0.0 0.0 0.0 0.0
               ,ambientTex     = Left ""
               ,diffuseTex     = Left ""
               ,specularTex    = Left ""}

whiteMat :: Material
whiteMat = Mat {name           = CBS.pack "white"
               ,matFile        = ""
               ,ambientColour  = Color4 1   1   1   1
               ,diffuseColour  = Color4 0.5 0.5 0.5 1
               ,specularColour = Color4 0   0   0   1
               ,ambientTex     = Left ""
               ,diffuseTex     = Left ""
               ,specularTex    = Left ""}