{-# LANGUAGE CPP, EmptyDataDecls, ScopedTypeVariables, FlexibleInstances, OverlappingInstances, MultiParamTypeClasses, FunctionalDependencies, FlexibleContexts, UndecidableInstances, TypeFamilies #-}
{-# LANGUAGE IncoherentInstances #-}
--
-- This amazing piece of code does that Data.Typeable.typeOf does,
-- but also does it for polymorphic functions.
-- This is Oleg type magic at its best.
module Data.PolyTypeable(PolyTypeable(..)) where
import Data.Typeable
import Data.Int
import Data.Word

----------------------------------
-- Type analysis, classifies each type constructor and type variables.

data TVar a
data TCon0 c
data TCon1 c a1
data TCon2 c a1 a2
data TCon3 c a1 a2 a3
data TCon4 c a1 a2 a3 a4
data TCon5 c a1 a2 a3 a4 a5

class Analyze a b | a -> b

analyze :: Analyze a b => a -> b
analyze = undefined

data W a

#define BASE(t) instance (r ~ TCon0 t)  => Analyze t r; instance (r ~ TCon0 t)  => Analyze (W t) r
BASE(())
BASE(Bool)
BASE(Char)
BASE(Ordering)
BASE(Int)
BASE(Integer)
BASE(Float)
BASE(Double)
BASE(Int8)
BASE(Int16)
BASE(Int32)
BASE(Int64)
BASE(Word8)
BASE(Word16)
BASE(Word32)
BASE(Word64)

instance (Analyze (W a1) ra1, r ~ TCon1 (c ()) ra1) => Analyze (W (c a1)) r
instance (Analyze (W a1) ra1, r ~ TCon1 (c ()) ra1) => Analyze    (c a1)  r

instance (Analyze (W a1) ra1, Analyze (W a2) ra2, r ~ TCon2 (c () ()) ra1 ra2) => Analyze (W (c a1 a2)) r
instance (Analyze (W a1) ra1, Analyze (W a2) ra2, r ~ TCon2 (c () ()) ra1 ra2) => Analyze    (c a1 a2)  r

instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, r ~ TCon3 (c () () ()) ra1 ra2 ra3) => Analyze (W (c a1 a2 a3)) r
instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, r ~ TCon3 (c () () ())ra1 ra2 ra3) => Analyze    (c a1 a2 a3)  r

instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, Analyze (W a4) ra4, r ~ TCon4 (c () () () ()) ra1 ra2 ra3 ra4) =>
         Analyze (W (c a1 a2 a3 a4)) r
instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, Analyze (W a4) ra4, r ~ TCon4 (c () () () ()) ra1 ra2 ra3 ra4) =>
         Analyze    (c a1 a2 a3 a4)  r

instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, Analyze (W a4) ra4, Analyze (W a5) ra5, r ~ TCon5 (c () () () () ()) ra1 ra2 ra3 ra4 ra5) =>
         Analyze (W (c a1 a2 a3 a4 a5)) r
instance (Analyze (W a1) ra1, Analyze (W a2) ra2, Analyze (W a3) ra3, Analyze (W a4) ra4, Analyze (W a5) ra5, r ~ TCon5 (c () () () () ()) ra1 ra2 ra3 ra4 ra5) =>
         Analyze    (c a1 a2 a3 a4 a5)  r

instance (r ~ TVar a) => Analyze (W a) r

----------------------------------
-- Convert an analysed type to a TypeRep.
-- The tricky part is to assign a fresh type variable to each Haskell type variable.

-- Return a 'TypeRep' for a type.  Type variables look like type constructors,
-- but start with a lower case letter.
class PolyTypeable a where
   polyTypeOf :: a -> TypeRep

