-- | Utility function over Ugen data structure.
module Sound.Sc3.Ugen.Util where

import qualified Data.Char {- base -}
import Data.List {- base -}
import Data.Maybe {- base -}

import qualified Data.List.Split as Split {- split -}

import qualified Sound.Sc3.Common.Base as Base {- hsc3 -}
import qualified Sound.Sc3.Common.Envelope as Envelope {- hsc3 -}
import qualified Sound.Sc3.Common.Mce as Mce {- hsc3 -}
import qualified Sound.Sc3.Common.Rate as Rate {- hsc3 -}
import qualified Sound.Sc3.Common.Uid as Uid {- hsc3 -}

import Sound.Sc3.Ugen.Brackets {- hsc3 -}
import Sound.Sc3.Ugen.Control {- hsc3 -}
import Sound.Sc3.Ugen.Label {- hsc3 -}
import Sound.Sc3.Ugen.Mrg {- hsc3 -}
import Sound.Sc3.Ugen.Primitive {- hsc3 -}
import Sound.Sc3.Ugen.Proxy {- hsc3 -}
import Sound.Sc3.Ugen.Ugen {- hsc3 -}

-- | 'Uid' of 'resolveID'.
toUid :: Uid.ID a => a -> UgenId
toUid = Uid . Uid.resolveID

-- * Ugen graph functions

-- | Depth first traversal of graph at `u', stopping at `halt_f', else applying `map_f' to each node.
ugenTraverse :: (Ugen -> Bool) -> (Ugen -> Ugen) -> Ugen -> Ugen
ugenTraverse halt_f map_f u =
  if halt_f u
    then u
    else
      let recur = ugenTraverse halt_f map_f
      in case u of
          Primitive_U p ->
            let i = ugenInputs p
            in map_f (Primitive_U (p {ugenInputs = map recur i}))
          Proxy_U (Proxy p ix) ->
            let i = ugenInputs p
            in map_f (Proxy_U (Proxy (p {ugenInputs = map recur i}) ix))
          Mce_U m -> map_f (mce (map recur (mceProxies m)))
          Mrg_U (Mrg l r) -> map_f (Mrg_U (Mrg (recur l) (recur r)))
          _ -> map_f u

{- | Right fold of Ugen graph.

> import qualified Sound.Sc3.Ugen.Pp as Pp
> let pp = Pp.ugen_concise_pp
> map pp (ugenFoldr (:) [] (sinOsc ar 440 0 * 0.1))
["*","SinOsc","440","0","0.1"]

> map pp (ugenFoldr (:) [] (pan2 (sinOsc ar 440 0) 0.25 0.1))
["[Pan2@0,Pan2@1]","Pan2@0","SinOsc","440","0","0.25","0.1","Pan2@1","SinOsc","440","0","0.25","0.1"]
-}
ugenFoldr :: (Ugen -> a -> a) -> a -> Ugen -> a
ugenFoldr f st u =
  let recur = flip (ugenFoldr f)
  in case u of
      Primitive_U p -> f u (foldr recur st (ugenInputs p))
      Proxy_U (Proxy p _) -> f u (foldr recur st (ugenInputs p)) -- ...
      Mce_U m -> f u (foldr recur st (mceProxies m))
      Mrg_U (Mrg l r) -> f u (f l (f r st))
      _ -> f u st

-- | Fold over Ugen and collect all bracketing messages from all Primitive nodes.
ugenCollectBrackets :: Ugen -> Brackets
ugenCollectBrackets =
  concatBrackets
    . map ugenBrackets
    . nub
    . ugenFoldr (:) []

-- | Are there any brackets at Ugen.
ugenHasAnyBrackets :: Ugen -> Bool
ugenHasAnyBrackets = (/= ([], [])) . ugenCollectBrackets

-- * Unit generator node constructors

-- | Control input node constructor.
control_f64 :: Rate.Rate -> Maybe Int -> String -> Sample -> Ugen
control_f64 r ix nm d = Control_U (Control r ix nm d False Nothing emptyBrackets)

{- | Control input node constructor.

Note that if the name begins with a t_ prefix the control is /not/
converted to a triggered control.  Please see 'trigControl'.
-}
control :: Rate.Rate -> String -> Double -> Ugen
control r = control_f64 r Nothing

-- | Variant of 'control' with meta data.
control_m :: Rate.Rate -> String -> Double -> Control_Meta_T3 Double -> Ugen
control_m rt nm df meta =
  let m = control_meta_t3 id meta
  in Control_U (Control rt Nothing nm df False (Just m) emptyBrackets)

-- | Generate group of two controls.  Names are generated according to 'control_group_suffixes'
control_pair :: Control_Group -> Rate.Rate -> String -> (Double, Double) -> Control_Meta_T3 Double -> (Ugen, Ugen)
control_pair grp rt nm (df1, df2) meta =
  let m = (control_meta_t3 id meta) {controlGroup = Just grp}
  in case control_group_suffixes grp of
      [lhs, rhs] ->
        ( Control_U (Control rt Nothing (nm ++ lhs) df1 False (Just m) emptyBrackets)
        , Control_U (Control rt Nothing (nm ++ rhs) df2 False (Just m) emptyBrackets)
        )
      _ -> error "control_pair"

