{-# LANGUAGE OverloadedStrings #-}

module Bricoleur.Config
  ( Config (..),
    Source (..),
    Expose (..),
    getConfig,
    example,
  )
where

import Control.Applicative ((<|>))
import qualified Data.Adnot as A
import qualified Data.ByteString as B
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import qualified Data.Vector as V

data Config = Config
  { confDocument :: FilePath,
    confSources :: [Source]
  }
  deriving (Eq, Show)

instance A.FromAdnot Config where
  parseAdnot = A.withSumNamed "config file" "document" go
    where
      go payload
        | Just file <- payload V.!? 0 =
          Config <$> A.parseAdnot file
            <*> mapM A.parseAdnot (V.toList (V.tail payload))
        | otherwise = Left "expected source file in config"

data Source = Source
  { sourceName :: T.Text,
    sourceDir :: FilePath,
    sourceCommands :: [String],
    sourceExpose :: Expose
  }
  deriving (Eq, Show)

instance A.FromAdnot Source where
  parseAdnot = A.withProduct "source" $ \p -> do
    name <- p A..: "name"
    dir <- p A..: "dir"
    cmds <- p A..: "cmd"
    expose <- p A..: "expose"
    return (Source name dir cmds expose)

data Expose
  = ExposeFile FilePath
  | ExposeSections FilePath
  | NamedMap (M.Map T.Text Expose)
  deriving (Eq, Show)

instance A.FromAdnot Expose where
  parseAdnot v = file v <|> sections v <|> namedMap v
    where
      file = A.withSumNamed "exposed fragments" "file" $ \ps ->
        case V.toList ps of
          [] -> Left "Expected name for file"
          [f] -> ExposeFile <$> A.parseAdnot f
          _ -> Left "Too many arguments to file"

      sections = A.withSumNamed "exposed fragments" "sections" $ \ps ->
        case V.toList ps of
          [] -> Left "Expected name for sections"
          [f] -> ExposeSections <$> A.parseAdnot f
          _ -> Left "Too many arguments to sections"

      namedMap = A.withProduct "exposed fragments" $
        fmap NamedMap . mapM A.parseAdnot

parseConfig :: B.ByteString -> Either String Config
parseConfig = A.decode

getConfig :: FilePath -> IO (Either String Config)
getConfig loc = do
  conf <- B.readFile loc
  return (parseConfig conf)

example :: Config
example =
  Config
    { confDocument = "main.md",
      confSources =
        [ Source
            { sourceName = "rust-sample",
              sourceDir = "s1",
              sourceCommands = ["cargo clean", "cargo build"],
              sourceExpose = ExposeFile "src/main.rs"
            },
          Source
            { sourceName = "haskell-sample",
              sourceDir = "s2",
              sourceCommands = ["cabal new-build"],
              sourceExpose = ExposeSections "Main.hs"
            }
        ]
    }
