-- | Folding-related functions and types.
module Language.Drasil.Sentence.Fold (
  -- * English-related Datatypes
  -- | For help working with listing information in English. Allows the below functions
  -- to make different kinds of lists based on the options defined here.
  EnumType(..), WrapType(..), SepType(..), FoldType(..),
  -- * Folding Functions
  -- ** Expression-related
  foldConstraints,
  -- ** Sentence-related
  foldlEnumList, foldlList, foldlSP, foldlSP_, foldlSPCol,
  foldlSent, foldlSent_, foldlSentCol, foldlsC, foldNums, numList
) where

import Language.Drasil.Classes ( Express(express), Quantity )
import Language.Drasil.Constraint
    ( Constraint(Range, Elem), ConstraintE )
import Language.Drasil.Document ( mkParagraph )
import Language.Drasil.Document.Core ( Contents )
import Language.Drasil.Expr.Class ( ExprC(($&&), realInterval) )
import Language.Drasil.Sentence
    ( Sentence(S, E, EmptyS, (:+:)), sParen, (+:+), sC, (+:+.), (+:) )
import qualified Language.Drasil.Sentence.Combinators as S (and_, or_)
import Utils.Drasil
import Data.Foldable (foldl')

-- TODO: This looks like it should be moved to wherever uses it, it's too specific.
-- | Helper for formatting a list of constraints.
foldConstraints :: Quantity c => c -> [ConstraintE] -> Sentence
foldConstraints :: forall c. Quantity c => c -> [ConstraintE] -> Sentence
foldConstraints c
_ [] = Sentence
EmptyS
foldConstraints c
c [ConstraintE]
e  = ModelExpr -> Sentence
E (ModelExpr -> Sentence) -> ModelExpr -> Sentence
forall a b. (a -> b) -> a -> b
$ (ModelExpr -> ModelExpr -> ModelExpr) -> [ModelExpr] -> ModelExpr
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 ModelExpr -> ModelExpr -> ModelExpr
forall r. ExprC r => r -> r -> r
($&&) ([ModelExpr] -> ModelExpr) -> [ModelExpr] -> ModelExpr
forall a b. (a -> b) -> a -> b
$ (ConstraintE -> ModelExpr) -> [ConstraintE] -> [ModelExpr]
forall a b. (a -> b) -> [a] -> [b]
map ConstraintE -> ModelExpr
forall {c}. (Express c, ExprC c) => Constraint c -> ModelExpr
constraintToExpr [ConstraintE]
e
  where
    constraintToExpr :: Constraint c -> ModelExpr
constraintToExpr (Range ConstraintReason
_ RealInterval c c
ri) = c -> ModelExpr
forall c. Express c => c -> ModelExpr
express (c -> ModelExpr) -> c -> ModelExpr
forall a b. (a -> b) -> a -> b
$ c -> RealInterval c c -> c
forall c. HasUID c => c -> RealInterval c c -> c
forall r c. (ExprC r, HasUID c) => c -> RealInterval r r -> r
realInterval c
c RealInterval c c
ri
    constraintToExpr (Elem ConstraintReason
_ c
set) = c -> ModelExpr
forall c. Express c => c -> ModelExpr
express c
set


-- | Partial function application of 'foldle' for sentences specifically.
-- Folds with spaces and adds a period (".") at the end.
foldlSent :: [Sentence] -> Sentence
foldlSent :: [Sentence] -> Sentence
foldlSent = (Sentence -> Sentence -> Sentence)
-> (Sentence -> Sentence -> Sentence)
-> Sentence
-> [Sentence]
-> Sentence
forall a. (a -> a -> a) -> (a -> a -> a) -> a -> [a] -> a
foldle Sentence -> Sentence -> Sentence
(+:+) Sentence -> Sentence -> Sentence
(+:+.) Sentence
EmptyS

-- | 'foldlSent' but does not add a period.
foldlSent_ :: [Sentence] -> Sentence
foldlSent_ :: [Sentence] -> Sentence
foldlSent_ = (Sentence -> Sentence -> Sentence)
-> Sentence -> [Sentence] -> Sentence
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Sentence -> Sentence -> Sentence
(+:+) Sentence
EmptyS

-- | 'foldlSent' but ends with colon.
foldlSentCol :: [Sentence] -> Sentence
foldlSentCol :: [Sentence] -> Sentence
foldlSentCol = (Sentence -> Sentence -> Sentence)
-> (Sentence -> Sentence -> Sentence)
-> Sentence
-> [Sentence]
-> Sentence
forall a. (a -> a -> a) -> (a -> a -> a) -> a -> [a] -> a
foldle Sentence -> Sentence -> Sentence
(+:+) Sentence -> Sentence -> Sentence
(+:) Sentence
EmptyS

-- | Fold sentences then turns into content using 'foldlSent'.
foldlSP :: [Sentence] -> Contents
foldlSP :: [Sentence] -> Contents
foldlSP = Sentence -> Contents
mkParagraph (Sentence -> Contents)
-> ([Sentence] -> Sentence) -> [Sentence] -> Contents
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Sentence] -> Sentence
foldlSent

