{-# LANGUAGE TupleSections #-}

module Options where

import           Control.Applicative
import           Control.Monad.M
import qualified Data.List as L

import           Data.Monoid
import qualified Distribution.Package as C
import qualified Distribution.Version as CV
import           Distribution.Text
import           Options.Applicative.Types (readerAsk)
import           Options.Applicative.Builder
import           Options.Applicative.Common
import           Options.Cabal
import           Options.CabalConstraints (toConstraints, none, CabalConstraints)
import           Options.DbProvider
import           PackageId
import           Pipes
import qualified Data.Set as S

data Options = Options { 
  dbprovider :: DbProvider,
  outputDir  :: FilePath,
  quiet    :: Bool,
  cabal :: Maybe FilePath,
  cabalConstraints :: CabalConstraints, 
  packages :: [C.PackageId]
} deriving Show

packageReadM :: Text a => ReadM a
packageReadM = do
  s <- readerAsk
  maybe 
   (readerError $ "failed parsing packages:\n " ++ s)
   return 
   -- Qualified, as simpleParse is a little obscure.
   (Distribution.Text.simpleParse s) 

parser :: Parser Options
parser = 
  Options <$> 
    option toProvider 
      (long "dbprovider" 
      <> short 'p'
      <> metavar "<provider,args>"
      <> value (CabalSandbox Nothing) 
      <> help "a ghc package db provider: cabal|ghc|dir\n")
    <*>
    strOption (
     long "output" 
     <> short 'o'
     <> metavar "<dir>" 
     <> value "./docsets" 
     <> help "the directory to write created docsets to")
    <*>
    switch (long "quiet" <> short 'q' <> help "set to quiet output")
    <*>
    option (Just <$> readerAsk)
     (long "cabal"
     <> short 'c'  
     <> metavar "<file.cabal>" 
     <> value Nothing
     <> help "the cabal file to retrieve package dependencies from")
    <*>
    option toConstraints
      (long "cabal-constraints"
      <> short 'r'
      <> value none 
      <> metavar "executable=name, .."
      <> help "limit package results from a cabal file source, see documentation")
    <*>
    many (
     argument packageReadM (metavar "packages" <>
     help "a list of packages to specifically build, e.g. either-1.0.1 text"
     ))

-- | Given two lists of package satisfying strings, 
-- return a list that is non-duplicate, the most versioned of the two.
-- e.g. if 'either-4.1.0' is in one, and 'either' is the other, 
-- the versioned is chosen. If both are versioned, both appear in
-- the final result.
reduce :: [C.PackageId] -> [C.PackageId]
reduce = fromAsc . L.sort where
  -- Here we exploit the fact that you only need to examine the next member of 
  -- an ascending list to determine a version
  fromAsc :: [C.PackageId] -> [C.PackageId]
  fromAsc []      = []
  fromAsc ([p])  = [p]
  fromAsc (p:nxt:rest)
    | p == nxt = -- duplicate 
      fromAsc (nxt:rest) 
    | unversioned p == unversioned nxt = 
      if unversioned p == p then
        fromAsc $ nxt:rest
      else -- both are versioned by list ordering
        p : nxt : fromAsc rest 
    | otherwise = -- they're different packages
        p : fromAsc ( nxt : rest )

versionless :: String -> C.PackageId
versionless n = C.PackageIdentifier (C.PackageName n) $ CV.Version [] [] 

prod_Packages :: Options -> ProducerM (S.Set String) () 
prod_Packages options = do
  cabal_dependencies <-
    lift $ case cabal options of 
      Nothing -> return [] 
      Just fp -> (S.toList . S.map versionless) 
                 <$> readPackages fp (cabalConstraints options)
  yield . S.fromList . map (show . disp) $ 
    reduce (packages options ++ cabal_dependencies)
