content: -- | Vivid-specific OSC Bundle encoding
  
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  module Vivid.OSC.Bundles (
       encodeOSCBundles
     , initTreeCommand
     ) where
  
  import Vivid.OSC
  -- import Vivid.OSC.Old (encodedOSC_addLength)
  import qualified Vivid.SC.Server.Commands as SCCmd
  import Vivid.SC.Server.Types (NodeId(..))
  
  import Data.ByteString (ByteString)
  import qualified Data.List as L
  import Data.Monoid
  
  -- TEMP:
  import Data.Word
  import qualified Data.ByteString.Lazy as BSL
  import qualified Data.ByteString as BS
  import Data.Binary (encode)
  encodedOSC_addLength :: ByteString -> ByteString
  encodedOSC_addLength bs =
     BSL.toStrict (encode (toEnum (BS.length bs) :: Word32)) <> bs
  
  
  -- | Encode OSC bundles, specifically for NRT synthesis.
  --   (It's more than just \"mconcat . map 'encodeOSCBundle'\").
  -- 
  --   Note also that the last action is when the song ends - so if you want
  --   e.g. a note to hold at the end you need to add a "wait"
  encodeOSCBundles :: [OSCBundle] -> ByteString
  encodeOSCBundles bundles =
     mconcat . map (encodedOSC_addLength . encodeOSCBundle) $ withEnd
   where
     sortedBundles :: [OSCBundle]
     sortedBundles =
        L.sortBy (\(OSCBundle t0 _) (OSCBundle t1 _) -> compare t0 t1) bundles
     sortedBundlesWithDefinitionsFirst :: [OSCBundle]
     sortedBundlesWithDefinitionsFirst =
        map putDefinitionsFirst joinedByTime
      where
        -- Note we aren't assuming there aren't bundles with the same timestamp.
        -- (Which isnt an issue if we got the bundles with 'runNRT', but it's good
        -- to check):
        joinedByTime :: [OSCBundle]
        joinedByTime =
           (flip map) groupedByTime $ \case
              as@(OSCBundle t _:_) ->
                 OSCBundle t (concatMap (\(OSCBundle _ a) -> a) as)
              [] -> error "Should be impossible"
        groupedByTime :: [[OSCBundle]]
        groupedByTime =
           L.groupBy (\(OSCBundle t0 _) (OSCBundle t1 _) -> t1 == t0) sortedBundles
  
        -- If there are "/d_recv" actions and other actions at the same timestamp, we
        -- put the "/d_recv"s before the other actions:
        putDefinitionsFirst :: OSCBundle -> OSCBundle
        putDefinitionsFirst (OSCBundle t actions) = OSCBundle t $ (\(a,b)->a<>b) $
           (flip L.partition) actions $ \case
              Right (OSC "/d_recv" _) -> True
              _ -> False
     lastTimestamp = (\(OSCBundle t _) ->t) $ last sortedBundles
  
     withEnd = mconcat [
         [OSCBundle (Timestamp 0) [Right initTreeCommand]]
        ,sortedBundlesWithDefinitionsFirst
        ,[OSCBundle lastTimestamp [Right $ OSC "" []]]
        ]
  
  
  
  initTreeCommand :: OSC
  initTreeCommand =
     SCCmd.g_new (NodeId 1) SCCmd.AddToHead (NodeId 0)
  
  