module Nes.APU.State.FrameCounter (
    FrameCounter (..),
    newFrameCounter,

    -- * Sequence mode
    SequenceMode (..),
    sequenceModeFromBool,

    -- * Utils
    shouldIncrementSequenceStep,
    shouldResetSequenceStep,
    incrementSequenceStep,
    resetSequence,
    setCycles,
) where

import Data.List ((!?))

data SequenceMode = FourStep | FiveStep deriving (SequenceMode -> SequenceMode -> Bool
(SequenceMode -> SequenceMode -> Bool)
-> (SequenceMode -> SequenceMode -> Bool) -> Eq SequenceMode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SequenceMode -> SequenceMode -> Bool
== :: SequenceMode -> SequenceMode -> Bool
$c/= :: SequenceMode -> SequenceMode -> Bool
/= :: SequenceMode -> SequenceMode -> Bool
Eq, Int -> SequenceMode -> ShowS
[SequenceMode] -> ShowS
SequenceMode -> String
(Int -> SequenceMode -> ShowS)
-> (SequenceMode -> String)
-> ([SequenceMode] -> ShowS)
-> Show SequenceMode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SequenceMode -> ShowS
showsPrec :: Int -> SequenceMode -> ShowS
$cshow :: SequenceMode -> String
show :: SequenceMode -> String
$cshowList :: [SequenceMode] -> ShowS
showList :: [SequenceMode] -> ShowS
Show, Int -> SequenceMode
SequenceMode -> Int
SequenceMode -> [SequenceMode]
SequenceMode -> SequenceMode
SequenceMode -> SequenceMode -> [SequenceMode]
SequenceMode -> SequenceMode -> SequenceMode -> [SequenceMode]
(SequenceMode -> SequenceMode)
-> (SequenceMode -> SequenceMode)
-> (Int -> SequenceMode)
-> (SequenceMode -> Int)
-> (SequenceMode -> [SequenceMode])
-> (SequenceMode -> SequenceMode -> [SequenceMode])
-> (SequenceMode -> SequenceMode -> [SequenceMode])
-> (SequenceMode -> SequenceMode -> SequenceMode -> [SequenceMode])
-> Enum SequenceMode
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: SequenceMode -> SequenceMode
succ :: SequenceMode -> SequenceMode
$cpred :: SequenceMode -> SequenceMode
pred :: SequenceMode -> SequenceMode
$ctoEnum :: Int -> SequenceMode
toEnum :: Int -> SequenceMode
$cfromEnum :: SequenceMode -> Int
fromEnum :: SequenceMode -> Int
$cenumFrom :: SequenceMode -> [SequenceMode]
enumFrom :: SequenceMode -> [SequenceMode]
$cenumFromThen :: SequenceMode -> SequenceMode -> [SequenceMode]
enumFromThen :: SequenceMode -> SequenceMode -> [SequenceMode]
$cenumFromTo :: SequenceMode -> SequenceMode -> [SequenceMode]
enumFromTo :: SequenceMode -> SequenceMode -> [SequenceMode]
$cenumFromThenTo :: SequenceMode -> SequenceMode -> SequenceMode -> [SequenceMode]
enumFromThenTo :: SequenceMode -> SequenceMode -> SequenceMode -> [SequenceMode]
Enum)

