{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Symantic.Parser.Grammar.Production where

import Control.Monad (Monad(..))
--import Data.Functor.Product (Product(..))
import Prelude (Num(..), undefined, error)
import Text.Show (Show(..), showString)
import Type.Reflection (Typeable)
import qualified Control.Applicative as App
import qualified Data.Either as Either
import qualified Data.Eq as Eq
import qualified Data.Function as Fun
import qualified Data.Maybe as Maybe
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
import qualified Language.Haskell.TH.Show as TH
import Data.Kind (Type)

import Symantic.Syntaxes.Classes
import Symantic.Semantics.Data
import Symantic.Semantics.Identity
import Symantic.Syntaxes.Derive

data Production (vs :: [Type]) a where
  -- TODO: move SomeData from Prod to here?
  ProdE :: Prod a -> Production vs a
  --ProdV :: Production (a ': vs) a
  --ProdN :: Production vs (a->b) -> Production (a ': vs) b
  --ProdW :: Production vs b -> Production (a ': vs) b

data Prod a = Prod
  { prodI :: SomeData Identity a
  , prodQ :: SomeData TH.CodeQ a
  }

appProd ::
  Production vs (a -> b) ->
  Production vs a ->
  Production vs b
--ProdE p `appProd` ProdN e = ProdN (ProdE ((.) .@ p) `appProd` e)
--ProdE p `appProd` ProdV = ProdN (ProdE p)
--ProdE p `appProd` ProdW e = ProdW (ProdE p `appProd` e)
ProdE p1 `appProd` ProdE p2 = ProdE (p1 .@ p2)
--ProdN e `appProd` ProdE p = ProdN (ProdE (flip .@ flip .@ p) `appProd` e)
--ProdN e `appProd` ProdV = ProdN (ProdE ap `appProd` e `appProd` ProdE id)
--ProdN e1 `appProd` ProdN e2 = ProdN (ProdE ap `appProd` e1 `appProd` e2)
--ProdN e1 `appProd` ProdW e2 = ProdN (ProdE flip `appProd` e1 `appProd` e2)
--ProdV `appProd` ProdE p = ProdN (ProdE (flip .@ id .@ p))
--ProdV `appProd` ProdN e = ProdN (ProdE (ap .@ id) `appProd` e)
--ProdV `appProd` ProdW e = ProdN (ProdE (flip .@ id) `appProd` e)
--ProdW e `appProd` ProdE p = ProdW (e `appProd` ProdE p)
--ProdW e `appProd` ProdV = ProdN e
--ProdW e1 `appProd` ProdN e2 = ProdN (ProdE (.) `appProd` e1 `appProd` e2)
--ProdW e1 `appProd` ProdW e2 = ProdW (e1 `appProd` e2)

-- data Production a where
--   Prod :: { prodI :: SomeData Identity a
--           , prodQ :: SomeData TH.CodeQ a
--           } -> Production a
--   ProdV :: Production a
--   ProdN :: Production a -> Production a
--   ProdW :: Production a -> Production a

{-# INLINE prodValue #-}
prodValue :: Production '[] a -> SomeData Identity a
prodValue (ProdE (Prod v _)) = v
{-# INLINE prodCode #-}
prodCode :: Production '[] a -> SomeData TH.CodeQ a
prodCode (ProdE (Prod _ c)) = c

{-# INLINE production #-}
production :: a -> TH.CodeQ a -> Production vs a
production v c = ProdE (Prod
  (SomeData (Var (Identity v)))
  (SomeData (Var c)))

{-# INLINE prod #-}
prod :: TH.Lift a => a -> Production vs a
prod x = production x [||x||]

{-# INLINE runValue #-}
runValue :: Production '[] a -> a
runValue (ProdE (Prod v _c)) = runIdentity (derive v)
{-# INLINE runCode #-}
runCode :: Production '[] a -> TH.CodeQ a
runCode (ProdE (Prod _v c)) = derive c

-- Missing instances in 'Language.Haskell.TH',
-- needed for 'prodCon'.
deriving instance TH.Lift TH.OccName
deriving instance TH.Lift TH.NameFlavour
deriving instance TH.Lift TH.ModName
deriving instance TH.Lift TH.PkgName
deriving instance TH.Lift TH.NameSpace
deriving instance TH.Lift TH.Name

-- | @$(prodCon 'SomeConstructor)@ generates the 'Production' for @SomeConstructor@.
prodCon :: TH.Name -> TH.Q TH.Exp
prodCon name = do
  info <- TH.reify name
  case info of
    TH.DataConI n _ty _pn ->
      [| production $(return (TH.ConE n))
           (TH.unsafeCodeCoerce (return (TH.ConE $(TH.lift n)))) |]
    _ -> error "[BUG]: impossible prodCon case"

instance Show (SomeData TH.CodeQ a) where
  -- The 'Derivable' constraint contained in 'SomeData'
  -- is 'TH.CodeQ', hence 'Symantic.View' cannot be used here.
  -- Fortunately 'TH.showCode' can be implemented.
  showsPrec p = showString Fun.. TH.showCode p Fun.. derive

unProdE :: Production '[] a -> Prod a
unProdE t = case t of ProdE t' -> t'

instance Abstractable (Production '[]) where
  lam f = ProdE (lam (unProdE Fun.. f Fun.. ProdE))
instance Abstractable1 (Production '[]) where
  lam1 f = ProdE (lam1 (unProdE Fun.. f Fun.. ProdE))
instance Abstractable Prod where
  -- Those 'undefined' are not unreachables by 'f'
  -- but this is the cost to pay for defining this instance.
  -- In particular, 'f' must not define the 'TH.CodeQ' part
  -- using the 'Identity' part.
  lam f = Prod
    (lam (\x -> let Prod fx _ = f (Prod x undefined) in fx))
    (lam (\y -> let Prod _ fy = f (Prod undefined y) in fy))
instance Abstractable1 Prod where
  lam1 f = Prod
    (lam1 (\x -> let Prod fx _ = f (Prod x undefined) in fx))
    (lam1 (\y -> let Prod _ fy = f (Prod undefined y) in fy))
instance Instantiable (Production vs) where
  (.@) = appProd
instance Instantiable Prod where
  Prod f1 f2 .@ Prod x1 x2 = Prod (f1 .@ x1) (f2 .@ x2)
instance Varable Prod where
  var = Fun.id
instance Unabstractable (Production vs) where
  ap = ProdE ap
  const = ProdE const
  id = ProdE id
  (.) = ProdE (.)
  flip = ProdE flip
  ($) = ProdE ($)
instance Unabstractable Prod where
  ap = Prod ap ap
  const = Prod const const
  id = Prod id id
  flip = Prod flip flip
  (.) = Prod (.) (.)
  ($) = Prod ($) ($)
instance (Num (SomeData Identity a), Num (SomeData TH.CodeQ a)) => Num (Prod a) where
  Prod x1 x2 + Prod y1 y2 = Prod (x1 + y1) (x2 + y2)
  Prod x1 x2 * Prod y1 y2 = Prod (x1 * y1) (x2 * y2)
  Prod x1 x2 - Prod y1 y2 = Prod (x1 - y1) (x2 - y2)
  abs (Prod x1 x2) = Prod (abs x1) (abs x2)
  fromInteger i = Prod (fromInteger i) (fromInteger i)
  negate (Prod x1 x2) = Prod (negate x1) (negate x2)
  signum (Prod x1 x2) = Prod (signum x1) (signum x2)
instance Eitherable (Production vs) where
  either = ProdE either
  left = ProdE left
  right = ProdE right
instance Eitherable Prod where
  either = Prod either either
  left = Prod left left
  right = Prod right right
instance (TH.Lift c, Typeable c) => Constantable c (Production vs) where
  constant c = ProdE (constant c)
instance (TH.Lift c, Typeable c) => Constantable c Prod where
  constant c = Prod (constant c) (constant c)
instance (Inferable a (SomeData Identity), Inferable a (SomeData TH.CodeQ)) => Inferable a (Production vs) where
  infer = ProdE infer
instance (Inferable a (SomeData Identity), Inferable a (SomeData TH.CodeQ)) => Inferable a Prod where
  infer = Prod infer infer
instance Maybeable (Production vs) where
  nothing = ProdE nothing
  just = ProdE just
instance Maybeable Prod where
  nothing = Prod nothing nothing
  just = Prod just just
instance Listable (Production vs) where
  nil = ProdE nil
  cons = ProdE cons
instance Listable Prod where
  nil = Prod nil nil
  cons = Prod cons cons
instance Equalable (Production vs) where
  equal = ProdE equal
instance Equalable Prod where
  equal = Prod equal equal
instance Inferable () (SomeData Identity) where
  infer = constant ()
instance Inferable () (SomeData TH.CodeQ) where
  infer = constant ()

-- TH.CodeQ
instance Anythingable TH.CodeQ
instance Abstractable TH.CodeQ where
  lam f = [|| \x -> $$(f [||x||]) ||]
instance Instantiable TH.CodeQ where
  f .@ x = [|| $$f $$x ||]
instance Abstractable1 TH.CodeQ where
  lam1 f = [|| \u -> $$(f [||u||]) ||]
instance Varable TH.CodeQ where
  var = Fun.id
instance Unabstractable TH.CodeQ where
  ap = [|| (App.<*>) ||]
  id = [|| Fun.id ||]
  const = [|| Fun.const ||]
  flip = [|| Fun.flip ||]
  ($) = [|| (Fun.$) ||]
  (.) = [|| (Fun..) ||]
instance TH.Lift c => Constantable c TH.CodeQ where
  constant c = [|| c ||]
instance Eitherable TH.CodeQ where
  either = [|| Either.either ||]
  left = [|| Either.Left ||]
  right = [|| Either.Right ||]
instance Equalable TH.CodeQ where
  equal = [|| (Eq.==) ||]
instance IfThenElseable TH.CodeQ where
  ifThenElse test ok ko = [|| if $$test then $$ok else $$ko ||]
instance Listable TH.CodeQ where
  cons = [|| (:) ||]
  nil = [|| [] ||]
instance Maybeable TH.CodeQ where
  nothing = [|| Maybe.Nothing ||]
  just = [|| Maybe.Just ||]
instance Num a => Num (TH.CodeQ a) where
  x + y = [|| $$x + $$y||]
  x * y = [|| $$x * $$y||]
  x - y = [|| $$x - $$y||]
  abs x = [|| abs $$x ||]
  fromInteger i = [|| fromInteger $$(TH.liftTyped i) ||]
  negate x = [|| negate $$x ||]
  signum x = [|| signum $$x ||]