{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module:      Data.Salak
-- Copyright:   (c) 2018 Daniel YU
-- License:     BSD3
-- Maintainer:  Daniel YU <leptonyu@gmail.com>
-- Stability:   experimental
-- Portability: portable
--
-- Configuration Loader for Production in Haskell.
--
module Data.Salak(
  -- * How to use this library
  -- $use

  -- * Properties Loader
    LoadProperties
  , runLoad
  , askProperties
  , setValue
  , loadCommandLine
  , loadEnvironment
  , loadJSON
  , loadYaml
  , loadYamlIfExists
  -- ** Predefined Loaders
  , defaultProperties
  , defaultProperties'
  , defaultPropertiesWithFile
  , defaultPropertiesWithFile'
  , empty
  -- * Lookup Properties
  , lookup
  , toKeys
  -- * Types
  , Property(..)
  , Properties(..)
  , FromProperties(..)
  , Return
  -- * Properties Loader Helper
  , insert
  , defaultParseCommandLine
  , ParseCommandLine
  , FileName
  -- * Operations
  , module Data.Salak.Operation
  -- * Dynamic Load
  , module Data.Salak.Dynamic
  ) where

import           Control.Monad.Trans.Class (lift)
import           Data.Maybe
import           Data.Salak.Aeson
import           Data.Salak.CommandLine
import           Data.Salak.Dynamic
import           Data.Salak.Environment
import           Data.Salak.Operation
import           Data.Salak.Types
import           Data.Salak.Yaml
import           Data.Text                 (pack)
import           Prelude                   hiding (lookup)
import           System.Directory
import           System.FilePath           ((</>))

-- | Initialize default properties from `CommandLine` and `Environment`.
-- `CommandLine` use default parser.
defaultProperties :: IO Properties
defaultProperties = defaultProperties' defaultParseCommandLine

-- | Initialize default properties from `CommandLine` and `Environment`.
defaultProperties' :: ParseCommandLine -> IO Properties
defaultProperties' dpc = runLoad $ do
  loadCommandLine dpc
  loadEnvironment

-- | Yaml file name.
type FileName = String

-- | Initialize default properties from `CommandLine`, `Environment` and `Yaml` files.
-- All these configuration sources has orders, from highest order to lowest order:
--
-- > 1. CommandLine
-- > 2. Environment
-- > 3. Specified Yaml file(file in "salak.config.dir")
-- > 4. Yaml file in current directory
-- > 5. Yaml file in home directory
--
defaultPropertiesWithFile
  :: FileName -- ^ specify default config file name, can reset by config "salak.config.name" from `CommandLine` or `Environment`.
  -> IO Properties
defaultPropertiesWithFile name = defaultPropertiesWithFile' name defaultParseCommandLine

-- | Initialize default properties from `CommandLine`, `Environment` and `Yaml` files.
-- All these configuration sources has orders, from highest order to lowest order:
--
-- > 1. CommandLine
-- > 2. Environment
-- > 3. Specified Yaml file(file in "salak.config.dir")
-- > 4. Yaml file in current directory
-- > 5. Yaml file in home directory
--
defaultPropertiesWithFile'
  :: FileName -- ^ specify default config file name, can reset by config "salak.config.name" from `CommandLine` or `Environment`.
  -> ParseCommandLine -- ^ parser for command line
  -> IO Properties
defaultPropertiesWithFile' name dpc = runLoad $ do
  loadCommandLine dpc
  loadEnvironment
  p <- askProperties
  let n  = fromMaybe name $ p .>> "salak.config.name"
      f  = p .>> "salak.config.dir"
  setValue "salak.config.name" (PStr $ pack n)
  case f of
    Just y -> loadYaml (y </> n)
    _      -> return ()
  c <- lift getCurrentDirectory
  loadYamlIfExists (Just $ c </> n)
  h <- lift getHomeDirectory
  loadYamlIfExists (Just $ h </> n)

-- $use
--
-- | This library default a standard configuration load process. It can load properties from `CommandLine`, `Environment`,
-- `JSON value` and `Yaml` files. They all load to the same format `Properties`. Earler property source has higher order
-- to load property. For example:
--
-- > CommandLine:  --package.a.enabled=true
-- > Environment: PACKAGE_A_ENABLED: false
--
-- > lookup "package.a.enabled" properties => Just True
--
-- `CommandLine` has higher order then `Environment`, for the former load properties earler then later.
--
-- Usage:
--
-- > data Config = Config
-- >   { name :: Text
-- >   , dir  :: Maybe Text
-- >   , ext  :: Int
-- >   } deriving (Eq, Show)
-- >
-- > instance FromProperties Config where
-- >   fromProperties v = Config
-- >         <$> v .?> "name"
-- >         <*> v .?> "dir"
-- >         <*> v .?> "ext" .?= 1
--
-- > main = do
-- >   p <- defaultPropertiesWithFile "salak.yml"
-- >   let config  = p .>> "salak.config"  :: Config
-- >       enabled = p .?> "salak.enabled" .|= True
-- >   print config
-- >   print enabled