module Graphics.UI.WX.Attributes
    (
    
      Attr, Prop((:=),(:~),(::=),(::~)), ReadAttr, WriteAttr, CreateAttr
    , get, set, swap
    , mapAttr, mapAttrW
    
    
    , newAttr, readAttr, writeAttr, nullAttr, constAttr, makeAttr
    
    
    , attrName, propName, containsProperty
    
    
    , reflectiveAttr, createAttr, withProperty, findProperty
    , withStyleProperty, withStylePropertyNot
    
    , PropValue(..)
    , filterProperty 
    
    , castAttr, castProp, castProps
    ) where
import Graphics.UI.WX.Types
import Data.Dynamic
infixr 0 :=,:~,::=,::~
data Prop w     = forall a. Attr w a := a         
                | forall a. Attr w a :~ (a -> a)  
                | forall a. Attr w a ::= (w -> a) 
                | forall a. Attr w a ::~ (w -> a -> a) 
instance Show a => Show (Prop a) where
  show ((Attr string _ _ _ _) :=  _) = "Attr \"" ++ string ++ "\" _ _ _ _ :=  _"
  show ((Attr string _ _ _ _) :~  _) = "Attr \"" ++ string ++ "\" _ _ _ _ :~  _"
  show ((Attr string _ _ _ _) ::= _) = "Attr \"" ++ string ++ "\" _ _ _ _ ::= _"
  show ((Attr string _ _ _ _) ::~ _) = "Attr \"" ++ string ++ "\" _ _ _ _ ::~ _"
type CreateAttr w a = Attr w a
type ReadAttr w a = Attr w a
type WriteAttr w a = Attr w a
data Attr w a   = Attr String (Maybe (a -> Dynamic, Dynamic -> Maybe a))  
                              (w -> IO a) (w -> a -> IO ())               
                              (w -> (a -> a) -> IO a)                     
instance Show a => Show (Attr w a) where
  show (Attr string _ _ _ _) =
    "Attr \"" ++ string ++ "\" _ _ _ _"
castAttr :: (v -> w) -> Attr w a -> Attr v a
castAttr coerce (Attr name mbdyn getter setter upd)
  = Attr name mbdyn (\v -> getter (coerce v)) (\v x -> (setter (coerce v) x))
                    (\v f -> upd (coerce v) f) 
castProp :: (v -> w) -> Prop w -> Prop v
castProp coerce prop
  = case prop of
      (attr := x)   -> (castAttr coerce attr) := x
      (attr :~ f)   -> (castAttr coerce attr) :~ f
      (attr ::= f)  -> (castAttr coerce attr) ::= (\v -> f (coerce v))
      (attr ::~ f)  -> (castAttr coerce attr) ::~ (\v x -> f (coerce v) x)
castProps :: (v -> w) -> [Prop w] -> [Prop v]
castProps coerce props
  = map (castProp coerce) props
reflectiveAttr :: Typeable a => String -> (w -> IO a) -> (w -> a -> IO ()) -> Attr w a
reflectiveAttr name getter setter
  = Attr name (Just (toDyn, fromDynamic)) getter setter updater 
  where
    updater w f   = do x <- getter w; setter w (f x); return x
createAttr :: Typeable a => String -> (w -> IO a) -> (w -> a -> IO ()) -> CreateAttr w a
createAttr name getter setter
  = reflectiveAttr name getter setter
makeAttr :: String -> (w -> IO a) -> (w -> a -> IO ()) -> (w -> (a -> a) -> IO a) -> Attr w a
makeAttr name getter setter updater
  = Attr name Nothing getter setter updater 
newAttr :: String -> (w -> IO a) -> (w -> a -> IO ()) -> Attr w a
newAttr name getter setter
  = makeAttr name getter setter updater 
  where
    updater w f   = do x <- getter w; setter w (f x); return x
readAttr :: String -> (w -> IO a) -> ReadAttr w a
readAttr name getter
  = newAttr name getter (\w x -> ioError (userError ("attribute '" ++ name ++ "' is read-only.")))
writeAttr :: String -> (w -> a -> IO ()) -> WriteAttr w a
writeAttr name setter
  = newAttr name (\w -> ioError (userError ("attribute '" ++ name ++ "' is write-only."))) setter
nullAttr :: String -> WriteAttr w a
nullAttr name
  = writeAttr name (\w x -> return ())
constAttr :: Typeable a => String -> a -> Attr w a
constAttr name x
  = newAttr name (\w -> return x) (\w x -> return ())
mapAttr :: (a -> b) -> (a -> b -> a) -> Attr w a -> Attr w b
mapAttr get set (Attr name reflect getter setter updater)
    = Attr name Nothing
                (\w   -> do a <- getter w; return (get a))
                (\w b -> do a <- getter w; setter w (set a b))
                (\w f -> do a <- updater w (\a -> set a (f (get a))); return (get a)) 
mapAttrW :: (v -> w) -> Attr w a -> Attr v a
mapAttrW f attr
  = castAttr f attr
