{-# LANGUAGE FunctionalDependencies     #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude          #-}

{- |

Functions for constructing 'Expression's that match the 'Show' implementations.

This module is only designed for testing and REPL use. It isn't re-exported into
the main Bricks API because it's a bit messy:

  - There are a lot of terse function names here that would clash with other
    things easily.
  - Some functions are partial, such as those that require strings that can be
    rendered unquoted.
  - It uses string overloading in a way that the regular API probably shouldn't.
  - The functions are oriented toward constructing 'Expression's, skipping over
    the intermediary types they're composed of, which is convenient but may make
    them insufficient for some use cases.

-}
module Bricks.Expression.Construction
  (

  -- * Lambdas
    lambda

  -- * Function application
  , apply

  -- * Variables
  , var

  -- * Dot
  , dot

  -- * List
  , list

  -- * Let
  , let'in
  , let'eq
  , let'inherit'from

  -- * Dict
  , dict
  , rec'dict
  , dict'eq
  , dict'inherit'from
  , dict'inherit

  -- * Dynamic strings
  , str
  , antiquote
  , Str'1'IsString (..)

  -- * Indented strings
  , str'indented
  , indent

  -- * Param builder
  , Param'Builder (..)
  , param
  , pattern
  , dict'param
  , def
  , ellipsis

  -- * Re-exports
  , Expression
  , (<>)
  , (&)
  , Maybe (Just, Nothing)

  ) where

-- Bricks
import Bricks.Expression
import Bricks.UnquotedString

-- Bricks internal
import           Bricks.Internal.Prelude
import qualified Bricks.Internal.Seq     as Seq
import           Bricks.Internal.Text    (Text)
import qualified Bricks.Internal.Text    as Text

-- Base
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.String        (IsString (fromString))


--------------------------------------------------------------------------------
--  Lambdas
--------------------------------------------------------------------------------

lambda :: Param'Builder -> Expression -> Expression
lambda a b =
  Expr'Lambda $ Lambda (buildParam a) b Nothing


--------------------------------------------------------------------------------
--  Function application
--------------------------------------------------------------------------------

apply :: Expression -> Expression -> Expression
apply a b =
  Expr'Apply $ Apply a b Nothing


--------------------------------------------------------------------------------
--  Variables
--------------------------------------------------------------------------------

var :: Text -> Expression
var x =
  Expr'Var $ Var (unquotedString'orThrow x) Nothing


--------------------------------------------------------------------------------
--  Dots
--------------------------------------------------------------------------------

dot :: Expression -> Expression -> Expression
dot a b =
  Expr'Dot $ Dot a b Nothing


--------------------------------------------------------------------------------
--  List
--------------------------------------------------------------------------------

list :: [Expression] -> Expression
list x =
  Expr'List $ List (Seq.fromList x) Nothing


--------------------------------------------------------------------------------
--  Let
--------------------------------------------------------------------------------

let'in :: [LetBinding] -> Expression -> Expression
let'in a b =
  Expr'Let $ Let (Seq.fromList a) b Nothing

let'eq :: Text -> Expression -> LetBinding
let'eq a b =
  LetBinding'Eq (Var (unquotedString'orThrow a) Nothing) b

