{- | Request and display status information from the synthesis server.

\/status messages receive \/status.reply messages.

\/g_queryTree messages recieve \/g_queryTree.reply messages.
-}
module Sound.Sc3.Server.Status where

import Data.List {- base -}
import Data.Maybe {- base -}
import Text.Printf {- base -}

import qualified Data.ByteString.Char8 as C {- bytestring -}
import qualified Data.Tree as T {- containers -}
import qualified Safe {- safe -}

import Sound.Osc.Datum {- hosc -}
import Sound.Osc.Text {- hosc -}

import Sound.Sc3.Server.Command.Plain

-- * Status

-- | Get /n/th field of /status.reply message as 'Floating'.
extractStatusField :: Floating n => Int -> [Datum] -> n
extractStatusField n =
  fromMaybe (error "extractStatusField")
    . datum_floating
    . flip (Safe.atNote "extractStatusField") n

-- | Names of /status.reply fields sent in reply to /status request.
statusFields :: [String]
statusFields =
  [ "Unused                      "
  , "# Ugens                     "
  , "# Synths                    "
  , "# Groups                    "
  , "# Synthdefs                 "
  , "% CPU (Average)             "
  , "% CPU (Peak)                "
  , "Sample Rate (Nominal)       "
  , "Sample Rate (Actual)        "
  ]

-- | Status pretty printer.
statusFormat :: [Datum] -> [String]
statusFormat d =
  let s = "***** SuperCollider Server Status *****"
      t = Safe.tailNote "statusFormat"
  in s : zipWith (++) (t statusFields) (map (showDatum (Just 5)) (t d))

-- | Concise pretty printer, one line, omits Peak-Cpu and Nominal-Sr.
status_format_concise :: [Datum] -> String
status_format_concise d =
  case d of
    [Int32 _, Int32 ugn, Int32 grp, Int32 syn, Int32 ins, Float cpu1, Float _cpu2, Double _sr1, Double sr2] ->
      printf
        "UGN=%-5d GRP=%-5d SYN=%-5d INS=%-5d CPU=%-5.1f SR=%-7.1f"
        ugn
        grp
        syn
        ins
        cpu1
        sr2
    _ -> error "status_format_concise?"

-- * Query Group

-- | Name or index and value or bus mapping.
type Query_Ctl = (Either String Int, Either Double Int)

-- | Nodes are either groups of synths.
data Query_Node
  = Query_Group Group_Id [Query_Node]
  | Query_Synth Synth_Id String (Maybe [Query_Ctl])
  deriving (Eq, Show)

-- | Pretty-print 'Query_Ctl'
query_ctl_pp :: Query_Ctl -> String
query_ctl_pp (p, q) = either id show p ++ ":" ++ either show show q

-- | Pretty-print 'Query_Node'
query_node_pp :: Query_Node -> String
query_node_pp n =
  case n of
    Query_Group k _ -> show k
    Query_Synth k nm c ->
      let c' = unwords (maybe [] (map query_ctl_pp) c)
      in show (k, nm, c')

{- | Control (parameter) data may be given as names or indices and as
values or bus mappings.

> queryTree_ctl (string "freq",float 440) == (Left "freq",Left 440.0)
> queryTree_ctl (int32 1,string "c0") == (Right 1,Right 0)
-}
queryTree_ctl :: (Datum, Datum) -> Query_Ctl
queryTree_ctl (p, q) =
  let err msg val = error (show ("queryTree_ctl", msg, val))
      f d = case d of
        AsciiString nm -> Left (C.unpack nm)
        Int32 ix -> Right (fromIntegral ix)
        _ -> err "string/int32" d
      g d = case d of
        Float k -> Left (realToFrac k)
        AsciiString b -> case C.unpack b of
          'c' : n -> Right (read n)
          _ -> err "c:_" d
        _ -> err "float/string" d
  in (f p, g q)

{- | If /rc/ is 'True' then 'Query_Ctl' data is expected (ie. flag was set at @\/g_queryTree@).
/k/ is the synth-id, and /nm/ the name.

> let d = [int32 1,string "freq",float 440]
> in queryTree_synth True 1000 "saw" d
-}
queryTree_synth :: Bool -> Synth_Id -> String -> [Datum] -> (Query_Node, [Datum])
queryTree_synth rc k nm d =
  let pairs l = case l of
        e0 : e1 : l' -> (e0, e1) : pairs l'
        _ -> []
      f r = case r of
        Int32 n : r' ->
          let (p, r'') = genericSplitAt (n * 2) r'
          in (map queryTree_ctl (pairs p), r'')
        _ -> error "queryTree_synth"
  in if rc
      then
        let (p, d') = f d
        in (Query_Synth k nm (Just p), d')
      else (Query_Synth k nm Nothing, d)

-- | Generate 'Query_Node' for indicated 'Group_Id'.
queryTree_group :: Bool -> Group_Id -> Int -> [Datum] -> (Query_Node, [Datum])
queryTree_group rc gid nc =
  let recur n r d =
        if n == 0
          then (Query_Group gid (reverse r), d)
          else
            let (c, d') = queryTree_child rc d
            in recur (n - 1) (c : r) d'
  in recur nc []

-- | Either 'queryTree_synth' or 'queryTree_group'.
queryTree_child :: Bool -> [Datum] -> (Query_Node, [Datum])
queryTree_child rc d =
  case d of
    Int32 nid : Int32 (-1) : AsciiString nm : d' ->
      queryTree_synth rc (fromIntegral nid) (C.unpack nm) d'
    Int32 gid : Int32 nc : d' ->
      queryTree_group rc (fromIntegral gid) (fromIntegral nc) d'
    _ -> error "queryTree_child"

-- | Parse result of ' g_queryTree '.
queryTree :: [Datum] -> Query_Node
queryTree d =
  case d of
    Int32 rc : Int32 gid : Int32 nc : d' ->
      let rc' = rc /= 0
          gid' = fromIntegral gid
          nc' = fromIntegral nc
      in case queryTree_group rc' gid' nc' d' of
          (r, []) -> r
          _ -> error "queryTree"
    _ -> error "queryTree"

-- | Extact sequence of 'Group_Id's from 'Query_Node'.
queryNode_to_group_seq :: Query_Node -> [Group_Id]
queryNode_to_group_seq nd =
  case nd of
    Query_Group k ch -> k : concatMap queryNode_to_group_seq ch
    Query_Synth _ _ _ -> []

-- | Transform 'Query_Node' to 'T.Tree'.
queryTree_rt :: Query_Node -> T.Tree Query_Node
queryTree_rt n =
  case n of
    Query_Synth _ _ _ -> T.Node n []
    Query_Group _ c -> T.Node n (map queryTree_rt c)
