{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE TypeSynonymInstances  #-}

module Gelatin.GL.TH where

import           Control.Exception   (assert)
import           Data.Proxy          (Proxy (..))
import           Foreign.C.String    (withCString)
import           GHC.TypeLits        (KnownSymbol, symbolVal)
import           Graphics.GL
import           Language.Haskell.TH

import           Gelatin.Shaders

genUniform :: TypeQ
           -- ^ The type of the uniform value.
           -- Most likely 'Bool', 'Float', 'V3', 'M44', etc.
           -> ExpQ
           -- ^ The function that marshals the value to the shader.
           -> DecsQ
genUniform typ func =
  [d|
  instance KnownSymbol name => HasGenFunc (Uniform name $typ) where
    type GenFunc (Uniform name $typ) = GLuint -> $typ -> IO ()
    genFunction _ program val = do
      let ident = symbolVal (Proxy :: Proxy name)
      loc <- withCString ident $ glGetUniformLocation program
      $func loc val
      glGetError >>= \case
        0 -> return ()
        e -> do
          putStrLn $ unwords [ "Could not update uniform"
                             , ident
                             , "with value"
                             , show val
                             , ", encountered error (" ++ show e ++ ")"
                             , show (GL_INVALID_OPERATION, "invalid operation")
                             , show (GL_INVALID_VALUE, "invalid value")
                             ]
          assert False $ return ()

  |]