{- |
Copyright: (c) 2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

Static analysis summary.
-}

module Stan.Analysis.Summary
    ( Summary (..)
    , createSummary
    ) where

import Stan.Analysis (Analysis (..))
import Stan.Category (Category)
import Stan.Core.Id (Id)
import Stan.Core.ModuleName (ModuleName)
import Stan.Inspection (Inspection (..))
import Stan.Inspection.All (getInspectionById)
import Stan.Observation (Observation (..))
import Stan.Severity (Severity)

import qualified Data.HashMap.Strict as HM


-- | Short info about analysis.
data Summary = Summary
    { Summary -> Id Inspection
summaryInspectionId :: !(Id Inspection)  -- ^ The most popular 'Inspection'
    , Summary -> Category
summaryCategory     :: !Category  -- ^ The most popular 'Category'
    , Summary -> ModuleName
summaryModule       :: !ModuleName  -- ^ Module with the biggest number of observations
    , Summary -> Severity
summarySeverity     :: !Severity  -- ^ The highest 'Severity'
    }

data SummaryData = SummaryData
    { SummaryData -> HashMap (Id Inspection) Int
sdInspections :: !(HashMap (Id Inspection) Int)  -- ^ Count of each inspection
    , SummaryData -> HashMap Category Int
sdCategories  :: !(HashMap Category Int)  -- ^ Count of each category
    , SummaryData -> HashMap ModuleName Int
sdModules     :: !(HashMap ModuleName Int)  -- ^ Count of observations per module
    , SummaryData -> Maybe Severity
sdSeverity    :: !(Maybe Severity)  -- ^ Highest severity
    }

emptySummaryData :: SummaryData
emptySummaryData :: SummaryData
emptySummaryData = $WSummaryData :: HashMap (Id Inspection) Int
-> HashMap Category Int
-> HashMap ModuleName Int
-> Maybe Severity
-> SummaryData
SummaryData
    { sdInspections :: HashMap (Id Inspection) Int
sdInspections = HashMap (Id Inspection) Int
forall a. Monoid a => a
mempty
    , sdCategories :: HashMap Category Int
sdCategories  = HashMap Category Int
forall a. Monoid a => a
mempty
    , sdModules :: HashMap ModuleName Int
sdModules     = HashMap ModuleName Int
forall a. Monoid a => a
mempty
    , sdSeverity :: Maybe Severity
sdSeverity    = Maybe Severity
forall a. Maybe a
Nothing
    }

summaryFromData :: SummaryData -> Maybe Summary
summaryFromData :: SummaryData -> Maybe Summary
summaryFromData SummaryData{..} = do
    Id Inspection
