module Sound.SC3.Server.Process.Options (
  Verbosity(..)
, ServerOptions(..)
, defaultServerOptions
, fromBuildDirectory
, fromPrefix
, fromApplicationBundle
, NetworkPort(..)
, defaultUDPPort
, defaultTCPPort
, RTOptions(..)
, onPort
, jackDeviceName
, withJackDeviceName
, defaultRTOptions
, defaultRTOptionsUDP
, defaultRTOptionsTCP
, NRTOptions(..)
, defaultNRTOptions
) where

import Data.Default (Default(..))
import Sound.SC3.Server.Enum
import System.FilePath ((</>))

-- | Used with the 'verbosity' field in 'ServerOptions'.
data Verbosity =
    Silent
  | Quiet
  | Normal
  | Verbose
  | VeryVerbose
  | ExtremelyVerbose
  deriving (Eq, Read, Show)

instance Enum Verbosity where
    fromEnum Silent           = -2
    fromEnum Quiet            = -1
    fromEnum Normal           = 0
    fromEnum Verbose          = 1
    fromEnum VeryVerbose      = 2
    fromEnum ExtremelyVerbose = 3
    toEnum (-2)               = Silent
    toEnum (-1)               = Quiet
    toEnum 0                  = Normal
    toEnum 1                  = Verbose
    toEnum 2                  = VeryVerbose
    toEnum 3                  = ExtremelyVerbose
    toEnum _                  = error "Verbosity (toEnum): bad argument"

instance Default Verbosity where
  def = Normal

-- ====================================================================
-- * Server options

-- | Specify general server options used both in realtime and non-realtime
-- mode.
data ServerOptions = ServerOptions {
    serverProgram               :: FilePath          -- ^ Path to the @scsynth@ program
  , numberOfControlBusChannels  :: Int               -- ^ Number of allocated control bus channels
  , numberOfAudioBusChannels    :: Int               -- ^ Number of allocated audio bus channels
  , numberOfInputBusChannels    :: Int               -- ^ Number of physical input channels
  , numberOfOutputBusChannels   :: Int               -- ^ Number of physical output channels
  , blockSize                   :: Int               -- ^ Synthesis block size
  , numberOfSampleBuffers       :: Int               -- ^ Number of allocated sample buffers
  , maxNumberOfNodes            :: Int               -- ^ Maximum number of synthesis nodes
  , maxNumberOfSynthDefs        :: Int               -- ^ Maximum number of synth definitions
  , realtimeMemorySize          :: Int               -- ^ Realtime memory size in bytes
  , numberOfWireBuffers         :: Int               -- ^ Number of unit generator connection buffers
  , numberOfRandomSeeds         :: Int               -- ^ Number of random number generator seeds
  , loadSynthDefs               :: Bool              -- ^ If 'True', load synth definitions from /synthdefs/ directory on startup
  , verbosity                   :: Verbosity         -- ^ 'Verbosity' level
  , ugenPluginPath              :: Maybe [FilePath]  -- ^ List of UGen plugin search paths
  , restrictedPath              :: Maybe FilePath    -- ^ Sandbox path to restrict OSC command filesystem access
  } deriving (Eq, Show)

instance Default ServerOptions where
  def = defaultServerOptions

-- | Default server options.
defaultServerOptions :: ServerOptions
defaultServerOptions = ServerOptions {
    serverProgram              = "scsynth"
  , numberOfControlBusChannels = 4096
  , numberOfAudioBusChannels   = 128
  , numberOfInputBusChannels   = 8
  , numberOfOutputBusChannels  = 8
  , blockSize                  = 64
  , numberOfSampleBuffers      = 1024
  , maxNumberOfNodes           = 1024
  , maxNumberOfSynthDefs       = 1024
  , realtimeMemorySize         = 8192
  , numberOfWireBuffers        = 64
  , numberOfRandomSeeds        = 64
  , loadSynthDefs              = True
  , verbosity                  = def
  , ugenPluginPath             = Nothing
  , restrictedPath             = Nothing
  }

-- | Run @scsynth@ from a SuperCollider build directory.
--
-- Since 0.8.0
fromBuildDirectory :: FilePath -> ServerOptions -> ServerOptions
fromBuildDirectory dir options = options {
    serverProgram = dir </> "server/scsynth/scsynth"
  , ugenPluginPath = Just [ dir </> "server/plugins" ] }

-- | Run @scsynth@ from a Linux installation prefix.
--
-- Since 0.8.0
fromPrefix :: FilePath -> ServerOptions -> ServerOptions
fromPrefix dir options = options {
    serverProgram = dir </> "bin/scsynth"
  , ugenPluginPath = Just [ dir </> "lib/SuperCollider/plugins" ] }

-- | Run @scsynth@ from an OSX application bundle.
--
-- Since 0.8.0
fromApplicationBundle :: FilePath -> ServerOptions -> ServerOptions
fromApplicationBundle dir options = options {
    serverProgram = resources </> "scsynth"
  , ugenPluginPath = Just [ resources </> "plugins" ] }
  where resources = dir </> "Contents/Resources"

