{-# LANGUAGE ConstraintKinds   #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Client.Reporting
-- Copyright   :  (c) David Waern 2008
-- License     :  BSD-like
--
-- Maintainer  :  david.waern@gmail.com
-- Stability   :  experimental
-- Portability :  portable
--
-- Anonymous build report data structure, printing and parsing
--
-----------------------------------------------------------------------------
module Distribution.Client.BuildReports.Anonymous (
    BuildReport(..),
    InstallOutcome(..),
    Outcome(..),

    -- * Constructing and writing reports
    newBuildReport,

    -- * parsing and pretty printing
    parseBuildReport,
    parseBuildReportList,
    showBuildReport,
--    showList,
  ) where

import Distribution.Client.Compat.Prelude
import Prelude ()

import Distribution.CabalSpecVersion
import Distribution.Client.BuildReports.Types
import Distribution.Client.Utils              (cabalInstallVersion)
import Distribution.Compiler                  (CompilerId (..))
import Distribution.FieldGrammar
import Distribution.Fields                    (readFields, showFields)
import Distribution.Fields.ParseResult        (ParseResult, parseFatalFailure, runParseResult)
import Distribution.Package                   (PackageIdentifier (..), mkPackageName)
import Distribution.PackageDescription        (FlagAssignment)
import Distribution.Parsec                    (PError (..), zeroPos)
import Distribution.System                    (Arch, OS)

import qualified Distribution.Client.BuildReports.Lens as L
import qualified Distribution.Client.Types             as BR (BuildFailure (..), BuildOutcome, BuildResult (..), DocsResult (..), TestsResult (..))

import qualified Data.ByteString       as BS
import qualified Data.ByteString.Char8 as BS8


-------------------------------------------------------------------------------
-- New
-------------------------------------------------------------------------------

newBuildReport :: OS -> Arch -> CompilerId -> PackageIdentifier -> FlagAssignment
    -> [PackageIdentifier] -> BR.BuildOutcome -> BuildReport
newBuildReport os' arch' comp pkgid flags deps result =
  BuildReport {
    package               = pkgid,
    os                    = os',
    arch                  = arch',
    compiler              = comp,
    client                = cabalInstallID,
    flagAssignment        = flags,
    dependencies          = deps,
    installOutcome        = convertInstallOutcome,
--    cabalVersion          = undefined
    docsOutcome           = convertDocsOutcome,
    testsOutcome          = convertTestsOutcome
  }
  where
    convertInstallOutcome = case result of
      Left  BR.PlanningFailed      -> PlanningFailed
      Left  (BR.DependentFailed p) -> DependencyFailed p
      Left  (BR.DownloadFailed  _) -> DownloadFailed
      Left  (BR.UnpackFailed    _) -> UnpackFailed
      Left  (BR.ConfigureFailed _) -> ConfigureFailed
      Left  (BR.BuildFailed     _) -> BuildFailed
      Left  (BR.TestsFailed     _) -> TestsFailed
      Left  (BR.InstallFailed   _) -> InstallFailed
      Right (BR.BuildResult _ _ _) -> InstallOk
    convertDocsOutcome = case result of
      Left _                                      -> NotTried
      Right (BR.BuildResult BR.DocsNotTried _ _)  -> NotTried
      Right (BR.BuildResult BR.DocsFailed _ _)    -> Failed
      Right (BR.BuildResult BR.DocsOk _ _)        -> Ok
    convertTestsOutcome = case result of
      Left  (BR.TestsFailed _)                    -> Failed
      Left _                                      -> NotTried
      Right (BR.BuildResult _ BR.TestsNotTried _) -> NotTried
      Right (BR.BuildResult _ BR.TestsOk _)       -> Ok

cabalInstallID :: PackageIdentifier
cabalInstallID =
  PackageIdentifier (mkPackageName "cabal-install") cabalInstallVersion

-------------------------------------------------------------------------------
-- FieldGrammar
-------------------------------------------------------------------------------

fieldDescrs
    :: ( Applicative (g BuildReport), FieldGrammar c g
       , c (Identity Arch)
       , c (Identity CompilerId)
       , c (Identity FlagAssignment)
       , c (Identity InstallOutcome)
       , c (Identity OS)
       , c (Identity Outcome)
       , c (Identity PackageIdentifier)
       , c (List VCat (Identity PackageIdentifier) PackageIdentifier)
       )
    => g BuildReport BuildReport
fieldDescrs = BuildReport
    <$> uniqueField       "package"                           L.package
    <*> uniqueField       "os"                                L.os
    <*> uniqueField       "arch"                              L.arch
    <*> uniqueField       "compiler"                          L.compiler
    <*> uniqueField       "client"                            L.client
    <*> monoidalField     "flags"                             L.flagAssignment
    <*> monoidalFieldAla  "dependencies"       (alaList VCat) L.dependencies
    <*> uniqueField       "install-outcome"                   L.installOutcome
    <*> uniqueField       "docs-outcome"                      L.docsOutcome
    <*> uniqueField       "tests-outcome"                     L.testsOutcome

-- -----------------------------------------------------------------------------
-- Parsing

parseBuildReport :: BS.ByteString -> Either String BuildReport
parseBuildReport s = case snd $ runParseResult $ parseFields s of
  Left (_, perrors) -> Left $ unlines [ err | PError _ err <- toList perrors ]
  Right report -> Right report

parseFields :: BS.ByteString -> ParseResult BuildReport
parseFields input = do
  fields <- either (parseFatalFailure zeroPos . show) pure $ readFields input
  case partitionFields fields of
    (fields', []) -> parseFieldGrammar CabalSpecV2_4 fields' fieldDescrs
    _otherwise    -> parseFatalFailure zeroPos "found sections in BuildReport"

parseBuildReportList :: BS.ByteString -> [BuildReport]
parseBuildReportList str =
  [ report | Right report <- map parseBuildReport (split str) ]

  where
    split :: BS.ByteString -> [BS.ByteString]
    split = filter (not . BS.null) . unfoldr chunk . BS8.lines
    chunk [] = Nothing
    chunk ls = case break BS.null ls of
                 (r, rs) -> Just (BS8.unlines r, dropWhile BS.null rs)

-- -----------------------------------------------------------------------------
-- Pretty-printing

showBuildReport :: BuildReport -> String
showBuildReport = showFields (const []) . prettyFieldGrammar CabalSpecV2_4 fieldDescrs