let'inherit'from :: Expression -> [Text] -> LetBinding
let'inherit'from a b =
  LetBinding'Inherit
    a
    (Seq.fromList $ fmap (\x -> Var (unquotedString'orThrow x) Nothing) b)


--------------------------------------------------------------------------------
--  Dicts
--------------------------------------------------------------------------------

dict :: [DictBinding] -> Expression
dict x =
  Expr'Dict $ Dict False (Seq.fromList x) Nothing

rec'dict :: [DictBinding] -> Expression
rec'dict x =
  Expr'Dict $ Dict False (Seq.fromList x) Nothing

dict'eq :: Expression -> Expression -> DictBinding
dict'eq =
  DictBinding'Eq

dict'inherit'from :: Expression -> [Text] -> DictBinding
dict'inherit'from a b =
  DictBinding'Inherit'Dict
    a
    (Seq.fromList (fmap (\x -> Str'Static x Nothing) b))

dict'inherit :: [Text] -> DictBinding
dict'inherit a =
  DictBinding'Inherit'Var
    (Seq.fromList $ fmap (\x -> Var (unquotedString'orThrow x) Nothing) a)


--------------------------------------------------------------------------------
--  Dynamic strings
--------------------------------------------------------------------------------

str :: [Str'1'IsString] -> Expression
str xs =
  Expr'Str $ Str'Dynamic (Seq.fromList (fmap unStr'1'IsString xs)) Nothing

antiquote :: Expression -> Str'1'IsString
antiquote =
  Str'1'IsString . Str'1'Antiquote

{- | A newtype for 'Str'1' just so we can give it the 'IsString' instance which
would be dubiously appropriate for the actual 'Str'1' type. -}

newtype Str'1'IsString = Str'1'IsString { unStr'1'IsString :: Str'1 }

instance IsString Str'1'IsString
  where
    fromString x =
      Str'1'IsString . Str'1'Literal $ Str'Static (Text.pack x) Nothing


--------------------------------------------------------------------------------
--  Indented strings
--------------------------------------------------------------------------------

str'indented :: [InStr'1] -> Expression
str'indented xs =
  Expr'Str'Indented $ InStr (Seq.fromList xs) Nothing

indent :: Natural -> [Str'1'IsString] -> Maybe Text -> InStr'1
indent n xs lbr =
  InStr'1
    n
    Nothing
    (Seq.fromList (fmap unStr'1'IsString xs))
    (fmap (\x -> Str'Static x Nothing) lbr)


--------------------------------------------------------------------------------
--  Param builder
--------------------------------------------------------------------------------

newtype Param'Builder = Param'Builder (NonEmpty Param)
  deriving Semigroup

paramBuilder :: Param -> Param'Builder
paramBuilder x =
  Param'Builder (x :| [])

param :: Text -> Param'Builder
param x =
  paramBuilder . Param'Name $ Var (unquotedString'orThrow x) Nothing

buildParam :: Param'Builder -> Param
buildParam (Param'Builder xs) =
  foldr1 mergeParams xs

pattern :: [DictPattern'1] -> Param'Builder
pattern xs =
  paramBuilder $ Param'DictPattern $ DictPattern (Seq.fromList xs) False

dict'param :: Text -> DictPattern'1
dict'param x =
  Bricks.Expression.DictPattern'1
    (Var (unquotedString'orThrow x) Nothing)
    Nothing

def :: Expression -> DictPattern'1 -> DictPattern'1
def b (DictPattern'1 a _) =
  DictPattern'1 a (Just b)

ellipsis :: Param'Builder
ellipsis =
  paramBuilder $ Param'DictPattern $ DictPattern Seq.empty True

{- | Combine two params, merging dict patterns with 'mergeDictPatterns' and
preferring the right-hand-side when names conflict. -}

mergeParams :: Param -> Param -> Param
mergeParams = (+)
  where
    (+) :: Param -> Param -> Param
    -- A name on the right overrides a name on the left
    Param'Both _n1 p1 + Param'Name n2 = Param'Both n2 p1
    -- The simplest combinations: turning one or the other into both
    Param'Name n + Param'DictPattern p = Param'Both n p
    Param'DictPattern p + Param'Name n = Param'Both n p
    -- Otherwise a name on the left gets overridden by anything on the right
    Param'Name _n + x = x
    -- Combinations that require merging the dict patterns
    Param'DictPattern p1 + Param'DictPattern p2 =
      Param'DictPattern (mergeDictPatterns p1 p2)
    Param'DictPattern p1 + Param'Both n p2 =
      Param'Both n (mergeDictPatterns p1 p2)
    Param'Both _n1 p1 + Param'Both n2 p2 =
      Param'Both n2 (mergeDictPatterns p1 p2)
    Param'Both n p1 + Param'DictPattern p2 =
      Param'Both n (mergeDictPatterns p1 p2)

{- | Combine two dict patterns, taking the concatenation of the item list, and
the Boolean /or/ of the ellipsis flag. -}

mergeDictPatterns :: DictPattern -> DictPattern -> DictPattern
mergeDictPatterns = (+)
  where
    DictPattern xs1 e1 + DictPattern xs2 e2 =
      DictPattern (xs1 <> xs2) (e1 || e2)