    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}

    {- |
        /RECOMMENDATION:/ Use "Data.Generics.Uniplate.Data" instead - it usually performs
        faster (sometimes significantly so) and requires no special instance declarations.

        This module supplies a method for writing 'Uniplate' / 'Biplate' instances. One
        instance declaration is required for each data type you wish to work with. The
        instances can be generated using Derive: <http://community.haskell.org/~ndm/derive/>.

        To take an example:

        > data Expr = Var Int | Neg Expr | Add Expr Expr
        >             deriving Typeable
        >
        > instance (Typeable a, Uniplate a) => PlateAll Expr a where
        >     plateAll (Var x  ) = plate Var |+ x
        >     plateAll (Neg x  ) = plate Neg |+ x
        >     plateAll (Add x y) = plate Add |+ x |+ y
    -}

    module Data.Generics.Uniplate.Typeable(
        module Data.Generics.Uniplate.Operations,
        module Data.Typeable,
        -- * The Class
        PlateAll(..),
        -- * The Combinators
        plate, (|+), (|-), plateProject
        ) where

    import Control.Arrow
    import Data.Generics.Uniplate.Operations
    import Data.Generics.Uniplate.Internal.Utils
    import Data.Generics.Str
    import Data.Typeable
    import Data.Ratio

    instance (Typeable a, Typeable b, Uniplate b, PlateAll a b) => Biplate a b where
        biplate = plateMore

    instance PlateAll a a => Uniplate a where
        uniplate = plateAll

    type Type from to = (Str to, Str to -> from)

    plateMore :: (Typeable from, Typeable to, PlateAll from to) => from -> Type from to
    plateMore x = res
        where
            res = case asTypeOf (cast x) (Just $ strType $ fst res) of
                      Nothing -> plateAll x
                      Just y -> (One y, \(One y) -> unsafeCoerce y)

    -- | This class should be defined for each data type of interest.
    class PlateAll from to where
        -- | This method should be defined using 'plate' and '|+', '|-'.
        plateAll :: from -> Type from to

    -- | The main combinator used to start the chain.
    plate :: from -> Type from to
    plate x = (Zero, \_ -> x)

    -- | The field to the right may contain the target.
    (|+) :: (Typeable item, Typeable to, PlateAll item to) => Type (item -> from) to -> item -> Type from to
    (|+) (xs,x_) y = case plateMore y of
                          (ys,y_) -> (Two xs ys,\(Two xs ys) -> x_ xs (y_ ys))

    -- | The field to the right /does not/ contain the target.
    --   This can be used as either an optimisation, or more commonly for excluding
    --   primitives such as Int.
    (|-) :: Type (item -> from) to -> item -> Type from to
    (|-) (xs,x_) y = (xs,\xs -> x_ xs y)

    -- | Write an instance in terms of a projection/injection pair. Usually used to define instances
    --   for abstract containers such as Map:
    --
    -- > instance (Ord a, Typeable a, PlateAll a c, Typeable b, PlateAll b c,
    -- >          Typeable c, PlateAll c c) => PlateAll (Map.Map a b) c where
    -- >     plateAll = plateProject Map.toList Map.fromList
    plateProject :: (Typeable item, Typeable to, PlateAll item to) => (from -> item) -> (item -> from) -> from -> Type from to
    plateProject into outof = second (outof . ) . plateAll . into

    -- * Instances

    -- ** Primitive Types

    instance PlateAll Int to where plateAll x = plate x
    instance PlateAll Bool to where plateAll x = plate x
    instance PlateAll Char to where plateAll x = plate x
    instance PlateAll Integer to where plateAll x = plate x
    instance PlateAll Double to where plateAll x = plate x
    instance PlateAll Float to where plateAll x = plate x
    instance PlateAll () to where plateAll x = plate x

    -- ** Container Types

    instance (PlateAll from to, Typeable from, Typeable to, Uniplate to) => PlateAll [from] to where
        plateAll []     = plate []
        plateAll (x:xs) = plate (:) |+ x |+ xs

    instance (PlateAll from to, Typeable from, Typeable to, Uniplate to) => PlateAll (Maybe from) to where
        plateAll Nothing  = plate Nothing
        plateAll (Just x) = plate Just |+ x

    instance (PlateAll a to, Typeable a, PlateAll b to, Typeable b, Typeable to, Uniplate to) =>
             PlateAll (Either a b) to where
        plateAll (Left  x) = plate Left  |+ x
        plateAll (Right x) = plate Right |+ x

    instance (PlateAll a to, Typeable a
             ,PlateAll b to, Typeable b
             ,Typeable to, Uniplate to) =>
             PlateAll (a,b) to where
        plateAll (a,b) = plate (,) |+ a |+ b

    instance (PlateAll a to, Typeable a
             ,PlateAll b to, Typeable b
             ,PlateAll c to, Typeable c
             ,Typeable to, Uniplate to) =>
             PlateAll (a,b,c) to where
        plateAll (a,b,c) = plate (,,) |+ a |+ b |+ c

    instance (PlateAll a to, Typeable a
             ,PlateAll b to, Typeable b
             ,PlateAll c to, Typeable c
             ,PlateAll d to, Typeable d
             ,Typeable to, Uniplate to) =>
             PlateAll (a,b,c,d) to where
        plateAll (a,b,c,d) = plate (,,,) |+ a |+ b |+ c |+ d

    instance (PlateAll a to, Typeable a
             ,PlateAll b to, Typeable b
             ,PlateAll c to, Typeable c
             ,PlateAll d to, Typeable d
             ,PlateAll e to, Typeable e
             ,Typeable to, Uniplate to) =>
             PlateAll (a,b,c,d,e) to where
        plateAll (a,b,c,d,e) = plate (,,,,) |+ a |+ b |+ c |+ d |+ e

    instance (Integral a, PlateAll a to, Typeable a, Typeable to, Uniplate to) => PlateAll (Ratio a) to where
        plateAll = plateProject (\x -> (numerator x, denominator x)) (uncurry (%))