{-# LANGUAGE DeriveFunctor #-}

module Otter.SearchRes
  ( SearchRes
  , Res(..)
  , Extraction(..)
  , FailureReason(..)
  , extractResults
  , delay
  , cons
  ) where

import Data.List.NonEmpty (NonEmpty(..))

data Res a = Res a | Delay deriving (Functor)

-- | Type representing the result of proof search. Every element in the list
-- corresponds to the result of analysing node in the search space by the search
-- algorithm. The result is 'Res' in the positive case the node is a goal node,
-- or 'Delay' in the negative case.
--
-- The search agorithm produces an element of `'SearchRes' a` lazily, inserting
-- 'Delay' constructors to ensure that the computation is productive even in the
-- case no goal node is found in the search space. This allows to perform proof
-- search in an on-demand fashion, possibly giving up after a certain number of
-- 'Delay's.
type SearchRes a = [Res a]

-- | Delays a search result stream.
--
-- @
-- delay x == Delay : x
-- @
delay :: SearchRes a -> SearchRes a
delay = (Delay :)

-- | Appends a result to a search result stream.
--
-- @
-- cons x y = Res x : y
-- @
cons :: a -> SearchRes a -> SearchRes a
cons x = (Res x :)

-- | Type of reasons why no result can be extracted from a search result stream.
-- 
-- Either the search space has been exhaustively searched and no result was
-- found (the query is not a theorem), or the search was terminated preemptively
-- according to an upper bound imposed on the maximum depth of the search space.
data FailureReason = NotATheorem | SpaceTooBig

-- | Type of the result of 'extractResults', which extracts all positive search
-- results from a search result stream.
-- An element of `Extraction a` is either a non-empty list of positive results,
-- or an element of 'SpaceExhausted' giving a reason why no result was found.
data Extraction a = AllResults (NonEmpty a) | NoResults FailureReason

resListUntil :: Int -> SearchRes a -> ([a], FailureReason)
resListUntil _ [] = ([], NotATheorem)
resListUntil 0 (_ : _)  = ([], SpaceTooBig)
resListUntil i (Res x : xs) = let (ys, b) = resListUntil i xs in (x : ys, b)
resListUntil i (Delay : xs) = resListUntil (i - 1) xs

-- | Extract all the positive results from a search result stream, stopping if
-- no result is found after the given number of delays.
extractResults :: Int -> SearchRes a -> Extraction a
extractResults i res = case resListUntil i res of
  ([], b) -> NoResults b
  (x : xs, _) -> AllResults (x :| xs)