get :: w -> Attr w a -> IO a
get w (Attr name reflect getter setter updater)
  = getter w
set :: w -> [Prop w] -> IO ()
set w props
  = mapM_ setprop props
  where
    setprop ((Attr name reflect getter setter updater) := x)
      = setter w x
    setprop ((Attr name reflect getter setter updater) :~ f)
      = do updater w f; return ()
    setprop ((Attr name reflect getter setter updater) ::= f)
      = setter w (f w)
    setprop ((Attr name reflect getter setter updater) ::~ f)
      = do updater w (f w); return ()
swap :: w -> Attr w a -> a -> IO a
swap w (Attr name reflect getter setter updater) x
  = updater w (const x)
attrName :: Attr w a -> String
attrName (Attr name _ _ _ _)
  = name
propName :: Prop w -> String
propName (attr := x)    = attrName attr
propName (attr :~ f)    = attrName attr
propName (attr ::= f)   = attrName attr
propName (attr ::~ f)   = attrName attr
containsProperty :: Attr w a -> [Prop w] -> Bool
containsProperty attr props
  = containsPropName (attrName attr) props
containsPropName :: String -> [Prop w] -> Bool
containsPropName name props
  = any (\p -> propName p == name) props
data PropValue a  = PropValue  a
                  | PropModify (a -> a)
                  | PropNone
instance Show a => Show (PropValue a) where
  show (PropValue x)  = "PropValue " ++ show x
  show (PropModify f) = "PropModify"
  show (PropNone)     = "PropNone"
filterProperty :: Typeable a => Attr w a -> [Prop w] -> (PropValue a, [Prop w])
filterProperty (Attr name _ _ _ _) props
  = walk [] PropNone props
  where
    
    walk :: Typeable a => [Prop w] -> PropValue a -> [Prop w] -> (PropValue a, [Prop w])
    walk acc res props
      = case props of
          
          (((Attr attr (Just (todyn,fromdyn)) _ _ _) := x):rest)  | name == attr
              -> case fromDynamic (todyn x) of
                   Just x  -> walk acc (PropValue x) rest
                   Nothing -> walk acc res props
                   
          
          (((Attr attr (Just (todyn,fromdyn)) _ _ _) :~ f):rest)  | name == attr
              -> let dynf x = case fromdyn (toDyn x) of
                                Just xx -> case fromDynamic (todyn (f xx)) of
                                             Just y  -> y
                                             Nothing -> x  
                                Nothing -> x 
                 in case res of
                      PropValue x  -> walk acc (PropValue (dynf x)) rest
                      PropModify g -> walk acc (PropModify (dynf . g)) rest
                      PropNone     -> walk acc (PropModify dynf) rest
          
          (((Attr attr _ _ _ _) := _):rest)   | name == attr  -> stop
          (((Attr attr _ _ _ _) :~ _):rest)   | name == attr  -> stop
          (((Attr attr _ _ _ _) ::= _):rest)  | name == attr  -> stop
          (((Attr attr _ _ _ _) ::~ _):rest)  | name == attr  -> stop
          
          (prop:rest)
              -> walk (prop:acc) res rest
          []  -> stop
       where
        stop  = (res, reverse acc ++ props)
  
               
withProperty :: Typeable a => Attr w a -> a -> (a -> [Prop w] -> b) -> [Prop w] -> b
withProperty attr def cont props
  = case filterProperty attr props of
      (PropValue x, ps)  -> cont x ps
      (PropModify f, ps) -> cont (f def) ps
      (PropNone, ps)     -> cont def ps
findProperty :: Typeable a => Attr w a -> a -> [Prop w] -> Maybe (a,[Prop w])
findProperty attr def props
  = case filterProperty attr props of
      (PropValue x, ps)  -> Just (x,ps)
      (PropModify f, ps) -> Just (f def,ps)
      (PropNone, ps)     -> Nothing
withStyleProperty :: Attr w Bool -> Style -> ([Prop w] -> Style -> a) -> [Prop w] -> Style -> a
withStyleProperty prop flag 
  = withStylePropertyEx prop (bitsSet flag) (\isSet style -> if isSet then (style .+. flag) else (style .-. flag)) 
withStylePropertyNot :: Attr w Bool -> Style -> ([Prop w] -> Style -> a) -> [Prop w] -> Style -> a
withStylePropertyNot prop flag 
  = withStylePropertyEx prop (not . bitsSet flag) (\isSet style -> if isSet then (style .-. flag) else (style .+. flag)) 
withStylePropertyEx :: Attr w Bool -> (Style -> Bool) -> (Bool -> Style -> Style) -> ([Prop w] -> Style -> a) -> [Prop w] -> Style -> a
withStylePropertyEx prop def transform cont props style
  = case filterProperty prop props of
      (PropValue x, ps)  -> cont ps (transform x style) 
      (PropModify f, ps) -> cont ps (transform (f (def style)) style)
      (PropNone, ps)     -> cont ps style