-- | Same as 'foldlSP' but uses 'foldlSent_'.
foldlSP_ :: [Sentence] -> Contents
foldlSP_ :: [Sentence] -> Contents
foldlSP_ = Sentence -> Contents
mkParagraph (Sentence -> Contents)
-> ([Sentence] -> Sentence) -> [Sentence] -> Contents
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Sentence] -> Sentence
foldlSent_

-- | Same as 'foldlSP' but uses 'foldlSentCol'.
foldlSPCol :: [Sentence] -> Contents
foldlSPCol :: [Sentence] -> Contents
foldlSPCol = Sentence -> Contents
mkParagraph (Sentence -> Contents)
-> ([Sentence] -> Sentence) -> [Sentence] -> Contents
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Sentence] -> Sentence
foldlSentCol

-- | Folds a list of elements separated by commas, including the last element.
foldlsC :: [Sentence] -> Sentence 
foldlsC :: [Sentence] -> Sentence
foldlsC [] = Sentence
EmptyS
foldlsC [Sentence]
xs = (Sentence -> Sentence -> Sentence) -> [Sentence] -> Sentence
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldl1 Sentence -> Sentence -> Sentence
sC [Sentence]
xs

-- | Type that helps determine enumeration method. Can use either numbers, uppercase letters, or lowercase letters.
data EnumType = Numb   | Upper   | Lower

-- | Type to help wrap a sentence with parenthesis or to add a period at the end.
data WrapType = Parens | Period

-- | Type to help separate words with commas or semicolons.
data SepType  = Comma  | SemiCol

-- | Type to help fold differently between listed items, or if there are options (ex. using "and" or "or" at the end of a list of words).
data FoldType = List   | Options

-- | Creates a list of elements separated by a "separator", ending with "and" or "or".
foldlList :: SepType -> FoldType -> [Sentence] -> Sentence
foldlList :: SepType -> FoldType -> [Sentence] -> Sentence
foldlList SepType
_ FoldType
_ []     = Sentence
EmptyS
foldlList SepType
_ FoldType
f [Sentence
a, Sentence
b] = FoldType -> Sentence -> Sentence -> Sentence
end FoldType
f Sentence
a Sentence
b
foldlList SepType
s FoldType
f [Sentence]
lst    = (Sentence -> Sentence -> Sentence)
-> (Sentence -> Sentence -> Sentence) -> [Sentence] -> Sentence
forall a. (a -> a -> a) -> (a -> a -> a) -> [a] -> a
foldle1 (SepType -> Sentence -> Sentence -> Sentence
sep SepType
s) (\Sentence
a Sentence
b -> FoldType -> Sentence -> Sentence -> Sentence
end FoldType
f (SepType -> Sentence -> Sentence -> Sentence
sep SepType
s Sentence
a Sentence
EmptyS) Sentence
b) [Sentence]
lst

-- | Creates a list of elements with "enumerators" in "wrappers" using foldlList.
foldlEnumList :: EnumType -> WrapType -> SepType -> FoldType -> [Sentence] -> Sentence
foldlEnumList :: EnumType
-> WrapType -> SepType -> FoldType -> [Sentence] -> Sentence
foldlEnumList EnumType
e WrapType
w SepType
s FoldType
l [Sentence]
lst = SepType -> FoldType -> [Sentence] -> Sentence
foldlList SepType
s FoldType
l ([Sentence] -> Sentence) -> [Sentence] -> Sentence
forall a b. (a -> b) -> a -> b
$ (Sentence -> Sentence -> Sentence)
-> [Sentence] -> [Sentence] -> [Sentence]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Sentence -> Sentence -> Sentence
(+:+) (EnumType -> WrapType -> Int -> [Sentence]
enumList EnumType
e WrapType
w (Int -> [Sentence]) -> Int -> [Sentence]
forall a b. (a -> b) -> a -> b
$ [Sentence] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Sentence]
lst) [Sentence]
lst
  where
    enumList :: EnumType -> WrapType -> Int -> [Sentence]
enumList EnumType
enum WrapType
wt Int
len = (String -> Sentence) -> [String] -> [Sentence]
forall a b. (a -> b) -> [a] -> [b]
map (WrapType -> Sentence -> Sentence
wrap WrapType
wt (Sentence -> Sentence)
-> (String -> Sentence) -> String -> Sentence
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Sentence
S) (Int -> [String] -> [String]
forall a. Int -> [a] -> [a]
take Int
len (EnumType -> [String]
chList EnumType
enum))
    chList :: EnumType -> [String]
