module Data.TagLabel ( module GHC.Generics
                     , TagLabel(..)
                     , SpelledAs(..)
                     , fromLabelText
                     , toLabelText
                     ) where

import           Data.Char
import           Data.Map
import           GHC.Generics    (Generic,Rep(..),(:+:)(..))
import           GHC.TypeLits
import           Protolude       hiding(fromLabel)
import qualified Data.Text       as T
import qualified GHC.Generics    as G

--------------------------------------------------------------------------------

-- |
class (Ord label) => TagLabel label where
   labelMap ::  Map Text label

   default labelMap :: (Generic label, GLabel (Rep label) ) => Map Text label
   labelMap = fmap G.to . fromList $  gLabelMap (Proxy :: Proxy (Rep label))

   reverseLabelMap :: Map label Text
   reverseLabelMap = fromList [ (b,a) | (a,b) <- assocs labelMap]

-- |
fromLabelText :: (TagLabel label) => Text -> Maybe label
fromLabelText t = lookup t labelMap

-- | 
toLabelText   :: (TagLabel label) => label -> Text
toLabelText l =
  fromMaybe (error "imposible branch on Data.Label") 
    $ lookup l reverseLabelMap

--------------------------------------------------------------------------------
-- Machinery

class GLabel f where
    gLabelMap :: Proxy f -> [(Text, f a)]

-- | Definitions: `D1` (instance base on to type definition metadata...we can ignore it and focus on `f`)
instance (GLabel f) => GLabel (D1 meta f) where
    gLabelMap _ = second M1 <$> gLabelMap (Proxy :: Proxy f)


-- | The labels of sum types are the labels of its parts 
instance (GLabel f, GLabel g) => GLabel ( f :+: g) where
  gLabelMap _ =  ( second L1  <$> gLabelMap (Proxy :: Proxy f))
              ++ ( second R1  <$> gLabelMap (Proxy :: Proxy g))

-- | The label of a constructor without arguments is the constructor's name itself
instance (KnownSymbol constructor) => GLabel (C1 ('MetaCons constructor a b) U1) where
  gLabelMap _ = [( convert . toSL $ symbolVal (Proxy :: Proxy constructor) , M1 U1)] 
    where
      convert s = if T.any isLower s then T.toLower s
                                     else s
-- | The labels with a constructor with a single argument, are the labels of its argument 
instance (TagLabel labelType) => GLabel (C1 meta1 (S1 meta2 (K1 meta3 labelType))) where
  gLabelMap _ = assocs $ (M1 . M1 . K1)  <$> labelMap

---------------------------------------------------------------------------------------
-- Standard labels

-- | The labels of a sum type, is the cartesian sum of its labels 
instance (TagLabel l1, TagLabel l2)  => TagLabel (Either l1 l2)  

-- | The label of a `SpelledAs x` is x, where x is a type level literal string (a "Symbol").
instance (KnownSymbol symbol) => TagLabel (SpelledAs symbol) where
   labelMap = fromList [(toSL $ symbolVal (Proxy :: Proxy symbol),  SymbolProxy)]

data SpelledAs (s :: Symbol) = SymbolProxy deriving(Show,Read,Eq,Ord,Generic)