module Sound.Jammit.Internal.Sox
( Audio(..)
, Time(..)
, renderAudio
, optimize
) where

import Control.Arrow (first)
import Control.Monad (void, forM, guard)

import System.Process (readProcess)

import Sound.Jammit.Internal.TempFile

data Audio
  = Empty                 -- ^ An empty stereo file
  | File FilePath         -- ^ An existing (stereo) file
  | Pad Time Audio        -- ^ Pad audio start with silence
  | Mix [(Double, Audio)] -- ^ Add audio sample-wise, also multiplying volumes
  | Concat [Audio]        -- ^ Sequentially connect audio
  deriving (Eq, Ord, Show, Read)

data Time
  = Seconds Double
  | Samples Integer
  deriving (Eq, Ord, Show, Read)

showTime :: Time -> String
showTime (Seconds d) = show d
showTime (Samples i) = show i ++ "s"

renderAudio :: Audio -> TempIO FilePath
renderAudio aud = case aud of
  Empty -> do
    fout <- newTempFile "render.wav"
    void $ liftIO $
      readProcess "sox" (["-n", fout] ++ words "trim 0 0 channels 2") ""
    return fout
  File f -> return f
  Pad t x -> do
    fin <- renderAudio x
    fout <- newTempFile "render.wav"
    void $ liftIO $ readProcess "sox" [fin, fout, "pad", showTime t] ""
    return fout
  Mix xs -> case xs of
    [] -> renderAudio Empty
    [(d, x)] -> do
      fin <- renderAudio x
      fout <- newTempFile "render.wav"
      void $ liftIO $
        readProcess "sox" ["-v", show d, fin, fout] ""
      return fout
    _ -> do
      dfins <- forM xs $ \(d, x) -> do
        fin <- renderAudio x
        return (d, fin)
      let argsin = concatMap
            (\(d, fin) -> ["-v", show d, fin]) dfins
      fout <- newTempFile "render.wav"
      void $ liftIO $
        readProcess "sox" (["--combine", "mix"] ++ argsin ++ [fout]) ""
      return fout
  Concat xs -> case xs of
    [] -> renderAudio Empty
    _ -> do
      fins <- mapM renderAudio xs
      fout <- newTempFile "render.wav"
      void $ liftIO $ readProcess "sox" (fins ++ [fout]) ""
      return fout

optimize :: Audio -> Audio
optimize aud = case aud of
  Pad (Samples 0) x -> x
  Pad (Seconds 0) x -> x
  Mix xs -> let
    xs' = do
      (d, x) <- xs
      guard $ d /= 0
      case optimize x of
        Mix ys -> map (first (* d)) ys
        x'     -> [(d, x')]
    in case xs' of
      []       -> Empty
      [(1, x)] -> x
      _        -> Mix xs'
  Concat xs -> let
    xs' = do
      x <- xs
      case optimize x of
        Concat ys -> ys
        x'        -> [x']
    in case xs' of
      []  -> Empty
      [x] -> x
      _   -> Concat xs'
  _ -> aud