-- | Generate range controls.  Names are generated according to 'control_group_suffixes'
control_rng :: Rate.Rate -> String -> (Double, Double) -> Control_Meta_T3 Double -> (Ugen, Ugen)
control_rng = control_pair Control_Range

-- | Triggered (kr) control input node constructor.
trigControl_f64 :: Maybe Int -> String -> Sample -> Ugen
trigControl_f64 ix nm d = Control_U (Control Rate.ControlRate ix nm d True Nothing emptyBrackets)

-- | Triggered (kr) control input node constructor.
trigControl :: String -> Double -> Ugen
trigControl = trigControl_f64 Nothing

-- | Set indices at a list of controls.
control_set :: [Ugen] -> [Ugen]
control_set =
  let f ix u = case u of
        Control_U c -> Control_U (c {controlIndex = Just ix})
        _ -> error "control_set: non control input?"
  in zipWith f [0 ..]

-- * Multiple channel expansion

mce1 :: Ugen -> Ugen
mce1 = mce . return

-- | Multiple channel expansion for two inputs.
mce2 :: Ugen -> Ugen -> Ugen
mce2 x y = mce [x, y]

-- | Extract two channels from possible Mce, if there is only one channel it is duplicated.
mce2c :: Ugen -> (Ugen, Ugen)
mce2c u =
  case mceChannels u of
    [] -> error "mce2c"
    [p] -> (p, p)
    p : q : _ -> (p, q)

-- | Variant of 'mce2c' that requires input to have two channels.
unmce2 :: Ugen -> (Ugen, Ugen)
unmce2 = Base.t2_from_list . mceChannels

-- | Multiple channel expansion for two inputs.
mce3 :: Ugen -> Ugen -> Ugen -> Ugen
mce3 x y z = mce [x, y, z]

-- | Variant of 'mce2c' that requires input to have two channels.
unmce3 :: Ugen -> (Ugen, Ugen, Ugen)
unmce3 = Base.t3_from_list . mceChannels

-- | Apply a function to each channel at a unit generator.
mceMap :: (Ugen -> Ugen) -> Ugen -> Ugen
mceMap f u = mce (map f (mceChannels u))

-- | Map with element index.
map_ix :: ((Int, a) -> b) -> [a] -> [b]
map_ix f = zipWith (curry f) [0 ..]

-- | Variant of 'mceMap' with element index.
mce_map_ix :: ((Int, Ugen) -> Ugen) -> Ugen -> Ugen
mce_map_ix f u = mce (map_ix f (mceChannels u))

-- | Apply Ugen list operation on Mce contents.
mceEdit :: ([Ugen] -> [Ugen]) -> Ugen -> Ugen
mceEdit f u =
  case u of
    Mce_U m -> mce (f (mceProxies m))
    _ -> error "mceEdit: non Mce value"

-- | Reverse order of channels at Mce.
mceReverse :: Ugen -> Ugen
mceReverse = mceEdit reverse

-- | Obtain indexed channel at Mce.
mceChannel :: Int -> Ugen -> Ugen
mceChannel n u =
  case u of
    Mce_U m -> mceProxies m !! n
    _ -> if n == 0 then u else error "mceChannel: non Mce value, non ZERO index"

{- | Obtain indexed channel at Mce, indices wrap around.

> map (\ix -> pp (mceChannelWrap ix (mce [1,2,3,4,5]))) [0 .. 9]
["1","2","3","4","5","1","2","3","4","5"]
-}
mceChannelWrap :: Int -> Ugen -> Ugen
mceChannelWrap n u =
  case u of
    Mce_U m -> mceProxies m !! (n `mod` Mce.mce_length m)
    _ -> u

-- | Transpose rows and columns, ie. {{a,b},{c,d}} to {{a,c},{b,d}}.
mceTranspose :: Ugen -> Ugen
mceTranspose = mce . map mce . transpose . map mceChannels . mceChannels

{- | Rotate mce /k/ places to the right, ie. {a,b,c,d} to {d,a,b,c}

>>> mceRotate 1 (mce [1,2,3,4]) == mce [4,1,2,3]
True
-}
mceRotate :: Int -> Ugen -> Ugen
mceRotate k =
  let rotateRight n p = let (b, a) = splitAt (length p - n) p in a ++ b
  in mce . rotateRight k . mceChannels

{- | 'concat' at mce channels of each input, ie. {{a,b},{c,d}} to {a,b,c,d}.

>>> mceConcat (map mce [[1,2],[3,4]]) == mce [1..4]
True
-}
mceConcat :: [Ugen] -> Ugen
mceConcat = mce . concatMap mceChannels

{- | Collect subarrays of mce.

>>> mceClump 2 (mce [1,2,3,4]) == mce2 (mce2 1 2) (mce2 3 4)
True
-}
mceClump :: Int -> Ugen -> Ugen
mceClump k = mce . map mce . Split.chunksOf k . mceChannels

