module Language.Sally.Types (
    
    Name
  , textFromName
  , nameFromT
  , nameFromS
  , catNamesWith
  , bangNames
  , scoreNames
  , nextName
  , stateName
  , inputName
  , varFromName
    
  , SallyBaseType(..)
  , SallyConst(..)
    
  , SallyState(..)
  , SallyPred(..)
  , SallyVar(..)
  , SallyArith(..)
  , SallyExpr(..)
  , ToSallyExpr(..)
  , SallyStateFormula(..)
  , SallyLet
  , SallyTransition(..)
  , SallySystem(..)
  , TrResult(..)
) where
import Data.Foldable (toList)
import Data.List (intersperse)
import Data.Ratio (numerator, denominator)
import Data.Sequence (Seq)
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import Text.PrettyPrint.ANSI.Leijen
import Language.Sally.SExpPP
newtype Name = Name { textFromName :: Text  }
  deriving (Show, Eq, Ord)
instance Pretty Name where
  pretty = text . T.unpack . textFromName
instance ToSExp Name where
  toSExp = SXBare . text . T.unpack . textFromName
nameFromS :: String -> Name
nameFromS = Name . T.pack
nameFromT :: Text -> Name
nameFromT = Name
instance IsString Name where
  fromString = nameFromS
catNamesWith :: Text -> Name -> Name -> Name
catNamesWith sp a b = Name (textFromName a `T.append` sp `T.append` textFromName b)
bangNames :: Name -> Name -> Name
bangNames = catNamesWith "!"
scoreNames :: Name -> Name -> Name
scoreNames = catNamesWith "_"
nextName :: Name -> Name
nextName = catNamesWith "" "next."
stateName :: Name -> Name
stateName = catNamesWith "" "state."
inputName :: Name -> Name
inputName = catNamesWith "" "input."
data SallyConst = SConstBool Bool
                | SConstInt  Integer
                | SConstReal Rational
  deriving (Show, Eq)
instance ToSExp SallyConst where
  toSExp (SConstBool b) = SXBare $ if b then text "true" else text "false"
  toSExp (SConstInt  x) =
    let bare = SXBare $ integer x
    in if x >= 0 then bare
       else SXList [bare]  
  toSExp (SConstReal x) =
    let nx = numerator x
        dx = denominator x
    in if dx == 1 then toSExp (SConstInt nx)  
                  else SXList [ SXBare "/", toSExp (SConstInt nx)
                              , toSExp (SConstInt dx) ]
data SallyBaseType = SBool
                   | SInt
                   | SReal
  deriving (Show, Eq)
instance ToSExp SallyBaseType where
  toSExp SBool = bareText "Bool"
  toSExp SInt  = bareText "Int"
  toSExp SReal = bareText "Real"
newtype SallyVar = SallyVar { textFromVar :: Text }
  deriving (Show, Eq)
instance ToSExp SallyVar where
  toSExp = SXBare . text . T.unpack . textFromVar
varFromName :: Name -> SallyVar
varFromName = SallyVar . textFromName
data SallyExpr = SELit   SallyConst              
               | SEVar   SallyVar                
               | SEPre   SallyPred               
               | SEArith SallyArith              
               | SEMux   SallyExpr SallyExpr SallyExpr  
  deriving (Show, Eq)
class ToSallyExpr a where
  
  toSallyExpr :: a -> SallyExpr
instance ToSExp SallyExpr where
  toSExp (SELit x)   = SXBare (sxPretty x)
  toSExp (SEVar x)   = SXBare (sxPretty x)
  toSExp (SEPre x)   = toSExp x
  toSExp (SEArith x) = toSExp x
  toSExp (SEMux x y z) = SXList [bareText "ite", toSExp x, toSExp y, toSExp z]
data SallyPred = SPConst Bool                    
               | SPExpr  SallyExpr               
               | SPAnd   (Seq SallyPred)         
               | SPOr    (Seq SallyPred)         
               | SPImpl  SallyPred SallyPred     
               | SPNot   SallyPred               
               | SPEq    SallyExpr SallyExpr     
               | SPLEq   SallyExpr SallyExpr     
               | SPGEq   SallyExpr SallyExpr     
               | SPLt    SallyExpr SallyExpr     
               | SPGt    SallyExpr SallyExpr     
  deriving (Show, Eq)
instance ToSExp SallyPred where
  toSExp (SPConst x)   = SXBare (text (if x then "true" else "false"))
  toSExp (SPExpr  x)   = SXBare (sxPretty x)
  toSExp (SPAnd   xs)  = SXList (bareText "and" : toList (fmap toSExp xs))
  toSExp (SPOr    xs)  = SXList (bareText "or"  : toList (fmap toSExp xs))
  toSExp (SPImpl  p q) = SXList [bareText "=>", toSExp p, toSExp q]
  toSExp (SPNot   p)   = SXList [bareText "not",  toSExp p]
  toSExp (SPEq    x y) = SXList [bareText "=",  toSExp x, toSExp y]
  toSExp (SPLEq   x y) = SXList [bareText "<=", toSExp x, toSExp y]
  toSExp (SPGEq   x y) = SXList [bareText ">=", toSExp x, toSExp y]
  toSExp (SPLt    x y) = SXList [bareText "<",  toSExp x, toSExp y]
  toSExp (SPGt    x y) = SXList [bareText "<",  toSExp x, toSExp y]
