{-# LANGUAGE UndecidableInstances, OverlappingInstances, Rank2Types,
    CPP, KindSignatures, MultiParamTypeClasses, EmptyDataDecls #-}

{-

(C) 2004--2005 Ralf Laemmel, Simon D. Foster

This module approximates Data.Generics.Basics.

-}


module Data.Generics.SYB.WithClass.Basics (

 module Data.Typeable,
 module Data.Generics.SYB.WithClass.Context,
 module Data.Generics.SYB.WithClass.Basics

) where

#if MIN_VERSION_base(4,7,0)
import Data.Typeable hiding (Proxy)
#else
import Data.Typeable
#endif

import Data.Generics.SYB.WithClass.Context

#ifdef __HADDOCK__
data Proxy
#else
data Proxy (a :: * -> *)
#endif

------------------------------------------------------------------------------
-- The ingenious Data class

class (Typeable a, Sat (ctx a)) => Data ctx a

   where

     gfoldl :: Proxy ctx
            -> (forall b c. Data ctx b => w (b -> c) -> b -> w c)
            -> (forall g. g -> w g)
            -> a -> w a

     -- Default definition for gfoldl
     -- which copes immediately with basic datatypes
     --
     gfoldl _ _ z = z

     gunfold :: Proxy ctx
             -> (forall b r. Data ctx b => c (b -> r) -> c r)
             -> (forall r. r -> c r)
             -> Constr
             -> c a

     toConstr :: Proxy ctx -> a -> Constr

     dataTypeOf :: Proxy ctx -> a -> DataType

     -- incomplete implementation

     gunfold _ _ _ _ = undefined

     dataTypeOf _ _ = undefined

     -- | Mediate types and unary type constructors
     dataCast1 :: Typeable1 t
               => Proxy ctx
               -> (forall b. Data ctx b => w (t b))
               -> Maybe (w a)
     dataCast1 _ _ = Nothing

     -- | Mediate types and binary type constructors
     dataCast2 :: Typeable2 t
               => Proxy ctx
               -> (forall b c. (Data ctx b, Data ctx c) => w (t b c))
               -> Maybe (w a)
     dataCast2 _ _ = Nothing



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

-- Generic transformations

type GenericT ctx = forall a. Data ctx a => a -> a


-- Generic map for transformations

gmapT :: Proxy ctx -> GenericT ctx -> GenericT ctx

gmapT ctx f x = unID (gfoldl ctx k ID x)
  where
    k (ID g) y = ID (g (f y))


-- The identity type constructor

newtype ID x = ID { unID :: x }


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

-- Generic monadic transformations

type GenericM m ctx = forall a. Data ctx a => a -> m a

-- Generic map for monadic transformations

gmapM :: Monad m => Proxy ctx -> GenericM m ctx -> GenericM m ctx
gmapM ctx f = gfoldl ctx k return
    where k c x = do c' <- c
                     x' <- f x
                     return (c' x')


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

-- Generic queries

type GenericQ ctx r = forall a. Data ctx a => a -> r


-- Map for queries

gmapQ :: Proxy ctx -> GenericQ ctx r -> GenericQ ctx [r]
gmapQ ctx f = gmapQr ctx (:) [] f

gmapQr :: Data ctx a
       => Proxy ctx
       -> (r' -> r -> r)
       -> r
       -> GenericQ ctx r'
       -> a
       -> r
gmapQr ctx o r f x = unQr (gfoldl ctx k (const (Qr id)) x) r
  where
    k (Qr g) y = Qr (\s -> g (f y `o` s))

-- The type constructor used in definition of gmapQr
newtype Qr r a = Qr { unQr  :: r -> r }



------------------------------------------------------------------------------
--
-- Generic unfolding
--
------------------------------------------------------------------------------



-- | Build a term skeleton
fromConstr :: Data ctx a => Proxy ctx -> Constr -> a
fromConstr ctx = fromConstrB ctx undefined

-- | Build a term and use a generic function for subterms
fromConstrB :: Data ctx a
            => Proxy ctx
            -> (forall b. Data ctx b => b)
            -> Constr
            -> a
fromConstrB ctx f = unID . gunfold ctx k z
 where
  k c = ID (unID c f)
  z = ID



-- | Monadic variation on \"fromConstrB\"
fromConstrM :: (Monad m, Data ctx a)
            => Proxy ctx
            -> (forall b. Data ctx b => m b)
            -> Constr
            -> m a
fromConstrM ctx f = gunfold ctx k z
 where
  k c = do { c' <- c; b <- f; return (c' b) }
  z = return



------------------------------------------------------------------------------
--
-- Datatype and constructor representations
--
------------------------------------------------------------------------------


--
-- | Representation of datatypes.
-- | A package of constructor representations with names of type and module.
-- | The list of constructors could be an array, a balanced tree, or others.
--
data DataType = DataType
                        { tycon   :: String
                        , datarep :: DataRep
                        }

              deriving Show


-- | Representation of constructors
data Constr = Constr
                        { conrep    :: ConstrRep
                        , constring :: String
                        , confields :: [String] -- for AlgRep only
                        , confixity :: Fixity   -- for AlgRep only
                        , datatype  :: DataType
                        }

instance Show Constr where
 show = constring


-- | Equality of constructors
instance Eq Constr where
  c == c' = constrRep c == constrRep c'


-- | Public representation of datatypes
data DataRep = AlgRep [Constr]
             | IntRep
             | FloatRep
             | StringRep
             | NoRep

            deriving (Eq,Show)


-- | Public representation of constructors
data ConstrRep = AlgConstr    ConIndex
               | IntConstr    Integer
               | FloatConstr  Double
               | StringConstr String

               deriving (Eq,Show)


--
-- | Unique index for datatype constructors.
-- | Textual order is respected. Starts at 1.
--
type ConIndex = Int


-- | Fixity of constructors
data Fixity = Prefix
            | Infix  -- Later: add associativity and precedence

            deriving (Eq,Show)


------------------------------------------------------------------------------
--
-- Observers for datatype representations
--
------------------------------------------------------------------------------


-- | Gets the type constructor including the module
dataTypeName :: DataType -> String
dataTypeName = tycon



-- | Gets the public presentation of datatypes
dataTypeRep :: DataType -> DataRep
dataTypeRep = datarep


-- | Gets the datatype of a constructor
constrType :: Constr -> DataType
constrType = datatype


-- | Gets the public presentation of constructors
constrRep :: Constr -> ConstrRep
constrRep = conrep


-- | Look up a constructor by its representation
repConstr :: DataType -> ConstrRep -> Constr
repConstr dt cr =
      case (dataTypeRep dt, cr) of
        (AlgRep cs, AlgConstr i)      -> cs !! (i-1)
        (IntRep,    IntConstr i)      -> mkIntConstr dt i
        (FloatRep,  FloatConstr f)    -> mkFloatConstr dt f
        (StringRep, StringConstr str) -> mkStringConstr dt str
        _ -> error "repConstr"



------------------------------------------------------------------------------
--
-- Representations of algebraic data types
--
------------------------------------------------------------------------------


-- | Constructs an algebraic datatype
mkDataType :: String -> [Constr] -> DataType
mkDataType str cs = DataType
                        { tycon   = str
                        , datarep = AlgRep cs
                        }


-- | Constructs a constructor
mkConstr :: DataType -> String -> [String] -> Fixity -> Constr
mkConstr dt str fields fix =
        Constr
                { conrep    = AlgConstr idx
                , constring = str
                , confields = fields
                , confixity = fix
                , datatype  = dt
                }
  where
    idx = head [ i | (c,i) <- dataTypeConstrs dt `zip` [1..],
                     showConstr c == str ]


-- | Gets the constructors
dataTypeConstrs :: DataType -> [Constr]
dataTypeConstrs dt = case datarep dt of
                        (AlgRep cons) -> cons
                        _ -> error "dataTypeConstrs"


-- | Gets the field labels of a constructor
constrFields :: Constr -> [String]
constrFields = confields


-- | Gets the fixity of a constructor
constrFixity :: Constr -> Fixity
constrFixity = confixity



------------------------------------------------------------------------------
--
-- From strings to constr's and vice versa: all data types
--
------------------------------------------------------------------------------


-- | Gets the string for a constructor
showConstr :: Constr -> String
showConstr = constring


-- | Lookup a constructor via a string
readConstr :: DataType -> String -> Maybe Constr
readConstr dt str =
      case dataTypeRep dt of
        AlgRep cons -> idx cons
        IntRep      -> mkReadCon (\i -> (mkPrimCon dt str (IntConstr i)))
        FloatRep    -> mkReadCon (\f -> (mkPrimCon dt str (FloatConstr f)))
        StringRep   -> Just (mkStringConstr dt str)
        NoRep       -> Nothing
  where

    -- Read a value and build a constructor
    mkReadCon :: Read t => (t -> Constr) -> Maybe Constr
    mkReadCon f = case (reads str) of
                    [(t,"")] -> Just (f t)
                    _ -> Nothing

    -- Traverse list of algebraic datatype constructors
    idx :: [Constr] -> Maybe Constr
    idx cons = let fit = filter ((==) str . showConstr) cons
                in if fit == []
                     then Nothing
                     else Just (head fit)


------------------------------------------------------------------------------
--
-- Convenience funtions: algebraic data types
--
------------------------------------------------------------------------------


-- | Test for an algebraic type
isAlgType :: DataType -> Bool
isAlgType dt = case datarep dt of
                 (AlgRep _) -> True
                 _ -> False


-- | Gets the constructor for an index
indexConstr :: DataType -> ConIndex -> Constr
indexConstr dt idx = case datarep dt of
                        (AlgRep cs) -> cs !! (idx-1)
                        _           -> error "indexConstr"


-- | Gets the index of a constructor
constrIndex :: Constr -> ConIndex
constrIndex con = case constrRep con of
                    (AlgConstr idx) -> idx
                    _ -> error "constrIndex"


-- | Gets the maximum constructor index
maxConstrIndex :: DataType -> ConIndex
maxConstrIndex dt = case dataTypeRep dt of
                        AlgRep cs -> length cs
                        _         -> error "maxConstrIndex"



------------------------------------------------------------------------------
--
-- Representation of primitive types
--
------------------------------------------------------------------------------


-- | Constructs the Int type
mkIntType :: String -> DataType
mkIntType = mkPrimType IntRep


-- | Constructs the Float type
mkFloatType :: String -> DataType
mkFloatType = mkPrimType FloatRep


-- | Constructs the String type
mkStringType :: String -> DataType
mkStringType = mkPrimType StringRep


-- | Helper for mkIntType, mkFloatType, mkStringType
mkPrimType :: DataRep -> String -> DataType
mkPrimType dr str = DataType
                        { tycon   = str
                        , datarep = dr
                        }


-- Makes a constructor for primitive types
mkPrimCon :: DataType -> String -> ConstrRep -> Constr
mkPrimCon dt str cr = Constr
                        { datatype  = dt
                        , conrep    = cr
                        , constring = str
                        , confields = error $ concat ["constrFields : ", (tycon dt), " is primative"]
                        , confixity = error "constrFixity"
                        }


mkIntConstr :: DataType -> Integer -> Constr
mkIntConstr dt i = case datarep dt of
                  IntRep -> mkPrimCon dt (show i) (IntConstr i)
                  _ -> error "mkIntConstr"


mkFloatConstr :: DataType -> Double -> Constr
mkFloatConstr dt f = case datarep dt of
                    FloatRep -> mkPrimCon dt (show f) (FloatConstr f)
                    _ -> error "mkFloatConstr"


mkStringConstr :: DataType -> String -> Constr
mkStringConstr dt str = case datarep dt of
                       StringRep -> mkPrimCon dt str (StringConstr str)
                       _ -> error "mkStringConstr"


------------------------------------------------------------------------------
--
-- Non-representations for non-presentable types
--
------------------------------------------------------------------------------


-- | Constructs a non-representation
mkNorepType :: String -> DataType
mkNorepType str = DataType
                        { tycon   = str
                        , datarep = NoRep
                        }


-- | Test for a non-representable type
isNorepType :: DataType -> Bool
isNorepType dt = case datarep dt of
                   NoRep -> True
                   _ -> False

