module Sound.Signal.Block where

import Data.Array (Array, (!), listArray)

import qualified Sound.Signal as Signal
import qualified Synthesizer.Plain.Signal as ListSignal

import qualified Data.List as List

import Data.Maybe.HT (toMaybe)
import Prelude hiding ((++), iterate, foldl, zipWith, tail, head)


instance Signal.C T where
   singleton = singleton
   unfoldR   = unfoldR defaultChunkSize
   reduceL   = reduceL
   mapAccumL = mapAccumL defaultChunkSize
   (++)      = append
   zipWith   = zipWith defaultChunkSize


type ChunkSize = Int

defaultChunkSize :: ChunkSize
defaultChunkSize = 256

newtype T a = Cons {
   chunks :: [Chunk a]
  }
   deriving (Show)

{- |
The array starts with index 0.
We always consider a subarray of 'body'
starting at 'offset' with size 'size'.
This way we safe copy operations
and we can efficiently 'drop', 'take' and 'append' chunk lists.
Unfortunately, 'Data.Array' does not provide subarrays with sharing.
Every chunk must have at least size 1.
-}
data Chunk a = Chunk {
   offset :: Int,
   size   :: ChunkSize,
   body   :: Array Int a
  }
   deriving (Show)


singleton :: a -> T a
singleton x = Cons [Chunk 0 1 (listArray (0,0) [x])]

isEmpty :: T a -> Bool
isEmpty (Cons x) = null x

head :: T a -> a
head (Cons xt) =
   case xt of
      [] -> error "Signal.Block.head: empty list"
      (Chunk start _ arr : _) -> arr ! start

tail :: T a -> T a
tail (Cons xt) =
   case xt of
      [] -> error "Signal.Block.tail: empty list"
      (Chunk start sz arr : xs) -> Cons
          (if sz>1
             then Chunk (succ start) (pred sz) arr : xs
             else xs)

tails :: T a -> [T a]
tails =
   List.unfoldr
      (\x -> toMaybe (not (isEmpty x))
         (let tailX = tail x in (tailX,tailX)))

toList :: T a -> [a]
toList =
   List.concatMap
      (\(Chunk start sz arr) ->
           take sz (map (arr!) [start..])) . chunks

toListAlt :: T a -> [a]
toListAlt = List.init . map head . tails

fromList :: ChunkSize -> [a] -> T a
fromList chunkSize =
   let recourse [] = []
       recourse xs =
          let actSize = minLength chunkSize xs
          in  Chunk 0 actSize (listArray (0,actSize-1) xs) :
                if actSize < chunkSize
                  then []
                  else recourse (drop chunkSize xs)
   in  Cons . recourse

{-
@minLength n x = min n (length x)@,
but 'minLength' is more lazy than 'length'.
-}
minLength :: Int -> [a] -> Int
minLength =
   let recourse seenSoFar expected xt =
          case xt of
             [] -> seenSoFar
             (_:xs) ->
                 if expected == 0
                   then seenSoFar
                   else recourse (succ seenSoFar) (pred expected) xs
   in  recourse 0

{-
poor man's implementation via lists
I do not know which array function could be of help here.
-}
unfoldR :: ChunkSize -> (acc -> Maybe (y, acc)) -> acc -> (acc, T y)
unfoldR chunkSize f acc =
   let (accEnd, xs) = ListSignal.unfoldR f acc
   in  (accEnd, fromList chunkSize xs)

reduceL :: (a -> acc -> Maybe acc) -> acc -> T a -> acc
reduceL f start =
   ListSignal.reduceL f start . toList
{- when running on array separately it would be complicated
to distinguish between termination because the signal is finished
and because the abort condition is fulfilled. -}
--   List.foldl' (\acc -> List.reduceL f acc . elems) start . toChunkList


mapAccumL :: ChunkSize ->
   (x -> acc -> Maybe (y, acc)) -> acc -> T x -> (acc, T y)
mapAccumL chunkSize f accStart xs =
   let (accEnd, ys) = ListSignal.mapAccumL f accStart (toList xs)
   in  (accEnd, fromList chunkSize ys)


append :: T a -> T a -> T a
append (Cons x) (Cons y) = Cons (x List.++ y)

zipWith :: ChunkSize -> (a -> b -> c) -> (T a -> T b -> T c)
zipWith chunkSize f x y =
   fromList chunkSize $
   List.zipWith f (toList x) (toList y)