module HTk.Canvasitems.CanvasTag (

  CanvasTag,

  TaggedCanvasItem(..),

  SearchSpec,
  allItems,
  aboveItem,
  belowItem,
  withTag,
  closest,
  enclosed,
  overlapping,

  createCanvasTag,

  addCanvasTag,
  removeCanvasTag,

   (&#&),
   (|#|),
   (^#),
   tagNot,

) where

import HTk.Kernel.Core
import HTk.Canvasitems.CanvasItem
import HTk.Canvasitems.CanvasItemAux (canvasitemMethods)
import Events.Destructible
import Events.Synchronized
import Util.Computation
import HTk.Kernel.Geometry (Position,Distance)


infixr 3 &#&
infixr 2 |#|
infixr 2 ^#


-- -----------------------------------------------------------------------
-- class TaggedCanvasItem
-- -----------------------------------------------------------------------

-- | A canvas item can have several tags (handlers for a set of canvas
-- items).
class CanvasItem w => TaggedCanvasItem w where
  -- Sets the tags for the specified canvas item.
  tags :: [CanvasTag] -> Config w
  tags cts item =
    mapM (\ct -> do
                   CanvasItemName name tid <-
                     getObjectName (toGUIObject ct)
                   cset item "tag" (show tid)) cts >> return item


-- -----------------------------------------------------------------------
-- tags
-- -----------------------------------------------------------------------

-- | The @CanvasTag@ datatype.
newtype CanvasTag = CanvasTag GUIOBJECT deriving Eq


-- -----------------------------------------------------------------------
-- configuration options
-- -----------------------------------------------------------------------

-- | Constructs a new canvas tag.
createCanvasTag :: Canvas
   -- ^ the parent canvas.
   -> [Config CanvasTag]
   -- ^ the list of configuration options for this canvas tag.
   -> IO CanvasTag
   -- ^ A canvas tag.
createCanvasTag cnv cnf =
  do
    wid <- createGUIObject (toGUIObject cnv) (CANVASITEM CANVASTAG [])
                           tagMethods
    configure (CanvasTag wid) cnf


-- -----------------------------------------------------------------------
-- instances
-- -----------------------------------------------------------------------

{-
instance Eq CanvasTag where
  w1 == w2 = (toGUIObject w1) == (toGUIObject w2)
-}

-- | Internal.
instance GUIObject CanvasTag where
  toGUIObject (CanvasTag wid) = wid
  cname _ = "CanvasTag"

-- | A canvas tag can be destroyed.
instance Destroyable CanvasTag where
  -- Destroys a canvas tag.
  destroy = destroy . toGUIObject

-- | A canvas tag is a canvas item (any canvas item is an instance of the
-- abstract @class CanvasItem@).
instance CanvasItem CanvasTag

-- | You can synchronize on a canvas tag.
instance Synchronized CanvasTag where
  -- Synchronizes on a canvas tag.
  synchronize w = synchronize (toGUIObject w)


-- -----------------------------------------------------------------------
-- commands
-- -----------------------------------------------------------------------

-- | Adds the canvas items identified by the @SearchSpec@ to
-- the tag.
addCanvasTag :: SearchSpec
   -- ^ the search specification.
   -> CanvasTag
   -- ^ the tag to add items to.
   -> IO ()
   -- ^ None.
addCanvasTag spec@(SearchSpec cmd) tag =
  do
    spec' <- cmd
    execMethod tag (\tnm -> tkAddTag tnm spec')

-- | Removes a canvas item from a canvas tag.
removeCanvasTag :: CanvasItem i => i
   -- ^ the item to remove from the tag.
   -> CanvasTag
   -- ^ the tag to remove the item from.
   -> IO ()
   -- ^ None.
removeCanvasTag item tag =
  do
    tnm <- getObjectName (toGUIObject tag)
    execMethod item (\cnm -> tkDTag cnm tnm)


-- -----------------------------------------------------------------------
-- Logical combinations of canvas tags
-- -----------------------------------------------------------------------

-- | Forms the conjunction of two canvas tags
(&#&) :: CanvasTag -> CanvasTag
   -> IO CanvasTag -- ^ new canvas tag corresponding to (t1&&t2)
(&#&)  = complexCanvasTag CanvasTagAnd

-- | Forms the disjunction of two canvas tags
(|#|) :: CanvasTag -> CanvasTag
   -> IO CanvasTag -- ^ new canvas tag corresponding to (t1||t2)
(|#|)  = complexCanvasTag CanvasTagOr

-- | Forms "either - or" of two canvas tags
(^#) :: CanvasTag -> CanvasTag
   -> IO CanvasTag
      -- ^ new canvas tag corresponding to (t1^t2)
      -- equals (!t1&&t2)||(t1&&!t2)
(^#)  = complexCanvasTag CanvasTagXOr

-- Forms the negation of a canvas tag
tagNot :: CanvasTag
   -> IO CanvasTag -- ^ new canvas tag corresponding to !t
tagNot t = complexCanvasTag (\ x _ -> CanvasTagNot x) t t

---
-- auxilliary function for &#&,|#|,^# and tagNot
complexCanvasTag :: (CanvasTagOrID -> CanvasTagOrID -> CanvasTagOrID)
                 -> CanvasTag -> CanvasTag -> IO CanvasTag
complexCanvasTag f t1 t2
  = do
     CanvasItemName oid tid1 <- getObjectName (toGUIObject t1)
     tid2 <- getCanvasTagOrID (toGUIObject t2)
     Just par <- getParentObject (toGUIObject t1)
     wid <- createGUIObject par
                            (CANVASITEM CANVASTAG [])
                            tagMethods
     setObjectName wid (CanvasItemName oid (f tid1 tid2))
     ct <- configure (CanvasTag wid) []
     return ct

-- -----------------------------------------------------------------------
--  SearchSpec
-- -----------------------------------------------------------------------

-- | The @SearchSpec@ datatype
-- (see 'CanvasTag.addCanvasTag').
data SearchSpec = SearchSpec (IO String)

-- | Adds all objects in the canvas.
allItems :: SearchSpec
   -- ^ A @SearchSpec@ object.
allItems = SearchSpec (return "all")

-- | Adds the item just above the given item in the display list.
aboveItem ::  CanvasItem item => item
   -- ^ the item below the item to add.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
aboveItem item = SearchSpec (do {
        tid <- getCanvasTagOrID (toGUIObject item);
        return ("above [" ++ declVarList tid ++ "; list " ++ show tid ++ "]")
        })

-- | Adds the item just below in the given item in the display list.
belowItem ::  CanvasItem item => item
   -- ^ the item above the item to add.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
belowItem item = SearchSpec (do {
        tid <- getCanvasTagOrID (toGUIObject item);
        return ("below [" ++ declVarList tid ++ "; list " ++ show tid ++ "]")
        })

-- | Adds the item(s) identified by the given handler (which can also be
-- another canvas tag).
withTag ::  CanvasItem item => item
   -- ^ the canvas item handler.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
withTag item = SearchSpec (do {
        tid <- getCanvasTagOrID (toGUIObject item);
        return ("withtag [" ++ declVarList tid ++ "; list " ++ show tid ++ "]")
        })

-- | Adds the item closest to the given position.
closest :: Position
   -- ^ the concerned position.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
closest pos@(x, y) =
  SearchSpec (return ("closest " ++ show x ++ " " ++ show y))

-- | Adds the items enclosed in the specified region.
enclosed :: Position
   -- ^ the upper left position of the region.
   -> Position
   -- ^ the lower right position of the region.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
enclosed pos1 pos2 =
   SearchSpec (return ("enclosed " ++ showPos pos1 ++ " " ++ showPos pos2))

-- | Adds the items overpalling the specified region.
overlapping :: Position
   -- ^ the upper left position of the region.
   -> Position
   -- ^ the lower right position of the region.
   -> SearchSpec
   -- ^ A @SearchSpec@ object.
overlapping pos1 pos2 =
   SearchSpec (return ("overlapping " ++ showPos pos1 ++ " " ++ showPos pos2))

showPos :: (Distance,Distance) -> String
showPos (x,y) = " "++show x++" "++show y++" "


getCanvasTagOrID :: GUIOBJECT -> IO CanvasTagOrID
getCanvasTagOrID wid =
  do
    nm <- getObjectName wid
    case nm of
      CanvasItemName name tid -> return tid
      _ -> error "CanvasTag (getCanvasTagOrID) : not a canvas item name"


-- -----------------------------------------------------------------------
-- methods
-- -----------------------------------------------------------------------

tagMethods = canvasitemMethods {createCmd = tkCreateTag}


-- -----------------------------------------------------------------------
-- unparsing of commands
-- -----------------------------------------------------------------------

tkCreateTag :: ObjectName -> ObjectKind -> ObjectName -> ObjectID ->
               [ConfigOption] -> TclScript
tkCreateTag _ (CANVASITEM CANVASTAG []) (CanvasItemName name tid) oid _ =
      declVar tid ++ [" set " ++ vname ++ " t" ++ show oid]
   where vname = (drop 1 (show tid))

tkAddTag :: ObjectName -> String -> TclScript
tkAddTag (CanvasItemName name tid) spec =
   declVar tid ++ [show name ++ " addtag " ++ show tid ++ " " ++ spec]


tkDTag :: ObjectName -> ObjectName -> TclScript
tkDTag (CanvasItemName name cid) (CanvasItemName _ tid) =
   declVar tid ++ declVar cid ++
      [show name ++ " dtag " ++ show cid ++ " " ++ show tid]