chList EnumType
Numb  = (Integer -> String) -> [Integer] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> String
forall a. Show a => a -> String
show ([Integer
1..] :: [Integer])
    chList EnumType
Upper = (Char -> String) -> String -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Char -> String
forall a. Show a => a -> String
show [Char
'A'..Char
'Z']
    chList EnumType
Lower = (Char -> String) -> String -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Char -> String
forall a. Show a => a -> String
show [Char
'a'..Char
'z']
    wrap :: WrapType -> Sentence -> Sentence
wrap WrapType
Parens Sentence
x = Sentence -> Sentence
sParen Sentence
x
    wrap WrapType
Period Sentence
x = Sentence
x Sentence -> Sentence -> Sentence
:+: String -> Sentence
S String
"."

-- | Ending type helper functions to foldlList - not exported.
end :: FoldType -> (Sentence -> Sentence -> Sentence)
end :: FoldType -> Sentence -> Sentence -> Sentence
end FoldType
List    = Sentence -> Sentence -> Sentence
S.and_
end FoldType
Options = Sentence -> Sentence -> Sentence
S.or_

-- | Separator type helper function to foldlList - not exported.
sep :: SepType -> (Sentence -> Sentence -> Sentence)
sep :: SepType -> Sentence -> Sentence -> Sentence
sep SepType
Comma   = Sentence -> Sentence -> Sentence
sC
sep SepType
SemiCol = \Sentence
a Sentence
b -> Sentence
a Sentence -> Sentence -> Sentence
:+: String -> Sentence
S String
";" Sentence -> Sentence -> Sentence
+:+ Sentence
b

-- | Parses a list of integers into a nice sentence (ie. S "1, 4-7, and 13").
foldNums :: String -> [Int] -> Sentence
foldNums :: String -> [Int] -> Sentence
foldNums String
s [Int]
x = SepType -> FoldType -> [Sentence] -> Sentence
foldlList SepType
Comma FoldType
List ([Sentence] -> Sentence) -> [Sentence] -> Sentence
forall a b. (a -> b) -> a -> b
$ (String -> Sentence) -> [String] -> [Sentence]
forall a b. (a -> b) -> [a] -> [b]
map String -> Sentence
S (String -> [Int] -> [String]
numList String
s [Int]
x)

-- | Parses a list of integers into a list of strings (ie. ["1", "4-7", "13"]).
numList :: String -> [Int] -> [String]
numList :: String -> [Int] -> [String]
numList String
_ []  = String -> [String]
forall a. HasCallStack => String -> a
error String
"Empty list used with foldNums"
numList String
_ [Int
y] = [Int -> String
forall a. Show a => a -> String
show Int
y]
numList String
s [Int
y, Int
z]
  | Int
z Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
y Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 = [Int -> Int -> String -> String
rangeSep Int
y Int
z String
s]
  | Bool
otherwise  = (Int -> String) -> [Int] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Int -> String
forall a. Show a => a -> String
show [Int
y, Int
z]
numList String
s (Int
y:Int
z:[Int]
xs)
  | Int
z Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
y Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 = Int -> Int -> [Int] -> [String]
range Int
y Int
z [Int]
xs
  | Bool
otherwise  = Int -> String
forall a. Show a => a -> String
show Int
y String -> [String] -> [String]
forall a. a -> [a] -> [a]
: String -> [Int] -> [String]
numList String
s (Int
zInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:[Int]
xs)
  where
    range :: Int -> Int -> [Int] -> [String]
range Int
a Int
b []   = [Int -> Int -> String -> String
rangeSep Int
a Int
b String
s]
    range Int
a Int
b [Int
n]
      | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
b Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 = [Int -> Int -> String -> String
rangeSep Int
a Int
n String
s]
      | Bool
otherwise  = [Int -> Int -> String -> String
rangeSep Int
a Int
b String
s, Int -> String
forall a. Show a => a -> String
show Int
n]
    range Int
a Int
b l :: [Int]
l@(Int
n:[Int]
ns)
      | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
b Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 = Int -> Int -> [Int] -> [String]
range Int
a Int
n [Int]
ns
      | Bool
otherwise  = Int -> Int -> String -> String
rangeSep Int
a Int
b String
s String -> [String] -> [String]
forall a. a -> [a] -> [a]
: String -> [Int] -> [String]
numList String
s [Int]
l

-- | Helper for numList that concatenates integers to strings.
rangeSep :: Int -> Int -> String -> String
rangeSep :: Int -> Int -> String -> String
rangeSep Int
p Int
q String
s = Int -> String
forall a. Show a => a -> String
show Int
p String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
q