{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE DeriveGeneric              #-}
------------------------------------------------------------------------------
module Main ( main ) where
------------------------------------------------------------------------------
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy.Char8 as BL8
import           Data.Int
import           Data.List (isInfixOf)
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import           Data.Time
import           Data.Word
import           GHC.Generics
import           System.Envy
import           Test.Hspec
import           Test.QuickCheck
import           Test.QuickCheck.Instances ()
import           Test.QuickCheck.Monadic



------------------------------------------------------------------------------
data UserInfo = UserInfo {
      userName :: String
    , userAge :: Int
  } deriving (Show, Eq, Generic)


-- | Generic instance
instance FromEnv UserInfo

instance Arbitrary UserInfo where
  arbitrary = UserInfo <$> arbitrary <*> arbitrary

------------------------------------------------------------------------------
data ConnectInfo = ConnectInfo {
      pgHost :: String
    , pgPort :: Word16
    , pgUser :: String
    , pgPass :: String
    , pgDB   :: String
  } deriving (Show, Eq, Generic)


instance DefConfig ConnectInfo where
  defConfig = ConnectInfo "localhost" 5432 "user" "pass" "db"

-- | Generic instance with default values
instance FromEnv ConnectInfo

instance Arbitrary ConnectInfo where
    arbitrary = ConnectInfo <$> nonulls
                            <*> arbitrary
                            <*> nonulls
                            <*> nonulls
                            <*> nonulls
      where nonempty = getNonEmpty <$> arbitrary
            nonulls = nonempty `suchThat` (not . ('\NUL' `elem`))

------------------------------------------------------------------------------
-- | Posgtres config
data PGConfig = PGConfig {
    pgConnectInfo :: ConnectInfo -- ^ Connnection Info
  } deriving (Eq)

instance Arbitrary PGConfig where
    arbitrary = PGConfig <$> arbitrary

------------------------------------------------------------------------------
-- | Custom show instance
instance Show PGConfig where
  show PGConfig {..} = "<PGConfig>"

------------------------------------------------------------------------------
-- | FromEnv Instances, supports popular aeson combinators *and* IO
-- for dealing with connection pools
instance FromEnv PGConfig where
  fromEnv _ = PGConfig <$> (ConnectInfo <$> envMaybe "PG_HOST" .!= "localhost"
                                        <*> env "PG_PORT"
                                        <*> env "PG_USER"
                                        <*> env "PG_PASS"
                                        <*> env "PG_DB")

------------------------------------------------------------------------------
-- | To Environment Instances
instance ToEnv PGConfig where
  toEnv PGConfig{..} = let ConnectInfo{..} = pgConnectInfo
                       in
                       makeEnv [ "PG_HOST" .= pgHost
                               , "PG_PORT" .= pgPort
                               , "PG_USER" .= pgUser
                               , "PG_PASS" .= pgPass
                               , "PG_DB"   .= pgDB
                               ]


------------------------------------------------------------------------------
-- | Start tests
main :: IO ()
main = hspec $ do
  describe "Var ismorphisms hold" $ do
    it "Word8 Var isomorphism" $ property $
     \(x :: Word8) -> Just x == fromVar (toVar x)
    it "Word16 Var isomorphism" $ property $
     \(x :: Word16) -> Just x == fromVar (toVar x)
    it "Word32 Var isomorphism" $ property $
     \(x :: Word32) -> Just x == fromVar (toVar x)
    it "Int Var isomorphism" $ property $
     \(x :: Int) -> Just x == fromVar (toVar x)
    it "Int8 Var isomorphism" $ property $
     \(x :: Int8) -> Just x == fromVar (toVar x)
    it "Int16 Var isomorphism" $ property $
     \(x :: Int16) -> Just x == fromVar (toVar x)
    it "Int32 Var isomorphism" $ property $
     \(x :: Int32) -> Just x == fromVar (toVar x)
    it "Int64 Var isomorphism" $ property $
     \(x :: Int64) -> Just x == fromVar (toVar x)
    it "String Var isomorphism" $ property $
     \(x :: String) -> Just x == fromVar (toVar x)
    it "Double Var isomorphism" $ property $
     \(x :: Double) -> Just x == fromVar (toVar x)
    it "UTCTime Var isomorphism" $ property $
     \(x :: UTCTime) -> Just x == fromVar (toVar x)
    it "ByteString Var isomorphism" $ property $
     \(x :: B8.ByteString) -> Just x == fromVar (toVar x)
    it "ByteString Var isomorphism" $ property $
     \(x :: BL8.ByteString) -> Just x == fromVar (toVar x)
    it "Lazy Text Var isomorphism" $ property $
     \(x :: LT.Text) -> Just x == fromVar (toVar x)
    it "Text Var isomorphism" $ property $
     \(x :: T.Text) -> Just x == fromVar (toVar x)
    it "() Var isomorphism" $ property $
     \(x :: ()) -> Just x == fromVar (toVar x)
  describe "Can set to and from environment" $ do
    it "Isomorphism through setEnvironment['] and decodeEnv" $ property $
      \(pgConf::PGConfig) -> monadicIO $ do
        res <- run $ do
                 _ <- setEnvironment' pgConf
                 decodeEnv
        assert $ res == Right pgConf
    it "Storing and retrieving var" $ property $
      \(x::String) -> monadicIO $ do
        res <- run $ do
                 _ <- setEnvironment $ makeEnv [ "HSPECENVY_" .= toVar x]
                 runEnv $ env  "HSPECENVY_"
        assert $ if isInfixOf "\NUL" x then True else res == Right x
  describe "Can use generic FromEnv" $
    it "Isomorphism through setEnvironment and decodeEnv" $ property $
      \(ci::ConnectInfo) -> monadicIO $ do
        res <- run $ do
                 let ConnectInfo{..} = ci
                 _ <- setEnvironment $
                          makeEnv [ "PG_HOST" .= pgHost
                                  , "PG_PORT" .= pgPort
                                  , "PG_USER" .= pgUser
                                  , "PG_PASS" .= pgPass
                                  , "PG_DB"   .= pgDB
                                  ]
                 decodeWithDefaults (defConfig :: ConnectInfo)
        assert $ res == ci
  describe "Can use generic FromEnvNoDefault" $
    it "Isomorphism through setEnvironment and decodeEnv" $ property $
      \(u::UserInfo) -> monadicIO $ do
        res <- run $ do
                 let UserInfo{..} = u
                 _ <- setEnvironment $
                          makeEnv [ "USER_NAME" .= (userName ++ "")
                                  , "USER_AGE" .= userAge
                                  ]
                 decodeEnv
        assert $  if isInfixOf "\NUL" (userName u) then True else res == Right u