-- ====================================================================
-- * Realtime options

-- | Network port.
data NetworkPort =
    UDPPort Int
  | TCPPort Int
  deriving (Eq, Show)

instance Default NetworkPort where
  def = defaultUDPPort

-- | Default network port number.
defaultPortNumber :: Int
defaultPortNumber = 57110

-- | Default UDP port.
defaultUDPPort :: NetworkPort
defaultUDPPort = UDPPort defaultPortNumber

-- | Default TCP port.
defaultTCPPort :: NetworkPort
defaultTCPPort = TCPPort defaultPortNumber

-- | Realtime server options, parameterized by the OpenSoundControl
-- 'Transport' to be used.
data RTOptions = RTOptions {
    -- Network control
    networkPort             :: NetworkPort     -- ^ Network port
  , useZeroconf             :: Bool            -- ^ If 'True', publish scsynth service through Zeroconf
  , maxNumberOfLogins       :: Int             -- ^ Max number of supported logins if 'sessionPassword' is set
  , sessionPassword         :: Maybe String    -- ^ Session password
  -- Audio device control
  , hardwareDeviceName      :: Maybe String    -- ^ Hardware device name (see also 'jackDeviceName')
  , hardwareBufferSize      :: Int             -- ^ Hardware buffer size (no effect with JACK)
  , hardwareSampleRate      :: Int             -- ^ Hardware buffer size (no effect with JACK)
  , inputStreamsEnabled     :: Maybe Int       -- ^ Enabled input streams (CoreAudio only)
  , outputStreamsEnabled    :: Maybe Int       -- ^ Enabled output streams (CoreAudio only)
  } deriving (Eq, Show)

instance Default RTOptions where
  def = defaultRTOptions

-- | Default realtime server options.
defaultRTOptions :: RTOptions
defaultRTOptions = RTOptions {
    -- Network control
    networkPort             = def
  , useZeroconf             = False
  , maxNumberOfLogins       = 16
  , sessionPassword         = Nothing
    -- Audio device control
  , hardwareDeviceName      = Nothing
  , hardwareBufferSize      = 0
  , hardwareSampleRate      = 0
  , inputStreamsEnabled     = Nothing
  , outputStreamsEnabled    = Nothing
  }

-- | Create RTOptions with a specific network port.
--
-- Since 0.8.0
onPort :: NetworkPort -> RTOptions
onPort port = def { networkPort = port }

-- | Create a JACK hardware device name from an optional server name and a
--   client name.
--
-- Since 0.8.0
jackDeviceName ::
    Maybe String  -- ^ Optional JACK server name
 -> String        -- ^ JACK client name
 -> String        -- ^ Hardware device name
jackDeviceName = (++) . maybe "" ((++":"))

-- | Modify options to use a jack device name based on an optional server name
--   and a client name.
--
-- Since 0.8.0
withJackDeviceName ::
    Maybe String  -- ^ Optional JACK server name
 -> String        -- ^ JACK client name
 -> RTOptions     -- ^ Options to modify
 -> RTOptions     -- ^ Modified options
withJackDeviceName serverName clientName options = options {
    hardwareDeviceName = Just (jackDeviceName serverName clientName) }

-- | Default realtime server options (UDP transport).
defaultRTOptionsUDP :: RTOptions
{-# DEPRECATED defaultRTOptionsUDP "Use 'onPort defaultUDPPort' instead" #-}
defaultRTOptionsUDP =  defaultRTOptions { networkPort = defaultUDPPort }

-- | Default realtime server options (TCP transport).
defaultRTOptionsTCP :: RTOptions
{-# DEPRECATED defaultRTOptionsTCP "Use 'onPort defaultTCPPort' instead" #-}
defaultRTOptionsTCP = defaultRTOptions { networkPort = defaultTCPPort }

-- ====================================================================
-- * Non-Realtime options

-- | Non-realtime server options.
data NRTOptions = NRTOptions {
    inputFilePath         :: Maybe FilePath    -- ^ Path to input sound file ('Nothing' for no audio input)
  , outputFilePath        :: FilePath          -- ^ Path to output sound file
  , outputSampleRate      :: Int               -- ^ Output sound file sample rate
  , outputSoundFileFormat :: SoundFileFormat   -- ^ Output sound file header format (since 0.8.0)
  , outputSampleFormat    :: SampleFormat      -- ^ Output sound file sample format
  } deriving (Eq, Show)

instance Default NRTOptions where
  def = defaultNRTOptions

-- | Default non-realtime server options.
defaultNRTOptions :: NRTOptions
defaultNRTOptions = NRTOptions {
    inputFilePath         = Nothing
  , outputFilePath        = "output.wav"
  , outputSampleRate      = 44100
  , outputSoundFileFormat = Wave
  , outputSampleFormat    = PcmInt16
  }