content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE TemplateHaskell   #-}
  
  module Configuration
    ( ImprovizConfig
    , screenWidth
    , screenHeight
    , fullscreenDisplay
    , debug
    , screen
    , fontConfig
    , textureDirectories
    , geometryDirectories
    , codeFiles
    , materialDirectories
    , filterDirectories
    , assetsDirectory
    , serverPort
    , osc
    , apptitle
    , decorated
    , showText
    , getConfig
    , loadFolderConfig
    ) where
  
  import           Control.Applicative            ( (<|>) )
  import           Data.Maybe                     ( fromMaybe )
  import           Options.Applicative            ( execParser )
  import           System.FilePath.Posix          ( (</>) )
  
  import           Lens.Simple
  
  import           Configuration.CLI              ( ImprovizCLIConfig )
  import qualified Configuration.CLI             as CLI
  import           Configuration.Font             ( ImprovizFontConfig
                                                  , defaultFontConfig
                                                  )
  import           Configuration.OSC              ( ImprovizOSCConfig
                                                  , defaultOSCConfig
                                                  )
  import           Configuration.Screen           ( ImprovizScreenConfig
                                                  , defaultScreenConfig
                                                  )
  import           Data.Yaml                      ( (.!=)
                                                  , (.:?)
                                                  , FromJSON(..)
                                                  )
  import qualified Data.Yaml                     as Y
  
  data ImprovizConfig = ImprovizConfig
    { _screenWidth         :: Int
    , _screenHeight        :: Int
    , _fullscreenDisplay   :: Maybe Int
    , _debug               :: Bool
    , _screen              :: ImprovizScreenConfig
    , _fontConfig          :: ImprovizFontConfig
    , _textureDirectories  :: [FilePath]
    , _geometryDirectories :: [FilePath]
    , _materialDirectories :: [FilePath]
    , _filterDirectories   :: [FilePath]
    , _codeFiles           :: [FilePath]
    , _assetsDirectory     :: FilePath
    , _serverPort          :: Int
    , _osc                 :: ImprovizOSCConfig
    , _apptitle            :: String
    , _decorated           :: Bool
    , _showText            :: Bool
    }
    deriving Show
  
  makeLenses ''ImprovizConfig
  
  defaultConfigFile :: FilePath
  defaultConfigFile = "./improviz.yaml"
  
  defaultConfig :: ImprovizConfig
  defaultConfig = ImprovizConfig { _screenWidth         = 640
                                 , _screenHeight        = 480
                                 , _fullscreenDisplay   = Nothing
                                 , _debug               = False
                                 , _screen              = defaultScreenConfig
                                 , _fontConfig          = defaultFontConfig
                                 , _textureDirectories  = ["./textures"]
                                 , _geometryDirectories = ["./geometries"]
                                 , _materialDirectories = ["./materials"]
                                 , _filterDirectories   = ["./filters"]
                                 , _codeFiles           = []
                                 , _assetsDirectory     = "./assets"
                                 , _serverPort          = 3000
                                 , _osc                 = defaultOSCConfig
                                 , _apptitle            = "Improviz"
                                 , _decorated           = False
                                 , _showText            = True
                                 }
  
  instance FromJSON ImprovizConfig where
    parseJSON (Y.Object v) =
      ImprovizConfig
        <$> v
        .:? "screenwidth"
        .!= (defaultConfig ^. screenWidth)
        <*> v
        .:? "screenheight"
        .!= (defaultConfig ^. screenHeight)
        <*> v
        .:? "fullscreen"
        <*> v
        .:? "debug"
        .!= (defaultConfig ^. debug)
        <*> v
        .:? "screen"
        .!= (defaultConfig ^. screen)
        <*> v
        .:? "font"
        .!= (defaultConfig ^. fontConfig)
        <*> v
        .:? "textureDirectories"
        .!= (defaultConfig ^. textureDirectories)
        <*> v
        .:? "geometryDirectories"
        .!= (defaultConfig ^. geometryDirectories)
        <*> v
        .:? "materialDirectories"
        .!= (defaultConfig ^. materialDirectories)
        <*> v
        .:? "filterDirectories"
        .!= (defaultConfig ^. filterDirectories)
        <*> v
        .:? "codeFiles"
        .!= (defaultConfig ^. codeFiles)
        <*> v
        .:? "assetsDirectory"
        .!= (defaultConfig ^. assetsDirectory)
        <*> v
        .:? "serverPort"
        .!= (defaultConfig ^. serverPort)
        <*> v
        .:? "osc"
        .!= (defaultConfig ^. osc)
        <*> v
        .:? "apptitle"
        .!= (defaultConfig ^. apptitle)
        <*> v
        .:? "decorated"
        .!= (defaultConfig ^. decorated)
        <*> v
        .:? "showText"
        .!= (defaultConfig ^. showText)
    parseJSON _ = fail "Expected Object for Config value"
  
  getConfig :: IO ImprovizConfig
  getConfig = do
    cliCfg  <- execParser CLI.opts
    fileCfg <- readConfigFile
      $ fromMaybe defaultConfigFile (cliCfg ^. CLI.configFilePath)
    let cfg = fromMaybe defaultConfig fileCfg
    return $ mergeConfigs cfg cliCfg
  
  readConfigFile :: FilePath -> IO (Maybe ImprovizConfig)
  readConfigFile cfgFilePath = do
    yaml <- Y.decodeFileEither cfgFilePath
    case yaml of
      Left  err    -> print err >> return Nothing
      Right config -> return (Just config)
  
  mergeConfigs :: ImprovizConfig -> ImprovizCLIConfig -> ImprovizConfig
  mergeConfigs cfg cliCfg = cfg
    { _screenWidth = fromMaybe (cfg ^. screenWidth) (cliCfg ^. CLI.screenWidth)
    , _screenHeight = fromMaybe (cfg ^. screenHeight) (cliCfg ^. CLI.screenHeight)
    , _fullscreenDisplay = (cfg ^. fullscreenDisplay)
                             <|> (cliCfg ^. CLI.fullscreenDisplay)
    , _debug = (cfg ^. debug) || (cliCfg ^. CLI.debug)
    }
  
  loadFolderConfig :: FromJSON a => FilePath -> IO (Either String a)
  loadFolderConfig folderPath = do
    yaml <- Y.decodeFileEither $ folderPath </> "config.yaml"
    return $ case yaml of
      Left  err -> Left $ Y.prettyPrintParseException err
      Right cfg -> Right cfg
  