data SallyArith = SAAdd   SallyExpr SallyExpr  
                | SAMult  SallyExpr SallyExpr  
                | SADiv   SallyExpr SallyExpr  
                | SAExpr  SallyExpr            
  deriving (Show, Eq)
instance ToSExp SallyArith where
  toSExp (SAAdd x y)  = SXList [bareText "+", toSExp x, toSExp y]
  toSExp (SAMult x y) = SXList [bareText "*", toSExp x, toSExp y]
  toSExp (SADiv x y)  = SXList [bareText "/", toSExp x, toSExp y]
  toSExp (SAExpr e)   = toSExp e
data SallyState = SallyState
  { sName   :: Name                     
  , sVars   :: [(Name, SallyBaseType)]  
  , sInVars :: [(Name, SallyBaseType)]  
  }
  deriving (Show, Eq)
instance ToSExp SallyState where
  toSExp SallyState {sName=sn, sVars=sv, sInVars=siv} =
    SXList $ [ bareText "define-state-type"
             , toSExp sn
             , SXList $ map (\(n,t) -> SXList [toSExp n, toSExp t]) sv
             ] ++
             (if null siv then []
              else [SXList $ map (\(n,t) -> SXList [toSExp n, toSExp t]) siv])
data SallyStateFormula = SallyStateFormula
  { sfName   :: Name        
  , sfDomain :: Name        
  , sfPred   :: SallyPred   
  }
  deriving (Show, Eq)
instance ToSExp SallyStateFormula where
  toSExp SallyStateFormula {sfName=sn, sfDomain=sd, sfPred=sp} =
    SXList [ bareText "define-states"
           , toSExp sn
           , toSExp sd
           , toSExp sp
           ]
type SallyLet = (SallyVar, SallyExpr)
data SallyTransition = SallyTransition
  { traName :: Name        
  , traDom  :: Name        
  , traLet  :: [SallyLet]  
  , traPred :: SallyPred   
  }
  deriving (Show, Eq)
instance ToSExp SallyTransition where
  toSExp SallyTransition {traName=tn, traDom=td, traLet=tl, traPred=tp} =
      SXList $ [ bareText "define-transition"
               , toSExp tn
               , toSExp td
               ] ++
               (if null listOfBinds then [toSExp tp]
                else [SXList [bareText "let", SXList listOfBinds, toSExp tp]])
    where
      listOfBinds = map (\(v,e) -> SXList [toSExp v, toSExp e]) tl
data SallySystem = SallySystem
  { sysNm  :: Name  
  , sysSN  :: Name  
  , sysISN :: Name  
  , sysTN  :: Name  
  }
  deriving (Show, Eq)
instance ToSExp SallySystem where
    toSExp ss = SXList [ bareText "define-transition-system"
                       , toSExp (sysNm ss)
                       , toSExp (sysSN ss)
                       , toSExp (sysISN ss)
                       , toSExp (sysTN ss)
                       ]
data TrResult = TrResult
  { tresState    :: SallyState           
  , tresFormulas :: [SallyStateFormula]  
                                         
  , tresConsts   :: [SallyConst]         
  , tresInit     :: SallyStateFormula    
  , tresTrans    :: [SallyTransition]    
  , tresSystem   :: SallySystem          
  }
  deriving (Show, Eq)
instance Pretty TrResult where
  pretty tr = vcat [ consts_comment
                   , consts
                   , state_comment
                   , sxPretty (tresState tr)
                   ] <$$>
              vcat (formulas_comment : intersperse
                                         sallyCom
                                         (map sxPretty (tresFormulas tr))) <$$>
              
              vcat [ init_comment
                   , sxPretty (tresInit tr)
                   ] <$$>
              
              vcat (trans_comment : intersperse
                                      sallyCom
                                      (map sxPretty (tresTrans tr))) <$$>
              
              vcat (system_comment : [sxPretty (tresSystem tr)])
    where
      consts = if null (tresConsts tr) then text ";; NONE"
               else vcat (map sxPretty (tresConsts tr))
      consts_comment = sallyCom <+> text "Constants"
      state_comment  = linebreak <> sallyCom <+> text "State type"
      init_comment   = linebreak <> sallyCom <+> text "Initial State"
      formulas_comment = linebreak <> sallyCom <+> text "State Formulas"
      trans_comment  = linebreak <> sallyCom <+> text "Transitions"
      system_comment = linebreak <> sallyCom <+> text "System Definition"