{-# LANGUAGE GADTs               #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE Safe                #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Casting of values with dynamic types and translating from Copilot core
-- types to Copilot theorem types.

module Copilot.Theorem.TransSys.Cast
  ( Dyn
  , toDyn
  , cast
  , castedType
  , casting
  ) where

import Copilot.Core as C

import Data.Dynamic (Dynamic(..), fromDynamic, toDyn)
import GHC.Float

import qualified Copilot.Theorem.TransSys.Type as K

-- | Synonym for a dynamic type in Copilot core.
type Dyn = Dynamic

-- | Translation of a Copilot type into Copilot theorem's internal
-- representation.
castedType :: Type t -> K.U K.Type
castedType t = case t of
  Bool    -> K.U K.Bool
  Int8    -> K.U K.Integer
  Int16   -> K.U K.Integer
  Int32   -> K.U K.Integer
  Int64   -> K.U K.Integer
  Word8   -> K.U K.Integer
  Word16  -> K.U K.Integer
  Word32  -> K.U K.Integer
  Word64  -> K.U K.Integer
  Float   -> K.U K.Real
  Double  -> K.U K.Real

-- | Cast a dynamic value to a given type.
cast :: K.Type t -> Dyn -> t
cast t v
  | K.Integer <- t,  Just (vi :: Integer) <- _cast v = vi
  | K.Bool    <- t,  Just (vb :: Bool)    <- _cast v = vb
  | K.Real    <- t,  Just (vr :: Double)  <- _cast v = vr
  | otherwise = error "Bad type cast"

-- | Apply function to a corresponding type in Copilot theorem's internal
-- representation.
casting :: Type t -> (forall t' . K.Type t' -> a) -> a
casting t f = case castedType t of
  K.U K.Bool    -> f K.Bool
  K.U K.Integer -> f K.Integer
  K.U K.Real    -> f K.Real

class Casted b where
  _cast :: Dyn -> Maybe b

instance Casted Integer where
  _cast d
    | Just (v :: Int8)   <- fromDynamic d = Just $ toInteger v
    | Just (v :: Int16)  <- fromDynamic d = Just $ toInteger v
    | Just (v :: Int32)  <- fromDynamic d = Just $ toInteger v
    | Just (v :: Int64)  <- fromDynamic d = Just $ toInteger v
    | Just (v :: Word8)  <- fromDynamic d = Just $ toInteger v
    | Just (v :: Word16) <- fromDynamic d = Just $ toInteger v
    | Just (v :: Word32) <- fromDynamic d = Just $ toInteger v
    | Just (v :: Word64) <- fromDynamic d = Just $ toInteger v
    | otherwise                           = Nothing

instance Casted Bool where
  _cast d
    | Just (v :: Bool) <- fromDynamic d = Just v
    | otherwise                         = Nothing

instance Casted Double where
  _cast d
    | Just (v :: Float)  <- fromDynamic d = Just $ float2Double v
    | Just (v :: Double) <- fromDynamic d = Just v
    | otherwise                           = Nothing
