{-# language PackageImports #-}

module System.ProgressBar
    ( -- * Progress bars
      ProgressBar
    , progressBar
    , autoProgressBar
    , hProgressBar
    , mkProgressBar
      -- * Progress state
    , Progress(..)
      -- * Labels
    , Label
    , noLabel
    , msg
    , percentage
    , exact
      -- * Auto printing
    , ProgressRef
    , startProgress
    , incProgress
    ) where

import "async" Control.Concurrent.Async ( Async )
import "base" System.IO ( Handle, stderr )
import           "this" System.ProgressBar.State ( Progress(..) )
import qualified "this" System.ProgressBar.State as State

-- | Type of functions producing a progress bar.
type ProgressBar a
   = Label -- ^ Prefixed label.
  -> Label -- ^ Postfixed label.
  -> Integer
     -- ^ Total progress bar width in characters. Either used as given
     -- or as a default when the width of the terminal can not be
     -- determined.
     --
     -- See 'autoProgressBar'.
  -> Progress -- ^ Current progress.
  -> a

-- | Print a progress bar to 'stderr'
--
-- See 'hProgressBar'.
progressBar :: ProgressBar (IO ())
progressBar = hProgressBar stderr

-- | Print a progress bar to 'stderr' which takes up all available space.
--
-- The given width will be used if the width of the terminal can not
-- be determined.
--
-- See 'hProgressBar'.
autoProgressBar :: ProgressBar (IO ())
autoProgressBar = State.autoProgressBar

-- | Print a progress bar to a file handle.
--
-- Erases the current line! (by outputting '\r') Does not print a
-- newline '\n'. Subsequent invocations will overwrite the previous
-- output.
hProgressBar :: Handle -> ProgressBar (IO ())
hProgressBar = State.hProgressBar

-- | Renders a progress bar
--
-- >>> mkProgressBar (msg "Working") percentage 40 30 100
-- "Working [=======>.................]  30%"
mkProgressBar :: ProgressBar String
mkProgressBar = State.mkProgressBar

-- | A label that can be pre- or postfixed to a progress bar.
type Label
   = Progress -- ^ Current progress.
  -> String -- ^ Resulting label.

-- | The empty label.
--
-- >>> noLabel 30 100
-- ""
noLabel :: Label
noLabel = State.noLabel

-- | A label consisting of a static string.
--
-- >>> msg "foo" 30 100
-- "foo"
msg :: String -> Label
msg = State.msg

-- | A label which displays the progress as a percentage.
--
-- Constant width property:
-- ∀ d t : ℕ. d ≤ t → length (percentage d t) ≡ 4
--
-- >>> percentage 30 100
-- " 30%"
--
-- __Note__: if no work is to be done (todo == 0) the percentage will
-- always be 100%.

-- ∀ d t : ℕ. d ≤ t -> length (percentage d t) ≡ 3
percentage :: Label
percentage = State.percentage

-- | A label which displays the progress as a fraction of the total
-- amount of work.
--
-- Equal width property:
-- ∀ d₁ d₂ t : ℕ. d₁ ≤ d₂ ≤ t → length (exact d₁ t) ≡ length (exact d₂ t)
--
-- >>> exact 30 100
-- " 30/100"

-- ∀ d₁ d₂ t : ℕ. d₁ ≤ d₂ ≤ t -> length (exact d₁ t) ≡ length (exact d₂ t)
exact :: Label
exact = State.exact

-- * Auto-Printing Progress

type ProgressRef = State.ProgressRef Progress

-- | Start a thread to automatically display progress. Use incProgress to step
-- the progress bar.
startProgress
    :: Label -- ^ Prefixed label.
    -> Label -- ^ Postfixed label.
    -> Integer
       -- ^ Total progress bar width in characters. Only used if the
       -- width can not be automatically determined.
    -> Progress -- ^ Initial progress state.
    -> IO (ProgressRef, Async ())
startProgress = State.startProgress

-- | Increment the progress bar. Negative values will reverse the progress.
-- Progress will never be negative and will silently stop taking data
-- when it completes.
incProgress :: ProgressRef -> Integer -> IO ()
incProgress pr amount =
    State.incProgress pr
      (\st -> st { progressDone = progressDone st + amount })