{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiWayIf #-}

module Test.Sandwich.Formatters.TerminalUI.Draw.ColorProgressBar (
  bottomProgressBarColored
  ) where

import Brick
import Data.Foldable
import Data.Ord (comparing)
import Data.String.Interpolate
import GHC.Stack
import Lens.Micro
import Lens.Micro.TH
import Test.Sandwich.Formatters.TerminalUI.AttrMap
import Test.Sandwich.Formatters.TerminalUI.Types
import Test.Sandwich.RunTree
import Test.Sandwich.Types.RunTree
import Test.Sandwich.Types.Spec

type Chunk a = [(Rational, a)]

data ChunkSum = ChunkSum { _running :: Rational
                         , _notStarted :: Rational
                         , _pending :: Rational
                         , _success :: Rational
                         , _failure :: Rational }

zeroChunkSum :: ChunkSum
zeroChunkSum = ChunkSum 0 0 0 0 0

makeLenses ''ChunkSum

splitIntoChunks :: forall a. (Show a) => Rational -> [(Rational, a)] -> [[(Rational, a)]]
splitIntoChunks _ [] = []
splitIntoChunks chunkSize remaining = chunk : (splitIntoChunks chunkSize remaining')
  where
    (chunk, remaining') = go [] chunkSize remaining

    go :: Chunk a -> Rational -> [(Rational, a)] -> (Chunk a, [(Rational, a)])
    go chunkSoFar needed ((amount, val):xs) =
      if | amount == needed -> (chunkSoFar <> [(amount, val)], xs)
         | amount < needed -> go (chunkSoFar <> [(amount, val)]) (needed - amount) xs
         | amount > needed -> (chunkSoFar <> [(needed, val)], (amount - needed, val):xs)
         | otherwise -> error "impossible"
    go chunkSoFar needed [] = error [i|Bottomed out in go: #{chunkSoFar}, #{needed}|]

-- TODO: improve this to use block chars
getCharForChunk :: [(Rational, Status)] -> Widget n
getCharForChunk chunk = withAttr attrToUse (str full_five_eighth_height)
  where ChunkSum {..} = sumChunk chunk
        (_, attrToUse) = maxBy fst [(_running, runningAttr)
                                   , (_notStarted, notStartedAttr)
                                   , (_pending, pendingAttr)
                                   , (_success, successAttr)
                                   , (_failure, failureAttr)
                                   ]

sumChunk :: Chunk Status -> ChunkSum
sumChunk = foldl combine zeroChunkSum
  where combine chunkSum (amount, status) = chunkSum & (lensForStatus status) %~ (+ amount)

        lensForStatus NotStarted = notStarted
        lensForStatus (Running {}) = running
        lensForStatus (Done {statusResult=Success}) = success
        lensForStatus (Done {statusResult=(Failure (Pending {}))}) = pending
        lensForStatus (Done {statusResult=(Failure _)}) = failure
        lensForStatus (Done {statusResult=DryRun}) = notStarted
        lensForStatus (Done {statusResult=Cancelled}) = failure

maxBy :: (Foldable t, Ord a) => (b -> a) -> t b -> b
maxBy = maximumBy . comparing

-- * Block elems

-- full = "█"
-- seven_eighth = "▉"
-- six_eighth = "▊"
-- five_eighth = "▋"
-- four_eighth = "▌"
-- three_eighth = "▍"
-- two_eighth = "▎"
-- one_eighth = "▏"

full_five_eighth_height = "▆"

-- * Exports

bottomProgressBarColored app = Widget Greedy Fixed $ do
  c <- getContext
  render $ bottomProgressBarColoredWidth app (c ^. availWidthL)

bottomProgressBarColoredWidth app width = hBox [getCharForChunk chunk | chunk <- chunks]
  where
    statuses = concatMap getStatuses (app ^. appRunTree)
    statusesWithAmounts = [(testsPerChar, x) | x <- statuses]

    chunks = splitIntoChunks 1 statusesWithAmounts

    testsPerChar :: Rational = fromIntegral width / fromIntegral (length statuses)

    getStatuses :: (HasCallStack) => RunNodeWithStatus context a l t -> [a]
    getStatuses = extractValues (runTreeStatus . runNodeCommon)
