module GLL.Combinators.Options where

import Data.Function (on)

-- | CombinatorOptions datatype
--      * left_biased_choice: see function leftBiased
--      * pivot_select: provide a filtering function on `pivots'
data PCOptions = PCOptions  { left_biased_choice    :: Bool
                            , pivot_select          :: Maybe (Int -> Int -> Ordering)
                            , pivot_select_nt       :: Bool
                            , throw_errors          :: Bool
                            , do_memo               :: Bool
                            , max_errors            :: Int
                            , nt_select_test        :: Bool
                            , alt_select_test       :: Bool
                            , seq_select_test       :: Bool
                            }

-- | A list of 'CombinatorOption's for evaluating combinator expressions.
type CombinatorOptions    = [CombinatorOption]

-- | A single option.
type CombinatorOption     = PCOptions -> PCOptions

runOptions :: CombinatorOptions -> PCOptions
runOptions = runOptionsOn defaultOptions

runOptionsOn :: PCOptions -> CombinatorOptions -> PCOptions
runOptionsOn = foldr ($)

-- | The default options: no disambiguation.
defaultOptions :: PCOptions
defaultOptions = PCOptions False Nothing False False False 3 True True True

-- | Enables a 'longest-match' at production level.
maximumPivot :: CombinatorOption
maximumPivot opts = opts {pivot_select = Just compare}

-- | Enables a 'shortest-match' at production level.
minimumPivot :: CombinatorOption
minimumPivot opts = opts {pivot_select = Just (flip compare)}

-- | Discards a pivot select option (internal use only)
anyPivot :: CombinatorOption
anyPivot opts = opts {pivot_select = Nothing}

-- | Enables 'longest-match' at non-terminal level. 
maximumPivotAtNt :: CombinatorOption
maximumPivotAtNt opts = opts {pivot_select_nt = True, pivot_select = Just compare}

-- | 
-- Set the maximum number of errors shown in case of an unsuccessful parse.
maximumErrors :: Int -> CombinatorOption
maximumErrors n opts = opts { max_errors = n }

-- | 
-- If there are no parse results, the default behaviour is to return an empty list.
-- If this option is used, a runtime error will be reported, with debugging information.
throwErrors :: CombinatorOption
throwErrors opts = opts{throw_errors = True}


-- | 
-- Turns all occurrences of '<||>' into a 'left biased' variant:
--  only return results of the second alternate if the first alternate
-- does not have any results.
leftBiased :: CombinatorOption
leftBiased opts = opts { left_biased_choice = True }

-- | 
-- Whether to use unsafe memoisation to speed up the enumeration of parse results.
useMemoisation :: CombinatorOption
useMemoisation opts = opts { do_memo = True }

-- | Filter a list such that the only remaining elements are equal to
-- the maximum element, given an ordering operator.
maximumsWith :: (a -> a -> Ordering) -> [a] -> [a]
maximumsWith compare xs =
    case xs of
    []      -> []
    [x]     -> [x]
    x:xs    -> maxx xs x []
 where  maxx []     x acc = x : acc
        maxx (y:ys) x acc = case y `compare` x of
                            LT -> maxx ys x acc
                            GT -> maxx ys y []
                            EQ -> maxx ys y (x:acc)

-- assumes every sub-list contains only maximums already
maintainWith :: (Eq k) => (k -> k -> Ordering) -> [[(k,a)]] -> [[(k,a)]]
maintainWith compare =
    maintain .
    filter (not . null)
 where  maintain xss =
            let (max,_):_ = maximumsWith (compare `on` fst) $ map head xss
             in (filter ((== max) . fst . head) xss)

-- | 
-- Enables select tests at all levels: nonterminal, alternative and slot.
doSelectTest :: CombinatorOption
doSelectTest opts = opts { nt_select_test = True, alt_select_test = True
                         , seq_select_test = True }

-- | 
-- Disables select tests at all levels: nonterminal, alternative and slot.
noSelectTest :: CombinatorOption
noSelectTest opts = opts { nt_select_test = False, alt_select_test = False
                         , seq_select_test = False }

-- | 
-- Enables select tests at the level of alternatives
doAltSelectTest :: CombinatorOption
doAltSelectTest opts = opts { alt_select_test = True }

-- | 
-- Disables select tests at the level of alternatives
noAltSelectTest :: CombinatorOption
noAltSelectTest opts = opts { alt_select_test = False }

-- | 
-- Enables select tests at the level of nonterminals
doNtSelectTest :: CombinatorOption
doNtSelectTest opts = opts { nt_select_test = True }

-- | 
-- Disables select tests at the level of nonterminals
noNtSelectTest :: CombinatorOption
noNtSelectTest opts = opts { nt_select_test = False }

-- | 
-- Enables select tests at the level of grammar slots
doSlotSelectTest :: CombinatorOption
doSlotSelectTest opts = opts { seq_select_test = True }

-- | 
-- Disables select tests at the level of grammar slots
noSlotSelectTest :: CombinatorOption
noSlotSelectTest opts = opts { seq_select_test = False }