{-|
    Module      : Lazyboy.Control
    Description : Control flow features for Lazyboy
    Copyright   : (c) Rose 2019
    License     : BSD3
    Maintainer  : rose@lain.org.uk
    Stability   : experimental
    Portability : POSIX

    This module defines methods of controlling the flow of execution for Lazyboy.
-}

{-# LANGUAGE MultiParamTypeClasses #-}

module Lazyboy.Control where

import           Control.Monad.Trans.RWS
import           Data.Word
import           Lazyboy.Types

-- | Get a label, and in the process increment the counter used to track labels.
-- this provides a safe interface to label retrieval and utilization.
getLabel :: Lazyboy Integer
getLabel = do
  label <- get
  modify (+ 1)
  return label

-- | Get a local label. The name is guaranteed to be unique.
getLocalLabel :: Lazyboy Label
getLocalLabel = Local <$> getLabel

-- | Get a global label. The name is guaranteed to be unique.
getGlobalLabel :: Lazyboy Label
getGlobalLabel = Global <$> getLabel

-- | Execute an action within a global label and pass the action the label.
withLabel :: (Label -> Lazyboy ()) -> Lazyboy ()
withLabel block = do
  label <- getGlobalLabel
  tell [LABEL label]
  block label

-- | Execute an action within a local label and pass the action the label.
withLocalLabel :: (Label -> Lazyboy ()) -> Lazyboy ()
withLocalLabel block = do
  label <- getLocalLabel
  tell [LABEL label]
  block label

-- | Embed a file and return a global label for it.
-- A jump over the block of data is added to prevent the image data being executed.
embedFile :: FilePath -> Lazyboy Label
embedFile file = do
    label <- getGlobalLabel
    skipLabel <- getGlobalLabel
    tell [JP $ Name skipLabel]
    tell [LABEL label, INCLUDE file]
    tell [LABEL skipLabel]
    return label

-- | Embed an image and return a (global) label for it.
-- A jump over the block of data is added to prevent the image data being executed.
embedImage = embedFile

-- | Embed a sequence of bytes into the file and return a (global) label for it.
-- A jump over the block of data is added to prevent the image data being executed.
embedBytes :: [Word8] -> Lazyboy Label
embedBytes bytes = do
  label <- getGlobalLabel
  skipLabel <- getGlobalLabel
  tell [JP $ Name skipLabel]
  tell [LABEL label, BYTES bytes]
  tell [LABEL skipLabel]
  return label

-- | Suspend execution indefinitely by disabling interrupts and halting.
freeze :: Lazyboy ()
freeze = withLabel $ \label -> do
  tell [DI, HALT]
  tell [JP $ Name label]

-- | Executes the given action provided condition flag is set.
cond :: Condition -> Lazyboy a -> Lazyboy a
cond condition block = do
  label <- getLocalLabel
  tell [JPif condition (Name label)]
  a <- block
  tell [LABEL label]
  return a

-- | A typeclass for comparisons between registers and values.
class Comparable a b where
  equalTo     :: a -> b -> Lazyboy Condition -- ^ Check the equality of two items.
  notEqualTo  :: a -> b -> Lazyboy Condition -- ^ Check the inequality of two items.
  greaterThan :: a -> b -> Lazyboy Condition -- ^ Check whether `a` is greater than `b`.
  lessThan    :: a -> b -> Lazyboy Condition -- ^ Check whether `a` is less than `b`.

-- | An instance for comparing two 8-bit registers.
instance Comparable Register8 Register8 where
  equalTo A r      = tell [CPr r]            >> return NonZero
  equalTo r r'     = tell [LDrr A r, CPr r'] >> return NonZero
  notEqualTo A r   = equalTo A r             >> return Zero
  notEqualTo r r'  = equalTo r r'            >> return Zero
  greaterThan A r  = equalTo A r             >> return NoCarry
  greaterThan r r' = equalTo r r'            >> return NoCarry
  lessThan A r     = equalTo A r             >> return Carry
  lessThan r r'    = equalTo r r'            >> return Carry

-- | An instance for comparing an 8-bit register and a Word8.
instance Comparable Register8 Word8 where
  equalTo A n      = tell [CPn n]            >>  return NonZero
  equalTo r n      = tell [LDrr A r, CPn n]  >>  return NonZero
  notEqualTo A n   = equalTo A n             >>  return Zero
  notEqualTo r n   = equalTo r n             >>  return Zero
  greaterThan A n  = equalTo A n             >>  return NoCarry
  greaterThan r n  = equalTo r n             >>  return NoCarry
  lessThan A n     = equalTo A n             >>  return Carry
  lessThan r n     = equalTo r n             >>  return Carry

-- | An instance for comparing a Word8 and an 8-bit register (this is an alias).
instance Comparable Word8 Register8 where
  equalTo = flip equalTo
  notEqualTo = flip notEqualTo
  greaterThan = flip greaterThan
  lessThan = flip lessThan

-- | Executes an action which returns a condition flag, then conditionally executes
-- another action baed on the state of that condition flag.
if' :: Lazyboy Condition -> Lazyboy a -> Lazyboy a
if' condition block = do
  flag <- condition
  cond flag block

-- | Boolean NOT operation for inverting Condition flags.
not :: Lazyboy Condition -> Lazyboy Condition
not action = do
  flag <- action
  return $ case flag of
    Zero    -> NonZero
    NonZero -> Zero
    Carry   -> NoCarry
    NoCarry -> Carry

-- | Assign boolean values to two registers based on the result flags of 
-- some conditions and then AND them and return the result.
and :: Lazyboy Condition -> Lazyboy Condition -> Lazyboy Condition
and a b = do
  a' <- a
  cond a' $ do
    tell [LDrn L 1]
  b' <- b
  cond b' $ do
    tell [LDrn A 1]
  tell [ANDr L]
  return Zero

-- | Assign boolean values to two registers based on the result flags of
-- some conditions and then OR them and return the result.
or :: Lazyboy Condition -> Lazyboy Condition -> Lazyboy Condition
or a b = do
  a' <- a
  cond a' $ do
    tell [LDrn L 1]
  b' <- b
  cond b' $ do
    tell [LDrn A 1]
  tell [ORr L]
  return Zero