module Nes.APU.State.LengthCounter (
    LengthCounter (..),
    newLengthCounter,
    tickLengthCounter,
    loadLengthCounter,
    clearAndHaltLengthCounter,
    enableLengthCounter,
    disableLengthCounter,

    -- * Class
    HasLengthCounter (..),
    withLengthCounter,
    isSilencedByLengthCounter,
) where

import Data.List ((!?))

data LengthCounter = MkLC
    { LengthCounter -> Int
remainingLength :: {-# UNPACK #-} !Int
    , LengthCounter -> Bool
isHalted :: {-# UNPACK #-} !Bool
    , LengthCounter -> Bool
isEnabled :: {-# UNPACK #-} !Bool
    }

newLengthCounter :: LengthCounter
newLengthCounter :: LengthCounter
newLengthCounter = Int -> Bool -> Bool -> LengthCounter
MkLC Int
0 Bool
False Bool
False

tickLengthCounter :: LengthCounter -> LengthCounter
tickLengthCounter :: LengthCounter -> LengthCounter
tickLengthCounter LengthCounter
lc =
    if LengthCounter -> Int
remainingLength LengthCounter
lc Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Bool -> Bool
not (LengthCounter -> Bool
isHalted LengthCounter
lc)
        then LengthCounter
lc{remainingLength = remainingLength lc - 1}
        else LengthCounter
lc

{-# INLINE clearAndHaltLengthCounter #-}
clearAndHaltLengthCounter :: LengthCounter -> LengthCounter
clearAndHaltLengthCounter :: LengthCounter -> LengthCounter
clearAndHaltLengthCounter LengthCounter
lc = LengthCounter
lc{remainingLength = 0, isHalted = True}

-- | Load Length using the argument a an index in the length table
--
-- Note: It must not be done when the enabled bit (4015) is clear
loadLengthCounter :: Int -> LengthCounter -> LengthCounter
loadLengthCounter :: Int -> LengthCounter -> LengthCounter
loadLengthCounter Int
idx LengthCounter
lc
    | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ LengthCounter -> Bool
isEnabled LengthCounter
lc = LengthCounter
lc
    | Bool
otherwise = case [Int]
lengthTable [Int] -> Int -> Maybe Int
forall a. [a] -> Int -> Maybe a
!? Int
idx of
        Just Int
l -> LengthCounter
lc{remainingLength = l}
        Maybe Int
Nothing -> LengthCounter
lc -- Index is invalid

disableLengthCounter :: LengthCounter -> LengthCounter
disableLengthCounter :: LengthCounter -> LengthCounter
disableLengthCounter LengthCounter
lc = LengthCounter
lc{isEnabled = False, remainingLength = 0}

enableLengthCounter :: LengthCounter -> LengthCounter
enableLengthCounter :: LengthCounter -> LengthCounter
enableLengthCounter LengthCounter
lc = LengthCounter
lc{isEnabled = True}

class HasLengthCounter a where
    getLengthCounter :: a -> LengthCounter
    setLengthCounter :: LengthCounter -> a -> a

{-# INLINE withLengthCounter #-}
withLengthCounter :: (HasLengthCounter a) => (LengthCounter -> LengthCounter) -> a -> a
withLengthCounter :: forall a.
HasLengthCounter a =>
(LengthCounter -> LengthCounter) -> a -> a
withLengthCounter LengthCounter -> LengthCounter
f a
a = LengthCounter -> a -> a
forall a. HasLengthCounter a => LengthCounter -> a -> a
setLengthCounter (LengthCounter -> LengthCounter
f (LengthCounter -> LengthCounter) -> LengthCounter -> LengthCounter
forall a b. (a -> b) -> a -> b
$ a -> LengthCounter
forall a. HasLengthCounter a => a -> LengthCounter
getLengthCounter a
a) a
a

{-# INLINE isSilencedByLengthCounter #-}
isSilencedByLengthCounter :: (HasLengthCounter a) => a -> Bool
isSilencedByLengthCounter :: forall a. HasLengthCounter a => a -> Bool
isSilencedByLengthCounter = (Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (Int -> Bool) -> (a -> Int) -> a -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LengthCounter -> Int
remainingLength (LengthCounter -> Int) -> (a -> LengthCounter) -> a -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> LengthCounter
forall a. HasLengthCounter a => a -> LengthCounter
getLengthCounter

lengthTable :: [Int]
lengthTable :: [Int]
lengthTable =
    [ Int
10
    , Int
254
    , Int
20
    , Int
2
    , Int
40
    , Int
4
    , Int
80
    , Int
6
    , Int
160
    , Int
8
    , Int
60
    , Int
10
    , Int
14
    , Int
12
    , Int
26
    , Int
14
    , Int
12
    , Int
16
    , Int
24
    , Int
18
    , Int
48
    , Int
20
    , Int
96
    , Int
22
    , Int
192
    , Int
24
    , Int
72
    , Int
26
    , Int
16
    , Int
28
    , Int
32
    , Int
30
    ]