{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Generics.Eot.Datatype where

import           Data.Maybe
import           Data.Kind
import           Data.Proxy
import qualified GHC.Generics as GHC
import           GHC.Generics hiding (Datatype(..), Constructor(..))

-- | Type for meta information about ADTs.
data Datatype
  = Datatype {
    datatypeName :: String, -- ^ unqualified name of the type
    constructors :: [Constructor]
  }
  deriving (Show, Eq)

data Constructor
  = Constructor {
    constructorName :: String,
    fields :: Fields
  }
  deriving (Show, Eq)

-- | Type that represents meta information about fields of one
-- constructor.
data Fields
  = Selectors [String]
    -- ^ Record constructor, containing the list of the selector names.
  | NoSelectors Int
    -- ^ Constructor with fields, but without selector names.
    -- The argument gives the number of fields.
  | NoFields
    -- ^ Constructor without fields.
  deriving (Show, Eq)

-- * datatype

class GenericDatatype (a :: Type -> Type) where
  datatypeC :: Proxy a -> Datatype

instance (GHC.Datatype c, GenericConstructors f) =>
  GenericDatatype (D1 c f) where
    datatypeC Proxy = Datatype n constructors
      where
        n = GHC.datatypeName (undefined :: D1 c f x)
        constructors = getConstructors (Proxy :: Proxy f)

-- * constructors

class GenericConstructors (a :: Type -> Type) where
  getConstructors :: Proxy a -> [Constructor]

instance (GenericConstructors a, GenericConstructors b) =>
  GenericConstructors (a :+: b) where
    getConstructors Proxy = getConstructors a ++ getConstructors b
      where
        a :: Proxy a = Proxy
        b :: Proxy b = Proxy

instance (GHC.Constructor c, GenericFields f) =>
  GenericConstructors (C1 c f) where
    getConstructors Proxy = [Constructor n (getFields f)]
      where
        n = GHC.conName (undefined :: (C1 c f x))
        f :: Proxy f = Proxy

instance GenericConstructors V1 where
  getConstructors Proxy = []

-- * fields

getFields :: GenericFields a => Proxy a -> Fields
getFields proxy = case getFieldsC proxy of
  [] -> NoFields
  l@(Nothing : _) -> NoSelectors (length l)
  l@(Just _ : _) -> Selectors (catMaybes l)

class GenericFields (a :: Type -> Type) where
  getFieldsC :: Proxy a -> [Maybe String]

instance (GenericFields a, GenericFields b) =>
  GenericFields (a :*: b) where
    getFieldsC Proxy = getFieldsC a ++ getFieldsC b
      where
        a :: Proxy a = Proxy
        b :: Proxy b = Proxy

instance Selector c => GenericFields (S1 c (Rec0 f)) where
  getFieldsC proxy = [getField proxy]

getField :: forall c f . Selector c =>
  Proxy (S1 c (Rec0 f)) -> Maybe String
getField Proxy = case selName (undefined :: S1 c (Rec0 f) x) of
  "" -> Nothing
  s -> Just s

instance GenericFields U1 where
  getFieldsC Proxy = []
