{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}

-- | Data type for holding server state.
--
-- The server state consists mainly of the allocators needed for different types of resources, such as nodes, buffers and buses.
module Sound.SC3.Server.State (
  SyncId
, SyncIdAllocator
, syncIdAllocator
, NodeId
, NodeIdAllocator
, nodeIdAllocator
, BufferId
, BufferIdAllocator
, bufferIdAllocator
, ControlBusId
, ControlBusIdAllocator
, controlBusIdAllocator
, AudioBusId
, AudioBusIdAllocator
, audioBusIdAllocator
, Allocators
, mkAllocators
) where

import           Data.Int (Int32)
import           Sound.SC3.Server.Allocator (IdAllocator(..), RangeAllocator(..))
import qualified Sound.SC3.Server.Allocator.BlockAllocator.FirstFit as FirstFitAllocator
import qualified Sound.SC3.Server.Allocator.Range as Range
import qualified Sound.SC3.Server.Allocator.SetAllocator as SetAllocator
import qualified Sound.SC3.Server.Allocator.SimpleAllocator as SimpleAllocator
import qualified Sound.SC3.Server.Allocator.Wrapped as Wrapped
import           Sound.SC3.Server.Process.Options (ServerOptions(..))

-- | Synchronisation barrier id.
newtype SyncId = SyncId Int32 deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)

-- | Synchronisation barrier id allocator.
data SyncIdAllocator = forall a . (IdAllocator a, Id a ~ SyncId) => SyncIdAllocator !a

instance IdAllocator SyncIdAllocator where
    type Id SyncIdAllocator = SyncId
    alloc  (SyncIdAllocator a) = Wrapped.alloc SyncIdAllocator a
    free i (SyncIdAllocator a) = Wrapped.free SyncIdAllocator i a
    statistics (SyncIdAllocator a) = Wrapped.statistics a

-- | Node id.
newtype NodeId = NodeId Int32 deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)

-- | Node id allocator.
data NodeIdAllocator = forall a . (IdAllocator a, Id a ~ NodeId) => NodeIdAllocator !a

instance IdAllocator NodeIdAllocator where
    type Id NodeIdAllocator = NodeId
    alloc  (NodeIdAllocator a) = Wrapped.alloc NodeIdAllocator a
    free i (NodeIdAllocator a) = Wrapped.free NodeIdAllocator i a
    statistics (NodeIdAllocator a) = Wrapped.statistics a

-- | Buffer id.
newtype BufferId = BufferId Int32 deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)

-- | Buffer id allocator.
data BufferIdAllocator = forall a . (RangeAllocator a, Id a ~ BufferId) => BufferIdAllocator !a

instance IdAllocator BufferIdAllocator where
    type Id BufferIdAllocator = BufferId
    alloc  (BufferIdAllocator a) = Wrapped.alloc BufferIdAllocator a
    free i (BufferIdAllocator a) = Wrapped.free BufferIdAllocator i a
    statistics (BufferIdAllocator a) = Wrapped.statistics a

instance RangeAllocator BufferIdAllocator where
    allocRange n (BufferIdAllocator a) = Wrapped.allocRange BufferIdAllocator n a
    freeRange r (BufferIdAllocator a) = Wrapped.freeRange BufferIdAllocator r a

-- | Control bus id.
newtype ControlBusId = ControlBusId Int32
                     deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)

-- | Control bus id allocator.
data ControlBusIdAllocator = forall a . (RangeAllocator a, Id a ~ ControlBusId) => ControlBusIdAllocator !a

instance IdAllocator ControlBusIdAllocator where
    type Id ControlBusIdAllocator = ControlBusId
    alloc  (ControlBusIdAllocator a) = Wrapped.alloc ControlBusIdAllocator a
    free i (ControlBusIdAllocator a) = Wrapped.free ControlBusIdAllocator i a
    statistics (ControlBusIdAllocator a) = Wrapped.statistics a

instance RangeAllocator ControlBusIdAllocator where
    allocRange n (ControlBusIdAllocator a) = Wrapped.allocRange ControlBusIdAllocator n a
    freeRange r (ControlBusIdAllocator a) = Wrapped.freeRange ControlBusIdAllocator r a

-- | Audio bus id.
newtype AudioBusId = AudioBusId Int32
                     deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)

-- | Audio bus id allocator.
data AudioBusIdAllocator = forall a . (RangeAllocator a, Id a ~ AudioBusId) => AudioBusIdAllocator !a

instance IdAllocator AudioBusIdAllocator where
    type Id AudioBusIdAllocator = AudioBusId
    alloc  (AudioBusIdAllocator a) = Wrapped.alloc AudioBusIdAllocator a
    free i (AudioBusIdAllocator a) = Wrapped.free AudioBusIdAllocator i a
    statistics (AudioBusIdAllocator a) = Wrapped.statistics a

instance RangeAllocator AudioBusIdAllocator where
    allocRange n (AudioBusIdAllocator a) = Wrapped.allocRange AudioBusIdAllocator n a
    freeRange r (AudioBusIdAllocator a) = Wrapped.freeRange AudioBusIdAllocator r a

-- | Server allocators.
data Allocators = Allocators {
    syncIdAllocator       :: SyncIdAllocator
  , nodeIdAllocator       :: NodeIdAllocator
  , bufferIdAllocator     :: BufferIdAllocator
  , audioBusIdAllocator   :: AudioBusIdAllocator
  , controlBusIdAllocator :: ControlBusIdAllocator
  }

-- | Create a new state with default allocators.
mkAllocators :: ServerOptions -> Allocators
mkAllocators os =
  Allocators {
      syncIdAllocator =
        SyncIdAllocator $
          SimpleAllocator.cons
            (Range.range 0 (maxBound :: SyncId))
    , nodeIdAllocator =
        NodeIdAllocator $
          SetAllocator.cons
            (Range.range 1000 (1000 + fromIntegral (maxNumberOfNodes os)))
    , bufferIdAllocator =
        BufferIdAllocator $
          FirstFitAllocator.bestFit
            FirstFitAllocator.LazyCoalescing
            (Range.range 0 (fromIntegral (numberOfSampleBuffers os)))
    , audioBusIdAllocator =
        AudioBusIdAllocator $
          FirstFitAllocator.bestFit
            FirstFitAllocator.LazyCoalescing
            (Range.range
              (fromIntegral numHardwareChannels)
              (fromIntegral (numHardwareChannels + numberOfAudioBusChannels os)))
    , controlBusIdAllocator =
        ControlBusIdAllocator $
          FirstFitAllocator.bestFit
            FirstFitAllocator.LazyCoalescing
            (Range.range 0 (fromIntegral (numberOfControlBusChannels os)))
  }
  where
    numHardwareChannels = numberOfInputBusChannels os
                        + numberOfOutputBusChannels os