{-# INLINE sequenceModeFromBool #-}
sequenceModeFromBool :: Bool -> SequenceMode
sequenceModeFromBool :: Bool -> SequenceMode
sequenceModeFromBool = Int -> SequenceMode
forall a. Enum a => Int -> a
toEnum (Int -> SequenceMode) -> (Bool -> Int) -> Bool -> SequenceMode
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Int
forall a. Enum a => a -> Int
fromEnum

{-# INLINE sequenceModeStepCount #-}
sequenceModeStepCount :: SequenceMode -> Int
sequenceModeStepCount :: SequenceMode -> Int
sequenceModeStepCount = \case
    SequenceMode
FourStep -> Int
4
    SequenceMode
FiveStep -> Int
5

sequenceStepCycles :: SequenceMode -> [Int]
sequenceStepCycles :: SequenceMode -> [Int]
sequenceStepCycles = \case
    SequenceMode
FourStep -> [Int
3728, Int
7456, Int
11185, Int
14914, Int
14915]
    SequenceMode
FiveStep -> [Int
3728, Int
7456, Int
11185, Int
14914, Int
18640, Int
18641]

{-# INLINE shouldIncrementSequenceStep #-}
shouldIncrementSequenceStep :: FrameCounter -> Bool
shouldIncrementSequenceStep :: FrameCounter -> Bool
shouldIncrementSequenceStep FrameCounter
fc =
    let
        table :: [Int]
table = SequenceMode -> [Int]
sequenceStepCycles (SequenceMode -> [Int]) -> SequenceMode -> [Int]
forall a b. (a -> b) -> a -> b
$ FrameCounter -> SequenceMode
sequenceMode FrameCounter
fc
     in
        case [Int]
table [Int] -> Int -> Maybe Int
forall a. [a] -> Int -> Maybe a
!? FrameCounter -> Int
sequenceStep FrameCounter
fc of
            Maybe Int
Nothing -> Bool
False
            Just Int
s -> FrameCounter -> Int
cycles FrameCounter
fc Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
s

{-# INLINE shouldResetSequenceStep #-}
shouldResetSequenceStep :: FrameCounter -> Bool
shouldResetSequenceStep :: FrameCounter -> Bool
shouldResetSequenceStep FrameCounter
fc =
    let
        table :: [Int]
table = SequenceMode -> [Int]
sequenceStepCycles (SequenceMode -> [Int]) -> SequenceMode -> [Int]
forall a b. (a -> b) -> a -> b
$ FrameCounter -> SequenceMode
sequenceMode FrameCounter
fc
     in
        case [Int]
table [Int] -> Int -> Maybe Int
forall a. [a] -> Int -> Maybe a
!? Int
5 of
            Maybe Int
Nothing -> Bool
False
            Just Int
s -> FrameCounter -> Int
cycles FrameCounter
fc Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
s

data FrameCounter = MkFC
    { FrameCounter -> SequenceMode
sequenceMode :: {-# UNPACK #-} !SequenceMode
    , FrameCounter -> Bool
frameInterruptFlag :: {-# UNPACK #-} !Bool
    , FrameCounter -> Bool
inhibitInterrupt :: {-# UNPACK #-} !Bool
    , FrameCounter -> Int
sequenceStep :: {-# UNPACK #-} !Int
    , FrameCounter -> Int
cycles :: {-# UNPACK #-} !Int
    , FrameCounter -> Maybe Int
delayedWriteSideEffectCycle :: !(Maybe Int)
    }

newFrameCounter :: FrameCounter
newFrameCounter :: FrameCounter
newFrameCounter = SequenceMode
-> Bool -> Bool -> Int -> Int -> Maybe Int -> FrameCounter
MkFC SequenceMode
FourStep Bool
False Bool
False Int
0 Int
0 Maybe Int
forall a. Maybe a
Nothing

{-# INLINE resetSequence #-}
resetSequence :: FrameCounter -> FrameCounter
resetSequence :: FrameCounter -> FrameCounter
resetSequence FrameCounter
fc = FrameCounter
fc{cycles = 0, sequenceStep = 0}

{-# INLINE setCycles #-}
setCycles :: (Int -> Int) -> FrameCounter -> FrameCounter
setCycles :: (Int -> Int) -> FrameCounter -> FrameCounter
setCycles Int -> Int
f FrameCounter
fc = FrameCounter
fc{cycles = f $ cycles fc}

-- | Increment 'sequenceStep', or set to zero when sequence ends
{-# INLINE incrementSequenceStep #-}
incrementSequenceStep :: FrameCounter -> FrameCounter
incrementSequenceStep :: FrameCounter -> FrameCounter
incrementSequenceStep FrameCounter
fc =
    FrameCounter
fc
        { sequenceStep = nextStep `mod` (maxStep + 1)
        -- To get end of sequence
        }
  where
    nextStep :: Int
nextStep = FrameCounter -> Int
sequenceStep FrameCounter
fc Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
    maxStep :: Int
maxStep = SequenceMode -> Int
sequenceModeStepCount (FrameCounter -> SequenceMode
sequenceMode FrameCounter
fc)