-- | Foldl1 at channels of mce.
mceReduce :: (Ugen -> Ugen -> Ugen) -> Ugen -> Ugen
mceReduce f = foldl1 f . mceChannels

-- | mceReduce of *.
mceProduct :: Ugen -> Ugen
mceProduct = mceReduce (*)

-- * Transform

-- | Given /unmce/ function make halt mce transform.
halt_mce_transform_f :: (a -> [a]) -> [a] -> [a]
halt_mce_transform_f f l =
  let (l', e) = fromMaybe (error "halt_mce_transform: null?") (Base.sep_last l)
  in l' ++ f e

{- | The halt Mce transform, ie. lift channels of last input into list.
This is not used by hsc3, but it is used by hsc3-forth and stsc3.

>>> halt_mce_transform [1,2,mce2 3 4] == [1,2,3,4]
True
-}
halt_mce_transform :: [Ugen] -> [Ugen]
halt_mce_transform = halt_mce_transform_f mceChannels

-- | If the root node of a Ugen graph is /mce/, transform to /mrg/.
prepare_root :: Ugen -> Ugen
prepare_root u =
  case u of
    Mce_U m -> mrg (mceProxies m)
    Mrg_U m -> mrg2 (prepare_root (mrgLeft m)) (prepare_root (mrgRight m))
    _ -> u

-- * Multiple root graphs

-- | Multiple root graph node constructor (left input is output)
mrg2 :: Ugen -> Ugen -> Ugen
mrg2 u = Mrg_U . Mrg u

-- * Labels

-- | Lift a 'String' to a Ugen label (ie. for 'poll').
label :: String -> Ugen
label = Label_U . Label

{- | Unpack a label to a length prefixed list of 'Constant's.  There
is a special case for mce nodes, but it requires labels to be equal
length.  Properly, 'poll' would not unpack the label, it would be
done by the synthdef builder.

> unpackLabel False (label "/tmp")
-}
unpackLabel :: Bool -> Ugen -> [Ugen]
unpackLabel length_prefix u =
  case u of
    Label_U (Label s) ->
      let q = fromEnum '?'
          f c = if Data.Char.isAscii c then fromEnum c else q
          s' = map (fromIntegral . f) s
      in if length_prefix then fromIntegral (length s) : s' else s'
    Mce_U m ->
      let x = map (unpackLabel length_prefix) (mceProxies m)
      in if Base.equal_length_p x
          then map mce (transpose x)
          else error (show ("unpackLabel: mce length /=", x))
    _ -> error (show ("unpackLabel: non-label", u))

-- * Envelope

-- | 'mce' of 'Envelope.envelope_sc3_array'.
envelope_to_ugen :: Envelope.Envelope Ugen -> Ugen
envelope_to_ugen =
  let err = error "envGen: bad Envelope"
  in mce . fromMaybe err . Envelope.envelope_sc3_array

-- | 'mce' of 'Envelope.envelope_sc3_ienvgen_array'.
envelope_to_ienvgen_ugen :: Envelope.Envelope Ugen -> Ugen
envelope_to_ienvgen_ugen =
  let err = error "envGen: bad Envelope"
  in mce . fromMaybe err . Envelope.envelope_sc3_ienvgen_array

-- * Rate Flow

-- | Traverse graph rewriting audio rate nodes as control rate.
rewriteUgenRates :: (Rate.Rate -> Bool) -> Rate.Rate -> Ugen -> Ugen
rewriteUgenRates sel_f set_rt =
  let f u = case u of
        Primitive_U (Primitive rt nm i o s z b) -> Primitive_U (Primitive (if sel_f rt then set_rt else rt) nm i o s z b)
        _ -> u
  in ugenTraverse (const False) f -- requires endRewrite node (see rsc3-arf)

-- | Traverse graph rewriting audio rate nodes as control rate.
rewriteToControlRate :: Ugen -> Ugen
rewriteToControlRate = rewriteUgenRates (== Rate.AudioRate) Rate.ControlRate

-- | Traverse graph rewriting all nodes as demand rate.
rewriteToDemandRate :: Ugen -> Ugen
rewriteToDemandRate = rewriteUgenRates (const True) Rate.DemandRate

-- | Traverse graph rewriting audio and control nodes as initialisation rate.
rewriteToInitialisationRate :: Ugen -> Ugen
rewriteToInitialisationRate = rewriteUgenRates (`elem` [Rate.ControlRate, Rate.AudioRate]) Rate.InitialisationRate

-- | Select rewriting function given 'Rate.Rate'.
rewriteToRate :: Rate.Rate -> Ugen -> Ugen
rewriteToRate rt =
  case rt of
    Rate.ControlRate -> rewriteToControlRate
    Rate.DemandRate -> rewriteToDemandRate
    Rate.InitialisationRate -> rewriteToInitialisationRate
    Rate.AudioRate -> error "rewriteToRate: AudioRate?"
