content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  module Program.Mighty.Config (
    -- * Parsing a configuration file.
      parseOption
  #ifdef DHALL
    , parseOptionDhall
  #else
    , Natural
  #endif
    -- * Creating 'Option'.
    , defaultOption
    -- * Types
    , Option(..)
    ) where
  
  import Data.List.Split (splitOn)
  import Text.Parsec
  import Text.Parsec.ByteString.Lazy
  #ifdef DHALL
  import Data.String (fromString)
  import qualified Data.Text as T
  import Dhall(Generic, Natural, input, auto, FromDhall)
  import qualified Program.Mighty.Dhall.Option as Do
  
  #else
  import Program.Mighty.Types
  #endif
  
  import Program.Mighty.Parser
  
  ----------------------------------------------------------------
  
  data Option = Option {
      opt_port :: Natural
    , opt_host :: String
    , opt_debug_mode :: Bool
    , opt_user  :: String
    , opt_group :: String
    , opt_pid_file    :: FilePath
    , opt_report_file :: FilePath
    , opt_logging :: Bool
    , opt_log_file :: FilePath
    , opt_log_file_size :: Natural
    , opt_log_backup_number :: Natural
    , opt_index_file :: FilePath
    , opt_index_cgi  :: FilePath
    , opt_status_file_dir :: FilePath
    , opt_connection_timeout :: Natural
    , opt_proxy_timeout      :: Natural
    , opt_fd_cache_duration  :: Natural
    , opt_service :: Natural
    , opt_tls_port :: Natural
    , opt_tls_cert_file   :: FilePath
    , opt_tls_chain_files :: FilePath
    , opt_tls_key_file    :: FilePath
    , opt_quic_port :: Natural
    , opt_quic_addr :: [String]
    , opt_quic_debug_dir :: Maybe FilePath
    , opt_quic_qlog_dir  :: Maybe FilePath
    , opt_server_name :: String
    , opt_routing_file :: Maybe String
  #ifdef DHALL
  } deriving (Eq, Show, Generic)
  #else
  } deriving (Eq, Show)
  #endif
  
  #ifdef DHALL
  instance FromDhall Option
  #endif
  
  -- | Getting a default 'Option'.
  defaultOption :: Option
  defaultOption = Option {
      opt_port = 8080
    , opt_host = "*"
    , opt_debug_mode = True
    , opt_user  = "root"
    , opt_group = "root"
    , opt_pid_file    = "/var/run/mighty.pid"
    , opt_report_file = "/tmp/mighty_report"
    , opt_logging = True
    , opt_log_file = "/var/log/mighty"
    , opt_log_file_size = 16777216
    , opt_log_backup_number = 10
    , opt_index_file = "index.html"
    , opt_index_cgi  = "index.cgi"
    , opt_status_file_dir = "/usr/local/share/mighty/status"
    , opt_connection_timeout = 30
    , opt_proxy_timeout      = 0
    , opt_fd_cache_duration  = 10
    , opt_service = 0
    , opt_tls_port = 443
    , opt_tls_cert_file   = "cert.pem"
    , opt_tls_chain_files = "chain.pem"
    , opt_tls_key_file    = "privkey.pem"
    , opt_quic_port = 443
    , opt_quic_addr = ["127.0.0.1"]
    , opt_quic_debug_dir = Nothing
    , opt_quic_qlog_dir  = Nothing
    , opt_server_name = "Dummy"
    , opt_routing_file = Nothing
  }
  
  ----------------------------------------------------------------
  -- | Parsing a configuration file to get an 'Option'.
  parseOption :: FilePath -> IO Option
  parseOption file = makeOpt defaultOption <$> parseConfig file
  
  #ifdef DHALL
  parseOptionDhall :: FilePath -> IO Option
  parseOptionDhall = fmap optionFromDhall . input auto . fromString
  
  optionFromDhall :: Do.Option -> Option
  optionFromDhall o = Option
    { opt_port = Do.port o
    , opt_host = T.unpack $ Do.host o
    , opt_debug_mode = Do.debugMode o
    , opt_user  = T.unpack $ Do.user o
    , opt_group = T.unpack $ Do.group o
    , opt_pid_file    = T.unpack $ Do.pidFile o
    , opt_report_file = T.unpack $ Do.reportFile o
    , opt_logging = Do.logging o
    , opt_log_file = T.unpack $ Do.logFile o
    , opt_log_file_size = Do.logFileSize o
    , opt_log_backup_number = Do.logBackupNumber o
    , opt_index_file = T.unpack $ Do.indexFile o
    , opt_index_cgi  = T.unpack $ Do.indexCgi o
    , opt_status_file_dir = T.unpack $ Do.statusFileDir o
    , opt_connection_timeout = Do.connectionTimeout o
    , opt_proxy_timeout      = Do.proxyTimeout o
    , opt_fd_cache_duration  = Do.fdCacheDuration o
    , opt_service = Do.service o
    , opt_tls_port = Do.tlsPort o
    , opt_tls_cert_file   = T.unpack $ Do.tlsCertFile o
    , opt_tls_chain_files = T.unpack $ Do.tlsChainFiles o
    , opt_tls_key_file    = T.unpack $ Do.tlsKeyFile o
    , opt_quic_addr = T.unpack <$> Do.quicAddr o
    , opt_quic_port = Do.quicPort o
    , opt_quic_debug_dir = T.unpack <$> Do.quicDebugDir o
    , opt_quic_qlog_dir  = T.unpack <$> Do.quicQlogDir o
    , opt_server_name = "Dummy"
    , opt_routing_file = Nothing
  }
  #endif
  
  ----------------------------------------------------------------
  
  makeOpt :: Option -> [Conf] -> Option
  makeOpt def conf = Option {
      opt_port               = get "Port" opt_port
    , opt_host               = get "Host" opt_host
    , opt_debug_mode         = get "Debug_Mode" opt_debug_mode
    , opt_user               = get "User" opt_user
    , opt_group              = get "Group" opt_group
    , opt_pid_file           = get "Pid_File" opt_pid_file
    , opt_report_file        = get "Report_File" opt_report_file
    , opt_logging            = get "Logging" opt_logging
    , opt_log_file           = get "Log_File" opt_log_file
    , opt_log_file_size      = get "Log_File_Size" opt_log_file_size
    , opt_log_backup_number  = get "Log_Backup_Number" opt_log_backup_number
    , opt_index_file         = get "Index_File" opt_index_file
    , opt_index_cgi          = get "Index_Cgi" opt_index_cgi
    , opt_status_file_dir    = get "Status_File_Dir" opt_status_file_dir
    , opt_connection_timeout = get "Connection_Timeout" opt_connection_timeout
    , opt_proxy_timeout      = get "Proxy_Timeout" opt_proxy_timeout
    , opt_fd_cache_duration  = get "Fd_Cache_Duration" opt_fd_cache_duration
    , opt_service            = get "Service" opt_service
    , opt_tls_port           = get "Tls_Port" opt_tls_port
    , opt_tls_cert_file      = get "Tls_Cert_File" opt_tls_cert_file
    , opt_tls_chain_files    = get "Tls_Chain_Files" opt_tls_chain_files
    , opt_tls_key_file       = get "Tls_Key_File" opt_tls_key_file
    , opt_quic_addr          = get "Quic_Addr" opt_quic_addr
    , opt_quic_port          = get "Quic_Port" opt_quic_port
    , opt_quic_debug_dir     = get "Quic_Debug_Dir" opt_quic_debug_dir
    , opt_quic_qlog_dir      = get "Quic_Qlog_Dir" opt_quic_qlog_dir
    , opt_server_name        = "Dummy"
    , opt_routing_file       = Nothing
    }
    where
      get k func = maybe (func def) fromConf $ lookup k conf
  
  ----------------------------------------------------------------
  
  type Conf = (String, ConfValue)
  
  data ConfValue = CV_Natural Natural | CV_Bool Bool | CV_String String deriving (Eq,Show)
  
  class FromConf a where
      fromConf :: ConfValue -> a
  
  instance FromConf Natural where
      fromConf (CV_Natural n) = n
      fromConf _ = error "fromConf int"
  
  instance FromConf Bool where
      fromConf (CV_Bool b) = b
      fromConf _ = error "fromConf bool"
  
  instance FromConf String where
      fromConf (CV_String s) = s
      fromConf _ = error "fromConf string"
  
  instance FromConf (Maybe String) where
      fromConf (CV_String "") = Nothing
      fromConf (CV_String s)  = Just s
      fromConf _ = error "fromConf string"
  
  instance FromConf [String] where
      fromConf (CV_String s)  = splitOn "," s
      fromConf _ = error "fromConf string"
  
  ----------------------------------------------------------------
  
  parseConfig :: FilePath -> IO [Conf]
  parseConfig = parseFile config
  
  ----------------------------------------------------------------
  
  config :: Parser [Conf]
  config = commentLines *> many cfield <* eof
    where
      cfield = field <* commentLines
  
  field :: Parser Conf
  field = (,) <$> key <*> (sep *> value)
  
  key :: Parser String
  key = many1 (oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_") <* spcs
  
  sep :: Parser ()
  sep = () <$ char ':' *> spcs
  
  value :: Parser ConfValue
  value = choice [try cv_natural, try cv_bool, cv_string]
  
  -- Trailing should be included in try to allow IP addresses.
  cv_natural :: Parser ConfValue
  cv_natural = CV_Natural . read <$> many1 digit <* trailing
  
  cv_bool :: Parser ConfValue
  cv_bool = CV_Bool True  <$ string "Yes" <* trailing <|>
            CV_Bool False <$ string "No"  <* trailing
  
  cv_string :: Parser ConfValue
  cv_string = CV_String <$> many (noneOf " \t\n") <* trailing
  