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

{-# OPTIONS_GHC -Wno-orphans #-}

-- | Additional functionality for 'named' package.
module Util.Named
  ( (:!)
  , (:?)
  , (.!)
  , (.?)
  , (<.!>)
  , (<.?>)
  , ApplyNamedFunctor
  , NamedInner
  , KnownNamedFunctor (..)
  ) where

import Control.Lens (Iso', Wrapped(..), iso)
import Data.Aeson (FromJSON, ToJSON)
import Data.Data (Data)
import qualified Data.Kind as Kind
import Fmt (Buildable(..))
import GHC.TypeLits (KnownSymbol, symbolVal)
import Named ((:!), (:?), Name, NamedF(..))
import qualified Text.Show

import Util.Label (Label)

(.!) :: Name name -> a -> NamedF Identity a name
(.!) _ = ArgF . Identity

(.?) :: Name name -> Maybe a -> NamedF Maybe a name
(.?) _ = ArgF

(<.!>) :: Functor m => Name name -> m a -> m (NamedF Identity a name)
(<.!>) name = fmap (name .!)
infixl 4 <.!>

(<.?>) :: Functor m => Name name -> m (Maybe a) -> m (NamedF Maybe a name)
(<.?>) name = fmap (name .?)
infixl 4 <.?>

type family ApplyNamedFunctor (f :: Kind.Type -> Kind.Type) (a :: Kind.Type) where
  ApplyNamedFunctor Identity a = a
  ApplyNamedFunctor Maybe a = Maybe a

type family NamedInner (n :: Kind.Type) where
  NamedInner (NamedF f a _) = ApplyNamedFunctor f a

-- | Isomorphism between named entity and the entity itself wrapped into the
-- respective functor.
namedFL :: Label name -> Iso' (NamedF f a name) (f a)
namedFL _ = iso (\(ArgF x) -> x) ArgF

class KnownNamedFunctor f where
  -- | Isomorphism between named entity and the entity itself.
  namedL :: Label name -> Iso' (NamedF f a name) (ApplyNamedFunctor f a)

instance KnownNamedFunctor Identity where
  namedL l = namedFL l . _Wrapped'

instance KnownNamedFunctor Maybe where
  namedL l = namedFL l

----------------------------------------------------------------------------
-- Instances
----------------------------------------------------------------------------

deriving stock instance Eq (f a) => Eq (NamedF f a name)
deriving stock instance Ord (f a) => Ord (NamedF f a name)

instance (Show a, KnownSymbol name) => Show (NamedF Identity a name) where
  show (ArgF a) = symbolVal (Proxy @name) <> " :! " <> show a

instance (KnownSymbol name, Buildable (f a)) => Buildable (NamedF f a name) where
  build (ArgF a) = build (symbolVal (Proxy @name)) <> ": " <> build a

deriving stock instance
  (Typeable f, Typeable a, KnownSymbol name, Data (f a)) =>
  Data (NamedF f a name)

deriving newtype instance ToJSON a => ToJSON (NamedF Identity a name)
deriving newtype instance ToJSON a => ToJSON (NamedF Maybe a name)
deriving newtype instance FromJSON a => FromJSON (NamedF Identity a name)
deriving newtype instance FromJSON a => FromJSON (NamedF Maybe a name)