instance (Analyze a result, MyTypeable' HNil gout result) =>
         PolyTypeable a  where
    polyTypeOf a = fst $ mytypof' HNil (analyze a)

class MyTypeable' g0 gout classification | g0 classification -> gout where
     mytypof' :: g0 -> classification -> (TypeRep, gout)

instance (Typeable a) => MyTypeable' g0 g0 (TCon0 a) where
     mytypof' g0 _ = (typeOf (undefined::a), g0)

instance (Typeable c, MyTypeable' g0 g1 a1) =>
         MyTypeable' g0 g1 (TCon1 c a1) where
     mytypof' g0 _ = let (c, _)     = splitTyConApp $ typeOf (undefined :: c)
                         (tr1,g1)   = mytypof' g0 (undefined::a1)
                     in  (mkTyConApp c [tr1], g1)

instance (Typeable c, MyTypeable' g0 g a1, MyTypeable' g g2 a2) =>
         MyTypeable' g0 g2 (TCon2 c a1 a2) where
     mytypof' g0 _ = let (c, _)     = splitTyConApp $ typeOf (undefined :: c)
                         (tr1,g1)   = mytypof' g0 (undefined::a1)
                         (tr2,g2)   = mytypof' g1 (undefined::a2)
                     in  (mkTyConApp c [tr1, tr2], g2)

instance (Typeable c, MyTypeable' g0 g1 a1, MyTypeable' g1 g2 a2, MyTypeable' g2 g3 a3) =>
         MyTypeable' g0 g3 (TCon3 c a1 a2 a3) where
     mytypof' g0 _ = let (c, _)     = splitTyConApp $ typeOf (undefined :: c)
                         (tr1,g1)   = mytypof' g0 (undefined::a1)
                         (tr2,g2)   = mytypof' g1 (undefined::a2)
                         (tr3,g3)   = mytypof' g2 (undefined::a3)
                     in  (mkTyConApp c [tr1, tr2, tr3], g3)

instance (Typeable c, MyTypeable' g0 g1 a1, MyTypeable' g1 g2 a2, MyTypeable' g2 g3 a3, MyTypeable' g3 g4 a4) =>
         MyTypeable' g0 g4 (TCon4 c a1 a2 a3 a4) where
     mytypof' g0 _ = let (c, _)     = splitTyConApp $ typeOf (undefined :: c)
                         (tr1,g1)   = mytypof' g0 (undefined::a1)
                         (tr2,g2)   = mytypof' g1 (undefined::a2)
                         (tr3,g3)   = mytypof' g2 (undefined::a3)
                         (tr4,g4)   = mytypof' g3 (undefined::a4)
                     in  (mkTyConApp c [tr1, tr2, tr3, tr4], g4)

instance (Typeable c, MyTypeable' g0 g1 a1, MyTypeable' g1 g2 a2, MyTypeable' g2 g3 a3, MyTypeable' g3 g4 a4, MyTypeable' g4 g5 a5) =>
         MyTypeable' g0 g5 (TCon5 c a1 a2 a3 a4 a5) where
     mytypof' g0 _ = let (c, _)     = splitTyConApp $ typeOf (undefined :: c)
                         (tr1,g1)   = mytypof' g0 (undefined::a1)
                         (tr2,g2)   = mytypof' g1 (undefined::a2)
                         (tr3,g3)   = mytypof' g2 (undefined::a3)
                         (tr4,g4)   = mytypof' g3 (undefined::a4)
                         (tr5,g5)   = mytypof' g4 (undefined::a5)
                     in  (mkTyConApp c [tr1, tr2, tr3, tr4, tr5], g5)

instance (HIndex a g0 n, MyTypeable'' n g0 gout (TVar a)) =>
         MyTypeable' g0 gout (TVar a) where
     mytypof' = mytypof'' (undefined::n)


class MyTypeable'' n g0 gout classification | n g0 classification -> gout where
     mytypof'' :: n -> g0 -> classification -> (TypeRep, gout)

instance HLen g0 n1 => MyTypeable'' Z g0 (HCons a g0) (TVar a) where
     mytypof'' _ g0 _ = (mkany (undefined::S n1), HCons (undefined::a) g0)

instance Nat n => MyTypeable'' (S n) g0 g0 (TVar a) where
     mytypof'' _ g0 _ = (mkany (undefined::S n), g0)


mkany :: Nat n => n -> TypeRep
mkany n = mkTyConApp (mkTyCon ts) []
    where ts = "a" ++ show (nat n)

----------------------------------
-- Bits and pieces from HList.

-- Lookup the index of an item x in the list l
-- The index is 1-based. If not found, return 0
class Nat n => HIndex x l n | x l -> n

instance HIndex x HNil Z

instance (Nat n, TypeEq x a f, HIndex' f x (HCons a l) n)
   => HIndex x (HCons a l) n

class HIndex' f x l n | f x l -> n

instance HLen l n => HIndex' HTrue x l n
instance HIndex x l n => HIndex' HFalse x (HCons a l) n

class Nat n => HLen l n | l -> n
instance HLen HNil Z
instance HLen l n => HLen (HCons a l) (S n)

data Z
data S a

class Nat a where nat :: a -> Int
instance Nat Z where nat _ = 0
instance Nat a => Nat (S a) where nat _ = succ (nat (undefined::a))


data HTrue
data HFalse

data HNil = HNil
data HCons a b = HCons a b


class TypeEq' () x y b => TypeEq x y b | x y -> b
   where type'eq :: x -> y -> b
         type'eq _ _ = undefined::b
class TypeEq' q x y b | q x y -> b
class TypeEq'' q x y b | q x y -> b
instance TypeEq' () x y b => TypeEq x y b
instance b ~ HTrue => TypeEq' () x x b
instance TypeEq'' q x y b => TypeEq' q x y b
instance TypeEq'' () x y HFalse