{-# LANGUAGE ExistentialQuantification, FunctionalDependencies
  , KindSignatures, MultiParamTypeClasses
  , RankNTypes, ScopedTypeVariables #-}

{- | Helper functions to creates generic widgets.

The /parent type/, which is refered thoughout the module
documentation, could also be called the enclosing type. For example
given:

data Foo = Foo Bar Boo

then the parent type of Bar and Boo will be Foo.

-}
module Graphics.UI.SybWidget.SybOuter
    ( OuterWidget(..), FullPart(..)
    , mkGetterSetter, mkFullSpliter
    , isSingleConstructor, mkSpliterSingleConstr
    -- * Spliter
    , Spliter(..)
    , mapParts, mapPartsM, mapPartsMDelay
    , spliterToList, zipSpliterWithList
    -- * Constructor-value map
    , mkConstrValMap, updateConstrValMap, lookupValue, alwaysValue
    , ConstrValMap
    -- * Creating numeric widgets
    , numericGetSet, sybRead, sybShow
    -- * Type label
    , typeLabel
    )
where

import Maybe
import Data.RefMonad
import qualified Data.Map as M

import Graphics.UI.SybWidget.MySYB
import Graphics.UI.SybWidget.InstanceCreator
import Graphics.UI.SybWidget.PriLabel

class OuterWidget outer where
    updateLabel :: (PriLabel -> PriLabel) -> outer a -> outer a

-- |Widget with getter and setter.
data FullPart wid parent b = FullPart 
    { partWidget :: wid b
    , partGetter :: parent -> b           -- ^Extracts this parts value from the parent type
    , partSetter :: parent -> b -> parent -- ^Sets this value on a parent type
    }

-- |Has this type exactly one constructor? This function is
-- undefined for Int, Float, Double and Char.
isSingleConstructor :: (Data ctx a) => Proxy ctx -> a -> Bool
isSingleConstructor ctx x = length (constructors ctx x) == 1

-- |Constructs a Spliter using the constructor in the input type
-- ('y'). If 'y' has field labels, the individual parts are updated
-- with the field label names.
mkSpliterSingleConstr
    :: forall (ctx :: * -> *) a outer.
       (Data ctx a, OuterWidget outer) =>
       Proxy ctx
    -> (forall a1. (Data ctx a1) => a1 -> outer a1)
    -> a -> Spliter outer a a
mkSpliterSingleConstr ctx childToOuter y = spliter 
  where
    spliter = zipSpliterWithList updateLabel' fieldLabels foldType
    foldType :: Spliter outer a a
    foldType = gfoldl ctx k z y where
        k c x = Part (childToOuter x) c
        z :: c -> Spliter outer a c
        z c = Constructor c
    updateLabel' lbl p = updateLabel (bestLabel (fieldNameLabel lbl)) p
    fieldLabels        = constrFields $ toConstr ctx y

{-
type UpdateLabel part = forall a. (PriLabel -> PriLabel) -> part a -> part a

relabel :: UpdateLabel part -> [String]
        -> Spliter part m a -> Spliter part m a
relabel updateLabel lbls = zipSpliterWithList updateLabel' lbls
    where updateLabel' lbl = updateLabel (bestLabel (PriLabel FieldName lbl))

relabelWithFieldNames :: Data ctx m =>
                         Proxy ctx -> UpdateLabel part -> m
                      -> Spliter part m a -> Spliter part m a
relabelWithFieldNames ctx updateLabel x = zipSpliterWithList updateLabel' fieldLabels
    where updateLabel' lbl = updateLabel (bestLabel (PriLabel FieldName lbl))
          fieldLabels      = constrFields $ toConstr ctx x
-}


-- |Creates a Spliter containing 'FullPart'-s.
mkFullSpliter
    :: forall ctx parent part. (Data ctx parent) => 
       Proxy ctx -> Spliter part parent parent
    -> Spliter (FullPart part parent) parent parent
mkFullSpliter ctx = fst . mapPartsAcc helper 0 where
    helper depth bWid = (FullPart bWid (getFieldFun ctx depth) (setFieldFun ctx depth), depth + 1)

-- ****************** Get/Set actions **************

-- |Creates getter and setter command for a Spliter. That is, it will
-- create two function which sets/gets all the parts of the Spliter.
mkGetterSetter :: forall ctx wid getM setM parent.
                  (Monad getM, Monad setM, Data ctx parent) =>
                  Proxy ctx
               -> (forall a. wid a -> getM a)
               -> (forall a. wid a -> a -> setM ())
               -> Spliter wid parent parent
               -> (getM parent, parent -> setM ())
mkGetterSetter ctx getWidValue setWidValue = helper . mkFullSpliter ctx
    where 
      helper :: Spliter (FullPart wid parent) parent b -> (getM b, parent -> setM ())
      helper (Constructor c) = (return c, \_ -> return ())
      helper (Part (FullPart innerWid getter _) towardsConstr) =
          let (getTC, setTC) = helper towardsConstr
              getValue = do getX <- getWidValue innerWid
                            getTC' <- getTC
                            return (getTC' getX)
              setValue parent = do setTC parent 
                                   setWidValue innerWid (getter parent)
          in (getValue, setValue)

-- ****************** Spliter *********************

{- | The Splitter type contains the splitting of a type into a
Constructor and Parts.

The Spliter structure is reverse, in the sense that a type C a b c,
where C is a constructor and a, b and c is values to the constructor,
will be represented as (Splitter type in brackets):

   (Part (part c)                         { C a b c }
         (Part (part b)                   { c -> C a b c }
               (Part (part a)             { b -> c -> C a b c }
                     (Constructor C))))   { a -> b -> c -> C a b c }
-}
data Spliter part parent a
    = Constructor a
    | forall b. (Typeable b) => Part (part b) (Spliter part  parent (b -> a))

-- |Maps each part in a Spliter type.
mapParts :: forall (partA :: * -> *) (partB :: * -> *) parent. 
           (forall q. (Typeable q) => partA q -> partB q)
         -> Spliter partA parent parent
         -> Spliter partB parent parent
mapParts f = fst . mapPartsAcc (\_ part -> (f part, ())) ()

-- |Accumulator version of mapParts.
mapPartsAcc :: forall (partA :: * -> *) (partB :: * -> *) parent acc. 
               (forall q. (Typeable q) => acc -> partA q -> (partB q, acc))
            -> acc
            -> Spliter partA parent parent
            -> (Spliter partB parent parent, acc)
mapPartsAcc f initialAcc = helper where
    helper :: Spliter partA parent q -> (Spliter partB parent q, acc)
    helper (Constructor c) = (Constructor c, initialAcc)
    helper (Part x rest)
        = let (newRest, restAcc) = helper rest
              (part, acc) = f restAcc x
          in (Part part newRest, acc)

-- |Monadic version of mapParts. The mapping is done deep first.  It
-- is done deep first as we will then process the elements in the
-- field order. E.g. if the spliter is based on the:
--
--    data Foo = Foo Int Double
--
-- then the Int will be processed first, then the Double.
mapPartsM :: forall (partA :: * -> *) (partB :: * -> *) parent m.
             (Monad m) =>
             (forall q. (Typeable q) => partA q -> m (partB q))
          -> Spliter partA parent parent
          -> m (Spliter partB parent parent)
mapPartsM f = helper where
    helper :: Spliter partA parent q -> m (Spliter partB parent q)
    helper (Constructor c) = return $ Constructor c
    helper (Part a rest)
        = do rest'   <- helper rest
             newPart <- f a
             return $ Part newPart rest'

data Delay partA partB a
    = First   (partB a)
    | Delayed (partA a)

-- |Like mapPartsM, except that processing of certain parts can be delayed.
-- The first parameter decides which parts processing should be delayed.
-- 
-- This is usefull when fine grained control of execution order is desired.
mapPartsMDelay :: forall (partA :: * -> *) (partB :: * -> *) parent m.
                  (Monad m) =>
                 (forall q. (Typeable q) => partA q -> Bool)
               -> (forall q. (Typeable q) => partA q -> m (partB q))
               -> Spliter partA parent parent
               -> m (Spliter partB parent parent)
mapPartsMDelay delay f spliter = mapPartsM secondF =<< mapPartsM firstF spliter where
    firstF :: forall y. (Typeable y) => partA y -> m (Delay partA partB y)
    firstF part  = case delay part of
                     True  -> return $ Delayed part
                     False -> do part' <- f part
                                 return $ First part'
    secondF :: forall y. (Typeable y) => Delay partA partB y -> m (partB y)
    secondF part = do case part of
                        Delayed p -> f p
                        First   p -> return $ p

-- |Transforms a spiltter to a list. The list will follow the constructor fields order.
spliterToList :: (forall c. Typeable c => part c -> abstractPart)
              -- ^Function to transform each part in the spliter to a list element. Note
              -- that the parts have kind * -> *, but the output must be of kind *.
              -> Spliter part a b -> [abstractPart]
spliterToList _ (Constructor _)  = []
spliterToList f (Part part rest) = (spliterToList f rest) ++ [f part]

-- |Zips a list with a spliter using 'f'. The list members are zipped
-- in the order of the constructor fields. If not enough list members
-- are present the rest of the spilter is un-mapped.
zipSpliterWithList :: forall a m n part.
                      (forall q. (Typeable q) => a -> part q -> part q)
                   -> [a]
                   -> Spliter part m n -> Spliter part m n
zipSpliterWithList f xs spliter = fst $ helper xs spliter where
    helper :: forall b c. [a] -> Spliter part b c -> (Spliter part b c, [a])
    helper [] spliter'        = (spliter', [])
    helper ys (Constructor c) = (Constructor c, ys)
    helper ys (Part p rest) =
        case helper ys rest of
          (rest', [])     -> (Part p rest', [])
          (rest', (z:zs)) -> (Part (f z p) rest', zs)

-- ******************** Constr/value map *************

data ConstrValMap ref ctx a = ConstrValMap
    { pickConstrValMap :: ref (M.Map String a)
    , pickCtx          :: Proxy ctx
    }

-- |A map from from constructors to values. Used as memory when
-- creating multi-constructor widgtes. This way each time the
-- constructor is changed, we can look in the map to see if we had a
-- privious value for the new constructor.
mkConstrValMap :: (Data ctx a, RefMonad m ref) => Proxy ctx -> a -> m (ConstrValMap ref ctx a)
mkConstrValMap ctx x =
    do mapVar <- newRef (M.singleton (showConstr $ toConstr ctx x) x)
       return $ ConstrValMap mapVar ctx

-- |Updates the map with a new value.
updateConstrValMap :: (Data ctx a, RefMonad m ref) => ConstrValMap ref ctx a -> a -> m ()
updateConstrValMap valueMemory x =
    do let con = showConstr $ toConstr (pickCtx valueMemory) x
       modifyRef (pickConstrValMap valueMemory) (M.insert con x)
       return ()

-- |Look in the map to see if we have a value for the constructor.
lookupValue :: (Data ctx a, RefMonad m ref) => ConstrValMap ref ctx a -> Constr -> m (Maybe a)
lookupValue valueMemory constr =
    do cvMap <- readRef (pickConstrValMap valueMemory)
       return $ M.lookup (showConstr constr) cvMap

-- |Like 'lookupValue', except if it cannot find a value in the map
-- one will be created using 'createInstance'.
alwaysValue :: (Data ctx a, RefMonad m ref) => 
               ConstrValMap ref ctx a -> Constr -> m a
alwaysValue valueMemory constr =
    do maybeVal <- lookupValue valueMemory constr
       return $ case maybeVal of
                  Nothing -> fromJust $ instanceFromConstr (pickCtx valueMemory) constr
                  Just y  -> y

-- ************** Numeric helper functions ***************

{- |

Returns a getter and setter command for numeric types. The getter and
setter are applicable when numeric types are represented using
String. The function uses 'sybRead' and 'sybShow' to parse and
construct strings. In this way we avoid dependency on Show and Read
type classes.

It is generally a good idea to avoid dependencies. And it can be
essential to avoid dependency on Show and Read, if we want to
implement generic widgets for functions, as we cannot define Show and
Read for those.

It could be argued that Int, Double, Float, .. all are instances of
Read and Show, and it therefore unneccesary to avoid using these
classes. However, SYB will force any dependencies for these types on
all types for which we want generic functionality. SYB does that as we
make one piece of code handling all integer-like types, and one
handling all real-numbered types. Thus, we only have access to the
classes that are in the generic class's context.

The getter uses the last legitimate value when the input string is
non-parseable.
-}
numericGetSet :: (Data ctx a, RefMonad m ref) => Proxy ctx -> a
              -> m (String -> m a, a -> m String)
numericGetSet ctx initial =
    do lastVal <- newRef initial
       let getter textVal =
               do case sybRead ctx initial textVal of
                    Nothing -> readRef lastVal
                    Just x  -> do writeRef lastVal x
                                  return x
           setter x = do writeRef lastVal x
                         return $ sybShow ctx x
       return (getter, setter)

-- |Avoid dependency on the Read class, by using SYB to read a
-- value. It has _only_ been tested for numeric types.
-- 
-- See also 'numericGetSet'.
sybRead :: Data ctx a => Proxy ctx -> a -> String -> Maybe a
sybRead ctx typeProxy textVal =
    maybeConstr >>= (Just . fromConstr ctx) where
        maybeConstr = readConstr (dataTypeOf ctx typeProxy) textVal

-- |Avoid dependency on the Show class, by using SYB to show a
-- value. It has _only_ been tested for numeric types.
--
-- See also 'numericGetSet'.
sybShow :: Data ctx a => Proxy ctx -> a -> String
sybShow ctx x = showConstr $ toConstr ctx x

-- ************** Generating a label for a type *************

-- |Creates a default label for a type.
typeLabel :: Data ctx a => Proxy ctx -> a -> PriLabel
typeLabel ctx x = badConstrLabel $ dataTypeName $ dataTypeOf ctx x