{-# LANGUAGE TypeFamilies, LambdaCase, DeriveFunctor, StandaloneDeriving #-}
module Data.Extensible.GetOpt (OptionDescr(..)
  , OptDescr'
  , getOptRecord
  , withGetOpt
  
  , optFlag
  , optLastArg
  
  , optNoArg
  , optReqArg
  , optionNoArg
  , optionReqArg
  , optionOptArg) where
import Control.Monad.IO.Class
import Data.Extensible.Class
import Data.Extensible.Field
import Data.Extensible.Internal.Rig
import Data.Extensible.Product
import Data.Extensible.Wrapper
import Data.Functor.Identity
import Data.List (foldl')
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
data OptionDescr h a = forall s. OptionDescr (s -> h a) !s (OptDescr (s -> s))
deriving instance Functor h => Functor (OptionDescr h)
supplyOption :: Maybe String -> OptionDescr h a -> OptionDescr h a
supplyOption str od@(OptionDescr k s opt@(Option _ _ arg _)) = case (str, arg) of
  (Just a, ReqArg f _) -> OptionDescr k (f a s) opt
  (Nothing, NoArg f) -> OptionDescr k (f s) opt
  (a, OptArg f _) -> OptionDescr k (f a s) opt
  _ -> od
extendArg :: (Maybe String -> a -> b) -> ArgDescr a -> ArgDescr b
extendArg f (NoArg a) = NoArg $ f Nothing a
extendArg f (ReqArg a ph) = ReqArg (\s -> f (Just s) (a s)) ph
extendArg f (OptArg a ph) = OptArg (f <*> a) ph
type OptDescr' = OptionDescr Identity
instance Wrapper (OptionDescr h) where
  type Repr (OptionDescr h) a = OptionDescr h a
  _Wrapper = id
optNoArg :: [Char] 
    -> [String] 
    -> String 
    -> OptDescr' Int
optNoArg = optionNoArg Identity
optFlag :: [Char] 
    -> [String] 
    -> String 
    -> OptDescr' Bool
optFlag = optionNoArg (pure . (>0))
optionNoArg :: (Int -> h a) -> [Char] -> [String] -> String -> OptionDescr h a
optionNoArg f ss ls expl = OptionDescr f 0 $ Option ss ls (NoArg (+1)) expl
optReqArg :: [Char] 
    -> [String] 
    -> String 
    -> String 
    -> OptDescr' [String]
optReqArg = optionReqArg Identity
optLastArg :: [Char] 
    -> [String] 
    -> String 
    -> String 
    -> OptDescr' (Maybe String)
optLastArg ss ls ph expl = OptionDescr pure Nothing $ Option ss ls (ReqArg (const . Just) ph) expl
optionReqArg :: ([String] -> h a) -> [Char] -> [String] -> String -> String -> OptionDescr h a
optionReqArg f ss ls ph expl = OptionDescr f [] $ Option ss ls (ReqArg (:) ph) expl
optionOptArg :: ([Maybe String] -> h a) -> [Char] -> [String] -> String -> String -> OptionDescr h a
optionOptArg f ss ls ph expl = OptionDescr f [] $ Option ss ls (OptArg (:) ph) expl
getOptRecord :: RecordOf (OptionDescr h) xs 
    -> [String] 
    -> (RecordOf h xs, [String], [String], String -> String) 
getOptRecord descs args = (result, rs, es, flip usageInfo updaters) where
  (fs, rs, es) = getOpt Permute updaters args
  updaters = hfoldrWithIndex
      (\i (Field (OptionDescr _ _ (Option ss ls arg expl))) -> (:)
          $ Option ss ls (extendArg (\a _ -> over (pieceAt i) (liftField (supplyOption a))) arg) expl)
      [] descs
  result = hmap (\(Field (OptionDescr k x _)) -> Field (k x))
      $ foldl' (flip id) descs fs
withGetOpt :: MonadIO m => String 
  -> RecordOf (OptionDescr h) xs 
  -> (RecordOf h xs -> [String] -> m a) 
  -> m a
withGetOpt nonOptUsage descs k = getOptRecord descs <$> liftIO getArgs >>= \case
  (r, xs, [], _) -> k r xs
  (_, _, errs, usage) -> liftIO $ do
    mapM_ (hPutStrLn stderr) errs
    getProgName >>= die . usage . (++ (' ' : nonOptUsage))