{-# LANGUAGE DeriveAnyClass, FlexibleInstances, ScopedTypeVariables, DeriveGeneric, StandaloneDeriving, OverloadedStrings #-}

{-| Computer Aided Transceiver interface.
This provides a monad that allows to talk to serially connected transceivers.
To support a transceiver, there must be a `SerialCAT' provided for it.
-}
module Ham.Internal.CAT where

import Control.Monad (when)
import Control.Monad.Trans.RWS
import Control.Monad.Trans.Except
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class
import Control.Exception
import Data.Aeson
import qualified Data.ByteString.Char8 as B
import Data.Maybe (isJust)
import qualified Data.Text as T
import Data.Text (Text)
import GHC.Generics
import Ham.CAT.SerialCAT
import Ham.CAT.Radios
import Ham.Internal.Data
import System.Hardware.Serialport



-- | Configuration for the CAT monad.
data CATConfig = CATConfig { catPort :: String  -- ^ Serial port the radio is connected to. In Linux e.g. "/dev/ttyUSB0".
                           , catRadio :: Radio  -- ^ Radio identifier.
                           , catSerialSettings :: SerialPortSettings  -- ^ Serial settings for the connection. Note the radio must have the same settings.
                           } deriving (Generic, Show)

-- Automatically derive Show instance. That's not inclded in the serialport library.
deriving instance Show SerialPortSettings
deriving instance Generic SerialPortSettings
deriving instance Generic CommSpeed
deriving instance Generic FlowControl
deriving instance Generic StopBits
deriving instance Generic Parity

instance FromJSON SerialPortSettings
instance ToJSON SerialPortSettings
instance FromJSON CommSpeed
instance ToJSON CommSpeed
instance FromJSON FlowControl
instance ToJSON FlowControl

instance FromJSON StopBits
instance ToJSON StopBits
instance FromJSON Parity
instance ToJSON Parity

instance FromJSON CATConfig
instance ToJSON CATConfig

instance FromJSON Radio
instance ToJSON Radio


-- | Default configuration. Note this assumes port and radio, so you will want to adjust it.
defaultConfig :: CATConfig
defaultConfig = CATConfig { catPort = "/dev/ttyUSB0"
                          , catRadio = YaesuFT891
                          , catSerialSettings = defaultSerialSettings { commSpeed = CS4800
                                                                      , bitsPerWord = fromIntegral 8
                                                                      , stopb = One
                                                                      , parity = NoParity
                                                                      , flowControl = NoFlowControl
                                                                      , timeout = 2 } }

-- | State type for the CAT monad.
data CATState = CATState { statePort :: Maybe SerialPort  -- ^ Serial port, if one is open.
                         , stateInterface :: SerialCAT    -- ^ SerialCAT implementation for the radio.
                         }


-- | Default state. Note that this sets the SerialCAT to a default implementation. You will want to change that.
defaultState :: CATState
defaultState = CATState { statePort = Nothing
                        , stateInterface = yaesuFT891 }


data CATError = CATError CATErrorType Text deriving Show
data CATErrorType = CATErrorGeneric
                  | CATErrorIdentify
  deriving Show


-- | The computer aided transceiver monad.
newtype CAT m a = CAT { unCAT :: RWST CATConfig [Text] CATState (ExceptT CATError m) a }

instance Monad m => Functor (CAT m) where
  fmap f a = CAT $ fmap f (unCAT a)

instance Monad m => Applicative (CAT m) where
  pure = CAT . pure
  a <*> b = CAT $ unCAT a <*> unCAT b

instance Monad m => Monad (CAT m) where
  a >>= b = CAT $ unCAT a >>= (unCAT . b)


-- | Throw an exception in the `Overpass' monad.
throwCAT :: Monad m => CATError -> CAT m a
throwCAT = CAT . lift . throwE


-- | Catching exceptions in the `Overpass' monad.
catchCAT :: Monad m => CAT m a -> (CATError -> CAT m a) -> CAT m a
catchCAT act c = CAT $ liftCatch catchE (unCAT act) (unCAT . c)


-- | Run an action in the CAT monad.
runCAT :: MonadIO m => CATConfig
       -> CATState -- ^ State to start with.
       -> CAT m a
       -> m (Either CATError (a, [Text]))
runCAT config state act = runExceptT $ evalRWST (unCAT act') config state
  where
    act' = do
      result <- catchCAT (catInit >> act) $ \e -> catDeinit >> throwCAT e
      catDeinit
      return result




-- | FIXME: Add error handling. What if the radio can not be opened?
catInit :: MonadIO m => CAT m ()
catInit = do
  result <- CAT $ do
     portName <- asks catPort
     serialSettings <- asks catSerialSettings

     ms <- liftIO $ catch
           (do { s <- openSerial portName serialSettings; return (Just s) })
           (\(e :: SomeException) -> {- putStrLn (show e) >> -} return Nothing)
     get >>= \a -> put (a { statePort = ms })
     case ms of
       Just s -> do
         ident <- serialIdentify <$> gets stateInterface
         a <- liftIO $ ident s
         tell $ ["Identifying configured radio: " <> T.pack (show a)]
         if a
           then return Nothing
           else return $ Just $ CATError CATErrorIdentify "Radio was not identified as the configured one."
       _ -> return $ Just (CATError CATErrorGeneric "Could not open serial port.")
  case result of
    Just e -> throwCAT e
    _ -> return ()


-- | Close everything down.
catDeinit :: MonadIO m => CAT m ()
catDeinit = CAT $ do
  ms <- gets statePort
  maybe (return ()) (\s -> liftIO $ closeSerial s) ms
  get >>= \a -> put (a { statePort = Nothing })


-- | Get the current frequency from the transceiver.
catFrequency :: MonadIO m => CAT m (Maybe Frequency)
catFrequency = CAT $ do
  ms <- gets statePort
  i <- gets stateInterface
  maybe
    (tell ["Could not get frequency from radio"] >> return Nothing)
    (\s -> do
        f <- liftIO $ serialGetFrequency i s
        tell ["Got frequency " <> T.pack (show f)]
        return f
    )
    ms


-- | Get the current mode from the transceiver.
catMode :: MonadIO m => CAT m (Maybe QsoMode)
catMode = CAT $ do
  ms <- gets statePort
  i <- gets stateInterface
  maybe
    (tell ["Could not get mode from radio."] >> return Nothing)
    (\s -> do
        a <- liftIO $ serialGetMode i s
        tell ["Got mode " <> T.pack (show a)]
        return a
    )
    ms


-- | Get the SSB power setting in watts.
catPowerSSB :: MonadIO m => CAT m (Maybe Int)
catPowerSSB = CAT $ do
  ms <- gets statePort
  i <- gets stateInterface
  maybe (return Nothing) (\s -> liftIO $ serialGetPowerSSB i s) ms


-- | Set the SSB power in watts.
catSetPowerSSB :: MonadIO m => Int -> CAT m ()
catSetPowerSSB power = CAT $ do
  ms <- gets statePort
  i <- gets stateInterface
  maybe (return ()) (\s -> liftIO $ serialSetPowerSSB i s power) ms