{-# LANGUAGE DataKinds               #-}
{-# LANGUAGE DerivingStrategies      #-}
{-# LANGUAGE DeriveTraversable       #-}
{-# LANGUAGE DuplicateRecordFields   #-}
{-# LANGUAGE FlexibleInstances       #-}
{-# LANGUAGE GADTs                   #-}
{-# LANGUAGE MagicHash               #-}
{-# LANGUAGE NamedFieldPuns          #-}
{-# LANGUAGE PatternSynonyms         #-}
{-# LANGUAGE PolyKinds               #-}
{-# LANGUAGE RecordWildCards         #-}
{-# LANGUAGE ScopedTypeVariables     #-}
{-# LANGUAGE Strict                  #-}
{-# LANGUAGE TypeApplications        #-}
{-# LANGUAGE TypeFamilies            #-}
{-# LANGUAGE TypeOperators           #-}
{-# LANGUAGE UndecidableInstances    #-}
{-# LANGUAGE UndecidableSuperClasses #-}

module Vulkan.Requirement
  ( -- * Vulkan requirements
    InstanceRequirement(..)
  , DeviceRequirement(..)
  , -- * Utility functionality for handling structure chains
    KnownFeatureStruct(..)
  , KnownPropertyStruct(..)
  , SFeatureStruct(..)
  , SPropertyStruct(..)
  ) where

-- base
import Data.Typeable
  ( Typeable )
import Data.Word
  ( Word32 )

-- bytestring
import Data.ByteString
  ( ByteString )

-- vulkan
import Vulkan.Core10.Device
  ( DeviceCreateInfo(..)
  )
import Vulkan.Core10.DeviceInitialization
  ( PhysicalDeviceFeatures, PhysicalDeviceProperties
  )
import Vulkan.Core11.Promoted_From_VK_KHR_get_physical_device_properties2
    ( PhysicalDeviceFeatures2(..), PhysicalDeviceProperties2(..) )
import Vulkan.CStruct
  ( FromCStruct, ToCStruct )
import Vulkan.CStruct.Extends
  ( Extends )
import Vulkan.Zero

----------------------------------------------------------------
-- Instance Requirements
----------------------------------------------------------------

-- | A requirement on a Vulkan 'Instance'.
data InstanceRequirement where
  -- | Require a minimum Vulkan instance version.
  RequireInstanceVersion
    :: { InstanceRequirement -> Word32
version :: Word32 }
    -> InstanceRequirement
  -- | Require a Vulkan layer.
  RequireInstanceLayer
    :: { InstanceRequirement -> ByteString
instanceLayerName       :: ByteString
       , InstanceRequirement -> Word32
instanceLayerMinVersion :: Word32
         -- ^ The 'implementationVersion' of the layer must meet or exceed this
         -- version
       }
    -> InstanceRequirement
  -- | Require a Vulkan instance extension.
  RequireInstanceExtension
    :: { InstanceRequirement -> Maybe ByteString
instanceExtensionLayerName  :: Maybe ByteString
       , InstanceRequirement -> ByteString
instanceExtensionName       :: ByteString
       , InstanceRequirement -> Word32
instanceExtensionMinVersion :: Word32
       }
    -> InstanceRequirement

----------------------------------------------------------------
-- Device Requirements
----------------------------------------------------------------

-- A requirement on a Vulkan 'PhysicalDevice'.
data DeviceRequirement where
  -- | Require a minimum device version.
  RequireDeviceVersion
    :: { DeviceRequirement -> Word32
version :: Word32 }
    -> DeviceRequirement
  -- | Require a Vulkan physical device feature.
  RequireDeviceFeature
    :: forall struct
    .  KnownFeatureStruct struct
    => { DeviceRequirement -> ByteString
featureName   :: ByteString
       , ()
checkFeature  :: struct -> Bool
       , ()
enableFeature :: struct -> struct
       }
    -> DeviceRequirement
  -- | Require a Vulkan physical device property.
  RequireDeviceProperty
    :: forall struct
    .  KnownPropertyStruct struct
    => { DeviceRequirement -> ByteString
propertyName  :: ByteString
       , ()
checkProperty :: struct -> Bool
       }
    -> DeviceRequirement
  -- | Require a Vulkan device extension.
  RequireDeviceExtension
    :: { DeviceRequirement -> Maybe ByteString
deviceExtensionLayerName  :: Maybe ByteString
       , DeviceRequirement -> ByteString
deviceExtensionName       :: ByteString
       , DeviceRequirement -> Word32
deviceExtensionMinVersion :: Word32
       }
    -> DeviceRequirement

-- | Singleton for a Vulkan structure that can appear in 'PhysicalDeviceFeatures2'.
--
-- It is either 'PhysicalDeviceFeatures', or it 'Extends' 'PhysicalDeviceFeatures2'.
data SFeatureStruct feat where
  BasicFeatureStruct
    :: SFeatureStruct PhysicalDeviceFeatures
  ExtendedFeatureStruct
    :: ( Show feat
       , Extends PhysicalDeviceFeatures2 feat, Extends DeviceCreateInfo feat
       , Zero feat, FromCStruct feat, ToCStruct feat
       )
    => SFeatureStruct feat

-- | A Vulkan structure that can appear in 'PhysicalDeviceFeatures2'.
class Typeable feat => KnownFeatureStruct feat where
  sFeatureStruct :: SFeatureStruct feat

instance KnownFeatureStruct PhysicalDeviceFeatures where
  sFeatureStruct :: SFeatureStruct PhysicalDeviceFeatures
sFeatureStruct = SFeatureStruct PhysicalDeviceFeatures
BasicFeatureStruct

instance {-# OVERLAPPABLE #-}
         ( Typeable feat, Show feat
         , Extends PhysicalDeviceFeatures2 feat, Extends DeviceCreateInfo feat
         , Zero feat, FromCStruct feat, ToCStruct feat
         )
      => KnownFeatureStruct feat where
  sFeatureStruct :: SFeatureStruct feat
sFeatureStruct = forall feat.
(Show feat, Extends PhysicalDeviceFeatures2 feat,
 Extends DeviceCreateInfo feat, Zero feat, FromCStruct feat,
 ToCStruct feat) =>
SFeatureStruct feat
ExtendedFeatureStruct

-- | Singleton for a Vulkan structure that can appear in 'PhysicalDeviceProperties2'.
--
-- It is either 'PhysicalDeviceProperties', or it 'Extends' 'PhysicalDeviceProperties2'.
data SPropertyStruct prop where
  BasicPropertyStruct
    :: SPropertyStruct PhysicalDeviceProperties
  ExtendedPropertyStruct
    :: ( Typeable prop, Extends PhysicalDeviceProperties2 prop, FromCStruct prop, ToCStruct prop )
    => SPropertyStruct prop

-- | A Vulkan structure that can appear in 'PhysicalDeviceProperties2'.
class Typeable prop => KnownPropertyStruct prop where
  sPropertyStruct :: SPropertyStruct prop
instance KnownPropertyStruct PhysicalDeviceProperties where
  sPropertyStruct :: SPropertyStruct PhysicalDeviceProperties
sPropertyStruct = SPropertyStruct PhysicalDeviceProperties
BasicPropertyStruct
instance {-# OVERLAPPABLE #-}
         ( Typeable prop, Extends PhysicalDeviceProperties2 prop, FromCStruct prop, ToCStruct prop )
       => KnownPropertyStruct prop where
  sPropertyStruct :: SPropertyStruct prop
sPropertyStruct = forall prop.
(Typeable prop, Extends PhysicalDeviceProperties2 prop,
 FromCStruct prop, ToCStruct prop) =>
SPropertyStruct prop
ExtendedPropertyStruct