content: {-# LANGUAGE OverloadedStrings, ImpredicativeTypes, ConstraintKinds #-}
  import Control.Monad
  import Data.Bool
  import Data.List.Extra
  import Data.Maybe
  import System.Exit
  
  import qualified Data.Map as Map
  
  import Build
  import Build.Store
  import Build.System
  import Build.Task
  import Build.Task.Free()
  
  import Spreadsheet
  import Examples()
  
  -- | A build system that accepts a list of target keys.
  type MultiBuild c i k v = Tasks c k v -> [k] -> Store i k v -> Store i k v
  
  sequentialMultiBuild :: Build Monad i k v -> MultiBuild Monad i k v
  sequentialMultiBuild build task outputs store = case outputs of
      []     -> store
      (k:ks) -> sequentialMultiBuild build task ks (build task k store)
  
  sequentialMultiBuildA :: Build Applicative i k v -> MultiBuild Applicative i k v
  sequentialMultiBuildA build task outputs store = case outputs of
      []     -> store
      (k:ks) -> sequentialMultiBuildA build task ks (build task k store)
  
  inputCells :: [Cell]
  inputCells = [ "A1", "A2", "A3" ]
  
  inputs :: i -> Store i Cell Int
  inputs i = initialise i $ \cell -> fromMaybe 0 $ lookup cell $ zip inputCells [1..]
  
  spreadsheet :: Spreadsheet
  spreadsheet cell = case name cell of
      "B1"  -> Just 1                         --          1
      "B2"  -> Just $ "B1" + 1                -- 1 + 1 == 2
      "B3"  -> Just $ "A3" * abs "B2"         -- 3 * 2 == 6
      "C1"  -> Just $ IfZero "B3" "C2" 1000   --          1000
      "C2"  -> Just $ IfZero "B3" 2000 "C1"   --          1000
      "C3"  -> Just $ Random 1 6              --          1..6
      "F0"  -> Just 0                         --          0
      "F1"  -> Just 1                         --          1
      'F':_ -> Just $ rel (-1) 0 + rel (-2) 0 --          Fn = F(n - 1) + F(n - 2)
      _     -> Nothing
  
  acyclicSpreadsheet :: Spreadsheet
  acyclicSpreadsheet cell = case name cell of
      "B1"  -> Just 1                         --          1
      "B2"  -> Just $ "B1" + 1                -- 1 + 1 == 2
      "B3"  -> Just $ "A3" * abs "B2"         -- 3 * 2 == 6
      "C1"  -> Just $ IfZero "B3" "B2" 1000   --          1000
      "C2"  -> Just $ IfZero "B3" 2000 "C1"   --          1000
      "C3"  -> Just $ Random 1 6              --          1..6
      "F0"  -> Just 0                         --          0
      "F1"  -> Just 1                         --          1
      'F':_ -> Just $ rel (-1) 0 + rel (-2) 0 --          Fn = F(n - 1) + F(n - 2)
      _     -> Nothing
  
  targets :: [Cell]
  targets = [ "A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "F0", "F1", "F4" ]
  
  tasks :: Tasks Monad Cell Int
  tasks = spreadsheetTask spreadsheet
  
  tasksA :: Tasks Applicative Cell Int
  tasksA = spreadsheetTaskA acyclicSpreadsheet
  
  test :: String -> Build Monad i Cell Int -> i -> IO Bool
  test name build i = do
      let store   = inputs i
          result  = sequentialMultiBuild build tasks targets store
          correct = all (correctBuild tasks store result) targets
      -- when False $ putStrLn $ "========\n" ++ show (getInfo result) ++ "\n========"
      putStr $ name ++ " is "
      case (trim name, correct) of
          ("dumb", False) -> do putStr "incorrect, which is [OK]\n"; return True
          (_     , False) -> do putStr "incorrect: [FAIL]\n"       ; return False
          (_     , True ) -> do putStr "correct: [OK]\n"           ; return True
  
  testA :: String -> Build Applicative i Cell Int -> i -> IO Bool
  testA name build i = do
      let store   = inputs i
          result  = sequentialMultiBuildA build tasksA targets store
          correct = all (correctBuild tasks store result) targets
      -- when False $ putStrLn $ "========\n" ++ show (getInfo result) ++ "\n========"
      putStrLn $ name ++ " is " ++ bool "incorrect: [FAIL]" "correct: [OK]" correct
      return correct
  
  testSuite :: IO Bool
  testSuite = and <$> sequence
      [ test  "dumb      " dumb       ()
      , test  "busy      " busy       ()
      , test  "memo      " memo       ()
      , testA "make      " make       (0, Map.empty)
      , testA "ninja     " ninja      mempty
      , testA "cloudBuild" cloudBuild mempty
      , test  "excel     " excel      (const True, mempty)
      , test  "shake     " shake      mempty
      , test  "bazel     " bazel      mempty
      , test  "cloudShake" cloudShake mempty
      , testA "buck      " buck       mempty
      , test  "nix       " nix        mempty ]
  
  main :: IO ()
  main = do
      success <- testSuite
      unless success $ die "\n========== At least one test failed! ==========\n"
  