summaryInspectionId <- [(Id Inspection, Int)] -> Maybe (Id Inspection)
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(Id Inspection, Int)] -> Maybe (Id Inspection))
-> [(Id Inspection, Int)] -> Maybe (Id Inspection)
forall a b. (a -> b) -> a -> b
$ HashMap (Id Inspection) Int -> [(Id Inspection, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap (Id Inspection) Int
sdInspections
    Category
summaryCategory     <- [(Category, Int)] -> Maybe Category
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(Category, Int)] -> Maybe Category)
-> [(Category, Int)] -> Maybe Category
forall a b. (a -> b) -> a -> b
$ HashMap Category Int -> [(Category, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap Category Int
sdCategories
    ModuleName
summaryModule       <- [(ModuleName, Int)] -> Maybe ModuleName
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(ModuleName, Int)] -> Maybe ModuleName)
-> [(ModuleName, Int)] -> Maybe ModuleName
forall a b. (a -> b) -> a -> b
$ HashMap ModuleName Int -> [(ModuleName, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap ModuleName Int
sdModules
    Severity
summarySeverity     <- Maybe Severity
sdSeverity
    Summary -> Maybe Summary
forall (f :: * -> *) a. Applicative f => a -> f a
pure $WSummary :: Id Inspection -> Category -> ModuleName -> Severity -> Summary
Summary{..}
  where
    maxOnSnd :: Ord b => [(a, b)] -> Maybe a
    maxOnSnd :: [(a, b)] -> Maybe a
maxOnSnd = (NonEmpty (a, b) -> a) -> [(a, b)] -> Maybe a
forall a b. (NonEmpty a -> b) -> [a] -> Maybe b
viaNonEmpty ((a, b) -> a
forall a b. (a, b) -> a
fst ((a, b) -> a)
-> (NonEmpty (a, b) -> (a, b)) -> NonEmpty (a, b) -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((a, b) -> b) -> NonEmpty (a, b) -> (a, b)
forall b a. Ord b => (a -> b) -> NonEmpty a -> a
maximumOn1 (a, b) -> b
forall a b. (a, b) -> b
snd)

{- | Assemble 'Summary' after analysis. Returns 'Nothing' when there's
no 'Observations'. Otherwise, there's at least one observation, which
means that we can find the most popular 'Inspection', 'Category' and
the highest 'Severity'.
-}
createSummary :: Analysis -> Maybe Summary
createSummary :: Analysis -> Maybe Summary
createSummary Analysis{..} = SummaryData -> Maybe Summary
summaryFromData
    (SummaryData -> Maybe Summary) -> SummaryData -> Maybe Summary
forall a b. (a -> b) -> a -> b
$ (SummaryData -> Observation -> SummaryData)
-> SummaryData -> Observations -> SummaryData
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' SummaryData -> Observation -> SummaryData
handleObservation SummaryData
emptySummaryData Observations
analysisObservations
  where
    handleObservation :: SummaryData -> Observation -> SummaryData
    handleObservation :: SummaryData -> Observation -> SummaryData
handleObservation SummaryData{..} Observation{..} =
        let Inspection{..} = Id Inspection -> Inspection
getInspectionById Id Inspection
observationInspectionId
        in $WSummaryData :: HashMap (Id Inspection) Int
-> HashMap Category Int
-> HashMap ModuleName Int
-> Maybe Severity
-> SummaryData
SummaryData
            -- increase count for the current observations
            { sdInspections :: HashMap (Id Inspection) Int
sdInspections = (Int -> Int -> Int)
-> Id Inspection
-> Int
-> HashMap (Id Inspection) Int
-> HashMap (Id Inspection) Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) Id Inspection
observationInspectionId 1 HashMap (Id Inspection) Int
sdInspections

            -- increase count for each category
            , sdCategories :: HashMap Category Int
sdCategories = (Category -> HashMap Category Int -> HashMap Category Int)
-> HashMap Category Int
-> NonEmpty Category
-> HashMap Category Int
forall (f :: * -> *) a b.
Foldable f =>
(a -> b -> b) -> b -> f a -> b
flipfoldl'
                (\cat :: Category
cat -> (Int -> Int -> Int)
-> Category -> Int -> HashMap Category Int -> HashMap Category Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) Category
cat 1)
                HashMap Category Int
sdCategories
                NonEmpty Category
inspectionCategory

            -- increase count the module
            , sdModules :: HashMap ModuleName Int
sdModules = (Int -> Int -> Int)
-> ModuleName
-> Int
-> HashMap ModuleName Int
-> HashMap ModuleName Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) ModuleName
observationModuleName 1 HashMap ModuleName Int
sdModules

            -- choose the highest severity
            , sdSeverity :: Maybe Severity
sdSeverity = Maybe Severity -> Maybe Severity -> Maybe Severity
forall a. Ord a => a -> a -> a
max Maybe Severity
sdSeverity (Severity -> Maybe Severity
forall a. a -> Maybe a
Just Severity
inspectionSeverity)
            }

-- | Fast, strict maximum
maximumOn1 :: forall b a . Ord b => (a -> b) -> NonEmpty a -> a
maximumOn1 :: (a -> b) -> NonEmpty a -> a
maximumOn1 f :: a -> b
f (x :: a
x :| xs :: [a]
xs) = (a -> a -> a) -> a -> [a] -> a
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' a -> a -> a
cmp a
x [a]
xs
  where
    cmp :: a -> a -> a
    cmp :: a -> a -> a
cmp a :: a
a b :: a
b = case b -> b -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (a -> b
f a
a) (a -> b
f a
b) of
        LT -> a
b
        EQ -> a
a
        GT -> a
a