{-# OPTIONS -fno-implicit-prelude -fglasgow-exts #-}
{- |

Copyright   :  (c) Henning Thielemann 2006, 2008
License     :  GPL

Maintainer  :  synthesizer@henning-thielemann.de
Stability   :  provisional
Portability :  requires multi-parameter type classes

-}
module Synthesizer.Inference.Func.Signal where

import qualified Synthesizer.Physical.Signal as SigP
import qualified Synthesizer.SampleRateContext.Signal as SigC

-- import qualified Algebra.OccasionallyScalar as OccScalar
-- import qualified Algebra.Module         as Module
-- import qualified Algebra.Field          as Field
-- import qualified Algebra.Ring           as Ring

-- import Algebra.OccasionallyScalar (toScalar)

import Control.Monad.Fix (fix)
import Data.Maybe (catMaybes, isJust)
import Data.List  (transpose)
import NumericPrelude.List (shearTranspose)

-- import NumericPrelude
import PreludeBase as P

{- |
Each process must work the following way:
If the signal processor has a fixed sample rate or amplitude
either implied by its parameters or its inputs
then this parameter should be set as @Just@
in the corresponding fields of @SigP.T@.
These fields must be computed
independently from the function argument of type @(t',y')@.
This function argument is the pair of eventually used signal parameters
sample rate and amplitude.
If you set signal parameters to @Just@ with a value,
then you can expect that the corresponding pair member has the same value.
-}
newtype T t t' y y' yv =
   Cons {eval :: (t',y') -> Evaluated t t' y y' yv}

type Evaluated t t' y y' yv = SigP.T t (Parameter t') y (Parameter y') yv
{- |
Since all 'Just' values must contain the same value,
we could also use the data structure '(Peano, a)'
just like in the @unique-logic@ package.
-}
newtype Parameter a = Parameter {parameterDesc :: [Maybe a]}

liftParam2 ::
   ([Maybe a] -> [Maybe b] -> [Maybe c]) ->
   Parameter a -> Parameter b -> Parameter c
liftParam2 f (Parameter x) (Parameter y) = Parameter (f x y)

cons :: ((t',y') -> SigP.T t (Parameter t') y (Parameter y') yv) -> T t t' y y' yv
cons = Cons


contextFixAmplitude ::
      y'
   -> Evaluated t t' y y' yv
   -> SigC.T y y' yv
contextFixAmplitude amp =
   SigC.replaceAmplitude amp . SigP.content

fromContextFreeAmplitude ::
      Parameter t'
   -> SigC.T y y' yv
   -> Evaluated t t' y y' yv
fromContextFreeAmplitude sr (SigC.Cons _amp ss) =
   SigP.cons sr anyParameter ss

fromContextCheckAmplitude :: (Eq y') =>
      Parameter t'
   -> y'
   -> SigC.T y y' yv
   -> Evaluated t t' y y' yv
fromContextCheckAmplitude sr iamp (SigC.Cons amp ss) =
   SigP.cons sr (justParameter amp)
      (if iamp==amp then ss else error "fromContextCheckAmplitude: amplitudes differ")


anyParameter :: Parameter q
anyParameter = Parameter []

justParameter :: q -> Parameter q
justParameter x = Parameter [Just x]

inSampleRate :: (t',y') -> t'
inSampleRate = fst

inAmplitude :: (t',y') -> y'
inAmplitude = snd



{-
vectorSamples :: (Eq t', Module.C y yv) =>
   (y' -> y) -> T t t' y y' yv -> (t' -> [yv])
vectorSamples toAmpScalar sig =
   \inferedSampleRate ->
      let x'   = eval sig (inferedSampleRate, amp')
          amp' = guessParameter
                    "vectorSamples: input amplitude"
                    (SigP.amplitude x')
          amp = toAmpScalar amp' `SigP.asTypeOfAmplitude` x'
      in  amp *> SigP.samples x'

scalarSamples :: (Eq t', Ring.C y) =>
   (y' -> y) -> T t t' y y' y -> (t' -> [y])
scalarSamples toAmpScalar sig =
   \inferedSampleRate ->
      let x'  = sig (inferParameter inferedSampleRate (SigP.sampleRate x'),
                     amp')
          amp' = fromMaybe (error "scalarSamples: undetermined input amplitude")
                           (SigP.amplitude x')
          amp = toAmpScalar amp' `SigP.asTypeOfAmplitude` x'
      in  map (amp*) (SigP.samples x')



inferParameter :: Eq q => q -> Maybe q -> q
inferParameter infered =
   maybe infered
      (\x -> if x == infered
               then x
               else error ("inferParameter:" ++
                           " requested value and infered one differ"))
-}

equalParameter :: Eq q => String -> Maybe q -> Maybe q -> Maybe q
equalParameter name x y =
   case (x,y) of
      (Nothing,_) -> y
      (_,Nothing) -> x
      (Just xv, Just yv) ->
         if xv == yv
           then Just xv
           else error ("equalParameter: " ++ name ++ " differ")

equalSampleRate :: Eq t' => Maybe t' -> Maybe t' -> Maybe t'
equalSampleRate = equalParameter "sample rate"


zipJut :: (a -> a -> a) -> [a] -> [a] -> [a]
zipJut f =
   let aux (x:xs) (y:ys) = f x y : aux xs ys
       aux []     ys     = ys
       aux xs     []     = xs
   in  aux

{-|
  Merge the @Just@s of two lists.
  It does not check for validity of the data.
-}
mergeParameter :: Parameter q -> Parameter q -> Parameter q
mergeParameter =
   liftParam2 (zipJut (\x y -> if isJust x then x else y))

mergeSampleRate ::
   Evaluated t t' y0 y0' yv0 -> Evaluated t t' y1 y1' yv1 -> Parameter t'
mergeSampleRate x y =
   mergeParameter (SigP.sampleRate x) (SigP.sampleRate y)


mergeParameterEq :: Eq q => String -> Parameter q -> Parameter q -> Parameter q
mergeParameterEq name =
   liftParam2 (zipJut (equalParameter name))

mergeSampleRateEq :: Eq t' => Parameter t' -> Parameter t' -> Parameter t'
mergeSampleRateEq = mergeParameterEq "sample rate"

-- cf. Examples.merge
merge :: [a] -> [a] -> [a]
merge (x:xs) ys = x : merge ys xs
merge []     ys = ys

propMerge :: Eq a => [a] -> [a] -> Bool
propMerge xs ys  =  merge xs ys == concat (transpose [xs,ys])

mergeParameter' :: Parameter t' -> Parameter t' -> Parameter t'
mergeParameter' = liftParam2 merge

checkParameter :: Eq q => String -> q -> Maybe q -> q
checkParameter name x =
   maybe x (\y -> if x == y
                    then x
                    else error ("checkParameter: deviation from common " ++ name))

checkSampleRate :: Eq t' => t' -> Maybe t' -> t'
checkSampleRate = checkParameter "sample rate"

checkAmplitude :: Eq y' => y' -> Maybe y' -> y'
checkAmplitude = checkParameter "amplitude"


{-|
  This routine is prepared for infinite lists.
  In order to handle them we employ a Cantor diagonalization scheme.
  It does not check for validity of the data
  (i.e. equal @Just@ values)
  but it does only keep some @Just@s,
  and thus allows for a quick search of a guess of a parameter value.
-}
mergeParameters :: [Parameter q] -> Parameter q
mergeParameters =
   Parameter . map (head . (++[Nothing]) . filter isJust)
      . shearTranspose . map parameterDesc

mergeSampleRates :: [Evaluated t t' y y' yv] -> Parameter t'
mergeSampleRates =
   mergeParameters . map SigP.sampleRate

mergeParametersEq :: Eq q => String -> [Parameter q] -> Parameter q
mergeParametersEq name =
   Parameter . map (foldl (equalParameter name) Nothing)
      . shearTranspose . map parameterDesc

mergeSampleRatesEq :: Eq t' => [Parameter t'] -> Parameter t'
mergeSampleRatesEq = mergeParametersEq "sample rate"

{- |
This is a simple working version of 'mergeParameters',
which does not need @Eq@ constraint.
However, flattening a three-dimensional list
does handle different dimensions differently,
that is slower than the others.
-}
mergeParameters' :: [Parameter q] -> Parameter q
mergeParameters' =
   Parameter . concat . shearTranspose . map parameterDesc


{-
equalParameters :: Eq q => String -> [Parameter q] -> Parameter q
equalParameters name xs =
   let cxs = catMaybes xs
   in  if and (zipWith (==) cxs (tail cxs))
         then listToMaybe cxs
         else error ("equalParameters: " ++ name ++ " differ")

equalSampleRates :: Eq t' => [Maybe t'] -> Maybe t'
equalSampleRates = equalParameters "sample rates"
-}

guessParameter :: String -> Parameter q -> q
guessParameter context =
   head . (++ error (context ++ " undetermined")) . catMaybes . parameterDesc

guessSampleRate :: Evaluated t t' y y' yv -> t'
guessSampleRate = guessParameter "sample rate" . SigP.sampleRate

guessAmplitude :: Evaluated t t' y y' yv -> y'
guessAmplitude = guessParameter "amplitude" . SigP.amplitude



{- |
  A complex signal graph can be built without ever mentioning a sampling rate.
  However when it comes to playing or writing a file,
  we must determine the sampling rate eventually.
  This function simply passes a signal through
  while forcing it to the given sampling rate.
-}
fixSampleRate :: (Eq t') =>
      t'                {-^ sample rate -}
   -> T t t' y y' yv    {-^ passed through signal -}
   -> T t t' y y' yv
fixSampleRate forcedSampleRate input =
   Cons $ \infered ->
      let inputSig = eval input infered
      in  SigP.cons
             (justParameter forcedSampleRate)
             (SigP.amplitude inputSig)
             (if inSampleRate infered == forcedSampleRate
                then SigP.samples inputSig
                else error "fixSampleRate: sampleRates differ")

-- ***** Is this one correct? Has the usage of 'infered' a cycle?
{- | Create a loop (feedback) from one node to another one.
     That is, compute the fix point of a process iteration. -}
loop :: (Eq t') =>
      (T t t' y y' yv -> T t t' y y' yv)
                        {-^ process chain that shall be looped -}
   ->  T t t' y y' yv
loop f =
   fix (\x -> f (Cons $ \infered ->
          SigP.cons anyParameter anyParameter
                    (SigP.samples (eval x infered))))

-- example: loop (\y -> x + delay y)