{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Roboservant.Types.Breakdown where

import Data.Dynamic (Dynamic)
import Data.Hashable
import Data.Kind
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NEL
import Data.Typeable (Typeable)
import GHC.Generics
import Roboservant.Types.Internal
import Servant
import Roboservant.Types.Orphans()

breakdown ::
  (Hashable x, Typeable x, Breakdown x) =>
  x ->
  NonEmpty (Dynamic, Int)
breakdown :: forall x.
(Hashable x, Typeable x, Breakdown x) =>
x -> NonEmpty (Dynamic, Int)
breakdown x
x = forall a. (Hashable a, Typeable a) => a -> (Dynamic, Int)
hashedDyn x
x forall a. a -> [a] -> NonEmpty a
:| forall x. Breakdown x => x -> [(Dynamic, Int)]
breakdownExtras x
x

class Breakdown x where
  breakdownExtras :: x -> [(Dynamic, Int)]

instance (Hashable x, Typeable x) => Breakdown (Atom x) where
  breakdownExtras :: Atom x -> [(Dynamic, Int)]
breakdownExtras Atom x
_ = []

deriving via (Atom ()) instance Breakdown ()

deriving via (Atom Int) instance Breakdown Int
deriving via (Atom Char) instance Breakdown Char

deriving via (Compound (Maybe x)) instance (Typeable x, Hashable x, Breakdown x) => Breakdown (Maybe x)

instance (Hashable x, Typeable x, Breakdown x) => Breakdown [x] where
  breakdownExtras :: [x] -> [(Dynamic, Int)]
breakdownExtras [x]
stash =  forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (forall a. NonEmpty a -> [a]
NEL.toList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall x.
(Hashable x, Typeable x, Breakdown x) =>
x -> NonEmpty (Dynamic, Int)
breakdown) [x]
stash


class GBreakdown (f :: k -> Type) where
  gBreakdownExtras :: f a -> [(Dynamic, Int)]

instance (Hashable x, Typeable x, Generic x, GBreakdown (Rep x)) => Breakdown (Compound (x :: Type)) where
  breakdownExtras :: Compound x -> [(Dynamic, Int)]
breakdownExtras = forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a x. Generic a => a -> Rep a x
from forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall x. Compound x -> x
unCompound

instance GBreakdown f => GBreakdown (M1 S c f) where
  gBreakdownExtras :: forall (a :: k). M1 S c f a -> [(Dynamic, Int)]
gBreakdownExtras (M1 f a
f) = forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras f a
f

instance GBreakdown b => GBreakdown (M1 D a b) where
  gBreakdownExtras :: forall (a :: k). M1 D a b a -> [(Dynamic, Int)]
gBreakdownExtras (M1 b a
f) = forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras b a
f

instance GBreakdown b => GBreakdown (M1 C a b) where
  gBreakdownExtras :: forall (a :: k). M1 C a b a -> [(Dynamic, Int)]
gBreakdownExtras (M1 b a
f) = forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras b a
f

instance (GBreakdown a, GBreakdown b) => GBreakdown (a :*: b) where
  gBreakdownExtras :: forall (a :: k). (:*:) a b a -> [(Dynamic, Int)]
gBreakdownExtras (a a
a :*: b a
b) = forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras a a
a forall a. Semigroup a => a -> a -> a
<> forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras b a
b

instance (GBreakdown a, GBreakdown b) => GBreakdown (a :+: b) where
  gBreakdownExtras :: forall (a :: k). (:+:) a b a -> [(Dynamic, Int)]
gBreakdownExtras = \case
    L1 a a
a -> forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras a a
a
    R1 b a
a -> forall k (f :: k -> *) (a :: k).
GBreakdown f =>
f a -> [(Dynamic, Int)]
gBreakdownExtras b a
a

instance (Hashable a, Typeable a, Breakdown a) => GBreakdown (K1 R a) where
  gBreakdownExtras :: forall (a :: k). K1 R a a -> [(Dynamic, Int)]
gBreakdownExtras (K1 a
c) = forall a. NonEmpty a -> [a]
NEL.toList forall a b. (a -> b) -> a -> b
$ forall x.
(Hashable x, Typeable x, Breakdown x) =>
x -> NonEmpty (Dynamic, Int)
breakdown a
c

instance GBreakdown U1 where
  gBreakdownExtras :: forall (a :: k). U1 a -> [(Dynamic, Int)]
gBreakdownExtras U1 a
U1 = []

deriving via (Atom NoContent) instance Breakdown NoContent