{-# language DeriveAnyClass #-}
{-# language DeriveFoldable #-}
{-# language DeriveFunctor #-}
{-# language DeriveGeneric #-}
{-# language DeriveTraversable #-}
{-# language OverloadedStrings #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneDeriving #-}
{-# language TemplateHaskell #-}
module Language.Elm.Expression where

import Bound
import Bound.Var (unvar)
import Control.Monad
import Data.Bifoldable
import Data.Bifunctor
import Data.Eq.Deriving
import Data.Ord.Deriving
import Data.String
import Data.Text (Text)
import Text.Show.Deriving

import qualified Language.Elm.Name as Name
import Language.Elm.Pattern (Pattern)
import qualified Language.Elm.Pattern as Pattern

data Expression v
  = Var v
  | Global Name.Qualified
  | App (Expression v) (Expression v)
  | Let (Expression v) (Scope () Expression v)
  | Lam (Scope () Expression v)
  | Record [(Name.Field, Expression v)]
  | Proj Name.Field
  | Case (Expression v) [(Pattern Int, Scope Int Expression v)]
  | List [Expression v]
  | String !Text
  | Int !Integer
  | Float !Double
  deriving (Functor, Foldable, Traversable)

instance Applicative Expression where
  pure = Var
  (<*>) = ap

instance Monad Expression where
  (>>=) =
    flip $ bind Global

bind :: forall v v'. (Name.Qualified -> Expression v') -> (v -> Expression v') -> Expression v -> Expression v'
bind global var expression =
  case expression of
    Var v ->
      var v

    Global g ->
      global g

    App t1 t2 ->
      App (bind global var t1) (bind global var t2)

    Let e s ->
      Let (bind global var e) (bindScope s)

    Lam s ->
      Lam (bindScope s)

    Record fields ->
      Record $ second (bind global var) <$> fields

    Proj fname ->
      Proj fname

    Case scrutinee branches ->
      Case
        (bind global var scrutinee)
        (second bindScope <$> branches)

    List es ->
      List $ bind global var <$> es

    String s ->
      String s

    Int i ->
      Int i

    Float f ->
      Float f
  where
    bindScope :: Scope b Expression v -> Scope b Expression v'
    bindScope =
      toScope .
      bind (fmap F . global) (unvar (pure . B) (fmap F . var)) .
      fromScope

deriving instance Eq v => Eq (Expression v)
deriving instance Ord v => Ord (Expression v)
deriving instance Show v => Show (Expression v)

deriveEq1 ''Expression
deriveOrd1 ''Expression
deriveShow1 ''Expression

instance IsString (Expression v) where
  fromString = Global . fromString

apps :: Foldable f => Expression v -> f (Expression v) -> Expression v
apps = foldl App

appsView :: Expression v -> (Expression v, [Expression v])
appsView = go mempty
  where
    go args expr =
      case expr of
        App e1 e2 ->
          go (e2 : args) e1

        _ ->
          (expr, args)

if_ :: Expression v -> Expression v -> Expression v -> Expression v
if_ bool_ true false =
  Case bool_
    [ (Pattern.Con "Basics.True" [], Scope $ pure $ pure true)
    , (Pattern.Con "Basics.False" [], Scope $ pure $ pure false)
    ]

(|>) :: Expression v -> Expression v -> Expression v
(|>) e1 e2 = apps "Basics.|>" [e1, e2]

(<|) :: Expression v -> Expression v -> Expression v
(<|) e1 e2 = apps "Basics.<|" [e1, e2]

(<<) :: Expression v -> Expression v -> Expression v
(<<) e1 e2 = apps "Basics.<<" [e1, e2]

(>>) :: Expression v -> Expression v -> Expression v
(>>) e1 e2 = apps "Basics.>>" [e1, e2]

(++) :: Expression v -> Expression v -> Expression v
(++) e1 e2 = apps "Basics.++" [e1, e2]

tuple :: Expression v -> Expression v -> Expression v
tuple e1 e2 = apps "Basics.," [e1, e2]

lets :: Eq b => [(b, Expression v)] -> Scope b Expression v -> Expression v
lets =
  go (error "Language.Elm.Expression.lets unbound var") id
  where
    go :: Eq b => (b -> v') -> (v -> v') -> [(b, Expression v)] -> Scope b Expression v -> Expression v'
    go boundVar freeVar bindings scope =
      case bindings of
        [] ->
          unvar boundVar freeVar <$> fromScope scope

        (v, e):bindings' ->
          Let (freeVar <$> e) $
            toScope $
            go
              (\b -> if b == v then B () else F $ boundVar b)
              (F . freeVar)
              bindings'
              scope

foldMapGlobals
  :: Monoid m
  => (Name.Qualified -> m)
  -> Expression v
  -> m
foldMapGlobals f expr =
  case expr of
    Var _ ->
      mempty

    Global qname ->
      f qname

    App e1 e2 ->
      foldMapGlobals f e1 <> foldMapGlobals f e2

    Let e s ->
      foldMapGlobals f e <> foldMapGlobals f (Bound.fromScope s)

    Lam s ->
      foldMapGlobals f (Bound.fromScope s)

    Record fields ->
      foldMap (foldMap (foldMapGlobals f)) fields

    Proj _ ->
      mempty

    Case e branches ->
      foldMapGlobals f e <>
      foldMap
        (bifoldMap (Pattern.foldMapGlobals f) (foldMapGlobals f .Bound.fromScope))
        branches

    List es ->
      foldMap (foldMapGlobals f) es

    String _ ->
      mempty

    Int _ ->
      mempty

    Float _ ->
      mempty