-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

-- | Permissions for casts between wrappers and their inner types.
module Lorentz.Wrappable
  ( Unwrappable (..)
  , Wrappable
  ) where

import Data.Fixed (Fixed(..))
import GHC.Generics (C1, D1, Generic(..), Meta(..), Rec0, S1)
import GHC.TypeLits (ErrorMessage(..), TypeError)

import Morley.Michelson.Typed (ToT)
import Morley.Util.Named

-- | Declares that this type is just a wrapper over some other type
-- and it can be safely unwrapped to that inner type.
--
-- Inspired by lens @Wrapped@.
class ToT s ~ ToT (Unwrappabled s) => Unwrappable (s :: Type) where
  -- | The type we unwrap to (inner type of the newtype).
  --
  -- Used in constraint for Lorentz instruction wrapping into a Haskell newtype
  -- and vice versa.
  type Unwrappabled s :: Type
  type Unwrappabled s = GUnwrappabled s (Rep s)

-- | Declares that it is safe to wrap an inner type to the given
-- wrapper type. Can be provided in addition to 'Unwrappable'.
--
-- You can declare this instance when your wrapper exists just to
-- make type system differentiate the two types. Example:
-- @newtype TokenId = TokenId Natural@.
--
-- Do /not/ define this instance for wrappers that provide some invariants.
-- Example: @UStore@ type from @morley-upgradeable@.
--
-- 'Wrappable' is similar to lens @Wrapped@ class without the method.
class Unwrappable s => Wrappable (s :: Type)

type family GUnwrappabled (orig :: Type) (rep :: Type -> Type) :: Type where
  GUnwrappabled _ (D1 ('MetaData _ _ _ 'True) (C1 _ (S1 _ (Rec0 a)))) = a
  GUnwrappabled orig _ =
    TypeError ('Text "Type " ':<>: 'ShowType orig ':<>: 'Text " is not a newtype")

instance Unwrappable (NamedF Identity a name) where
  type Unwrappabled (NamedF Identity a name) = a

instance Wrappable (NamedF Identity a name)

instance Unwrappable (NamedF Maybe a name) where
  type Unwrappabled (NamedF Maybe a name) = Maybe a

instance Wrappable (NamedF Maybe a name)

instance Unwrappable (Fixed a) where
  type Unwrappabled (Fixed a) = Integer