{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module T15703 where

import Data.Kind
import Data.Type.Equality
import GHC.Generics
import T15703_aux

-----
-- The important bits
-----

-- DefaultSignatures-based instances
instance PMonoid a => PApplicative ((,) a)
instance SMonoid a => SApplicative ((,) a)
instance VMonoid a => VApplicative ((,) a)

instance SApplicative f => SApplicative (M1 i c f) where
  sPure x = singFun3 @(.@#@$) (%.) @@ singFun1 @M1Sym0 SM1 @@ (singFun1 @PureSym0 sPure) @@ x
  -- If I change the implementation of sPure above to be this:
  --
  --   sPure x = SM1 (sPure x)
  --
  -- Then T15703.hs compiles quickly again (< 1 second) with -O1.

  SM1 f %<*> SM1 x = SM1 (f %<*> x)

-------------------------------------------------------------------------------

type GenericPure :: a -> f a
type family GenericPure (x :: a) :: f a where
  GenericPure x = To1 (Pure x)

type GenericPureC (f :: Type -> Type) (x :: a) =
  (Pure x :: f a) ~ (GenericPure x :: f a)

type GenericAp :: f (a ~> b) -> f a -> f b
type family (g :: f (a ~> b)) `GenericAp` (x :: f a) :: f b where
  g `GenericAp` x = To1 (From1 g <*> From1 x)

type GenericApC (g :: f (a ~> b)) (x :: f a) =
  (g <*> x) ~ (g `GenericAp` x)

class PApplicative f where
  type Pure (x :: a) :: f a
  type Pure x = GenericPure x

  type (g :: f (a ~> b)) <*> (x :: f a) :: f b
  type g <*> x = g `GenericAp` x

data PureSym0 :: forall f a. a ~> f a
type instance Apply PureSym0 x = Pure x

data (<*>@#@$) :: forall f a b. f (a ~> b) ~> f a ~> f b
type instance Apply (<*>@#@$) g = (<*>@#@$$) g
data (<*>@#@$$) :: forall f a b. f (a ~> b) -> f a ~> f b
type instance Apply ((<*>@#@$$) g) x = g <*> x

class SApplicative f where
  sPure         :: forall a (x :: a).
                   Sing x -> Sing (Pure x :: f a)
  default sPure :: forall a (x :: a).
                   ( SGeneric1 f, SApplicative (Rep1 f)
                   , GenericPureC f x )
                => Sing x -> Sing (Pure x :: f a)
  sPure = sTo1 . sPure

  (%<*>)         :: forall a b (g :: f (a ~> b)) (x :: f a).
                    Sing g -> Sing x -> Sing (g <*> x)
  default (%<*>) :: forall a b (g :: f (a ~> b)) (x :: f a).
                    ( SGeneric1 f, SApplicative (Rep1 f)
                    , GenericApC g x )
                 => Sing g -> Sing x -> Sing (g <*> x)
  sg %<*> sx = sTo1 (sFrom1 sg %<*> sFrom1 sx)

class (PApplicative f, SApplicative f) => VApplicative f where
  applicativeHomomorphism         :: forall a b (g :: a ~> b) (x :: a).
                                     Sing g -> Sing x
                                  -> (Pure g <*> Pure x) :~: (Pure (g `Apply` x) :: f b)
  default applicativeHomomorphism :: forall a b (g :: a ~> b) (x :: a).
                                     ( VGeneric1 f, VApplicative (Rep1 f)
                                     , GenericPureC f g, GenericPureC f x, GenericPureC f (g `Apply` x)
                                     , GenericApC (Pure g :: f (a ~> b)) (Pure x :: f a)
                                     )
                                  => Sing g -> Sing x
                                  -> (Pure g <*> Pure x) :~: (Pure (g `Apply` x) :: f b)
  applicativeHomomorphism sg sx
    | Refl <- sFot1 @Type @f (sPure sg)
    , Refl <- sFot1 @Type @f (sPure sx)
    , Refl <- applicativeHomomorphism @(Rep1 f) sg sx
    , Refl <- sFot1 @Type @f pureGX
    , Refl <- sTof1 @Type @f (sTo1 pureGX)
    = Refl
    where
      pureGX :: Sing (Pure (Apply g x) :: Rep1 f b)
      pureGX = sPure (sg @@ sx)

instance PApplicative (K1 i c) where
  type Pure _ = 'K1 Mempty
  type 'K1 x <*> 'K1 y = 'K1 (x <> y)

instance SMonoid c => SApplicative (K1 i c) where
  sPure _ = SK1 sMempty
  SK1 x %<*> SK1 y = SK1 (x %<> y)

instance VMonoid c => VApplicative (K1 i c) where
  applicativeHomomorphism _ _ | Refl <- monoidLeftIdentity (sMempty @c) = Refl

instance PApplicative (M1 i c f) where
  type Pure x = 'M1 (Pure x)
  type 'M1 ff <*> 'M1 x = 'M1 (ff <*> x)

instance VApplicative f => VApplicative (M1 i c f) where
  applicativeHomomorphism sg sx
    | Refl <- applicativeHomomorphism @f sg sx
    = Refl

instance PApplicative (f :*: g) where
  type Pure a = Pure a ':*: Pure a
  type (ff ':*: gg) <*> (x ':*: y) = (ff <*> x) ':*: (gg <*> y)

instance (SApplicative f, SApplicative g) => SApplicative (f :*: g) where
  sPure a = sPure a :%*: sPure a
  (f :%*: g) %<*> (x :%*: y) = (f %<*> x) :%*: (g %<*> y)

instance (VApplicative f, VApplicative g) => VApplicative (f :*: g) where
  applicativeHomomorphism sg sx
    | Refl <- applicativeHomomorphism @f sg sx
    , Refl <- applicativeHomomorphism @g sg sx
    = Refl

instance PApplicative Par1 where
  type Pure x = 'Par1 x
  type 'Par1 f <*> 'Par1 x = 'Par1 (f @@ x)

instance SApplicative Par1 where
  sPure = SPar1
  SPar1 f %<*> SPar1 x = SPar1 (f @@ x)

instance VApplicative Par1 where
  applicativeHomomorphism _ _ = Refl
