-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

module Lorentz.Entrypoints.Helpers
  ( ctorNameToAnn
  , ctorNameToEp
  , CanHaveEntrypoints
  , ShouldHaveEntrypoints (..)
  , RequireSumType
  ) where

import qualified Data.Kind as Kind

import Michelson.Typed.Haskell
import Michelson.Typed.T
import Michelson.Untyped (EpName, FieldAnn, ann, epNameFromParamAnn)
import Util.Text
import Util.Type
import Util.TypeLits

ctorNameToAnn :: forall ctor. (KnownSymbol ctor, HasCallStack) => FieldAnn
ctorNameToAnn :: FieldAnn
ctorNameToAnn = Text -> FieldAnn
forall k (a :: k). HasCallStack => Text -> Annotation a
ann (Text -> FieldAnn) -> (Text -> Text) -> Text -> FieldAnn
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text
Text -> Text
headToLower (Text -> FieldAnn) -> Text -> FieldAnn
forall a b. (a -> b) -> a -> b
$ (KnownSymbol ctor => Text
forall (s :: Symbol). KnownSymbol s => Text
symbolValT' @ctor)

ctorNameToEp :: forall ctor. (KnownSymbol ctor, HasCallStack) => EpName
ctorNameToEp :: EpName
ctorNameToEp =
  FieldAnn -> Maybe EpName
epNameFromParamAnn ((KnownSymbol ctor, HasCallStack) => FieldAnn
forall (ctor :: Symbol).
(KnownSymbol ctor, HasCallStack) =>
FieldAnn
ctorNameToAnn @ctor)
  Maybe EpName -> EpName -> EpName
forall a. Maybe a -> a -> a
?: Text -> EpName
forall a. HasCallStack => Text -> a
error "Empty constructor-entrypoint name"

-- | A special type which wraps over a primitive type and states that it has
-- entrypoints (one).
--
-- Assuming that any type can have entrypoints makes use of Lorentz entrypoints
-- too annoying, so for declaring entrypoints for not sum types we require an
-- explicit wrapper.
newtype ShouldHaveEntrypoints a = ShouldHaveEntrypoints { ShouldHaveEntrypoints a -> a
unHasEntrypoints :: a }
  deriving stock (forall x.
 ShouldHaveEntrypoints a -> Rep (ShouldHaveEntrypoints a) x)
-> (forall x.
    Rep (ShouldHaveEntrypoints a) x -> ShouldHaveEntrypoints a)
-> Generic (ShouldHaveEntrypoints a)
forall x.
Rep (ShouldHaveEntrypoints a) x -> ShouldHaveEntrypoints a
forall x.
ShouldHaveEntrypoints a -> Rep (ShouldHaveEntrypoints a) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall a x.
Rep (ShouldHaveEntrypoints a) x -> ShouldHaveEntrypoints a
forall a x.
ShouldHaveEntrypoints a -> Rep (ShouldHaveEntrypoints a) x
$cto :: forall a x.
Rep (ShouldHaveEntrypoints a) x -> ShouldHaveEntrypoints a
$cfrom :: forall a x.
ShouldHaveEntrypoints a -> Rep (ShouldHaveEntrypoints a) x
Generic

deriving anyclass instance (WellTypedIsoValue r) => IsoValue (ShouldHaveEntrypoints r)

-- | Used to understand whether a type can potentially declare any entrypoints.
type family CanHaveEntrypoints (p :: Kind.Type) :: Bool where
  CanHaveEntrypoints (ShouldHaveEntrypoints _) = 'True
  CanHaveEntrypoints p = CanHaveEntrypointsT (ToT p)

type family CanHaveEntrypointsT (t :: T) :: Bool where
  CanHaveEntrypointsT ('TOr _ _) = 'True
  CanHaveEntrypointsT _ = 'False

-- | Ensure that given type is a sum type.
--
-- This helps to prevent attempts to apply a function to, for instance, a pair.
type family RequireSumType (a :: Kind.Type) :: Constraint where
  RequireSumType a =
    If (CanHaveEntrypoints a)
       (() :: Constraint)
       (TypeError ('Text "Expected Michelson sum type" ':$$:
                   'Text "In type `" ':<>: 'ShowType a ':<>: 'Text "`"
                  ))