{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | HTk\'s /listbox widget/ .
-- A scrollable widget that displays a set of text lines with selection
-- functionality.
module HTk.Widgets.ListBox (

  ListBox,
  newListBox,

  SelectMode(..),
  selectMode,
  getSelectMode,

  activateElem,
  selectionAnchor,

  ListBoxElem(..),
  elemNotFound

) where

import Control.Exception
import Data.List

import HTk.Kernel.Core
import HTk.Kernel.BaseClasses(Widget)
import HTk.Kernel.Configuration
import HTk.Widgets.ScrollBar
import HTk.Components.Index
import HTk.Components.Selection
import Data.Char(isSpace)
import HTk.Devices.XSelection
import Events.Synchronized
import Util.Computation
import Events.Destructible
import HTk.Kernel.Packer
import HTk.Kernel.Tooltip
import HTk.Tix.Subwidget

-- -----------------------------------------------------------------------
-- type
-- -----------------------------------------------------------------------

-- | The @ListBox@ datatype - parametrised over the type of
-- the list elements.
newtype ListBox a = ListBox GUIOBJECT deriving Eq


-- -----------------------------------------------------------------------
-- creation
-- -----------------------------------------------------------------------

-- | Constructs a new listbox widget and returns a handler.
newListBox :: (Container par, GUIValue a) => par
   -- ^ the parent widget, which has to be a container widget
   -- (an instance of @class Container@).
   ->
   [Config (ListBox a)]
   -- ^ the list of configuration options for this listbox
   -- widget.
   ->
   IO (ListBox a)
   -- ^ A listbox widget.
newListBox par cnf =
  do
    w <- createGUIObject (toGUIObject par) (LISTBOX []) lboxMethods
    configure (ListBox w) cnf


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

-- | Internal.
instance GUIObject (ListBox a) where
   toGUIObject (ListBox w) = w
   cname _ = "ListBox"

-- | A listbox widget can be destroyed.
instance Destroyable (ListBox a) where
   -- Destroys a listbox widget
   destroy = destroy . toGUIObject

-- | A listbox widget has standard widget properties
-- (concerning focus, cursor).
instance Widget (ListBox a)

-- | You can synchronize on a listbox object (in JAVA style).
instance Synchronized (ListBox a) where
  -- Synchronizes on a listbox object.
  synchronize = synchronize . toGUIObject

-- | A listbox widget has a configureable border.
instance HasBorder (ListBox a)

-- | A listbox widget has a foreground and background colour.
instance HasColour (ListBox a) where
  legalColourID = hasForeGroundColour

-- | A listbox is a stateful widget - it can be enabled or disabled.
instance HasEnable (ListBox a)

-- | You can specify the font of a listbox.
instance HasFont (ListBox a)

instance HasGrid (ListBox a)

-- | A listbox is a scrollable widget.
instance HasScroller (ListBox a)

-- | You can specify the size of a listbox.
instance HasSize (ListBox a)

-- | The value of a listbox is the list of the displayed objects (these
-- are instances of class @GUIValue@ and therefore instances
-- of class @Show@).
instance (GUIValue a, GUIValue [a]) => HasValue (ListBox a) [a] where
  value vals w =
    execMethod w (\nm -> tkInsert nm 0 (map toGUIValue vals)) >> return w
  -- Gets the list of displayed objects.
  getValue w = evalMethod w (\nm -> tkGet nm)

-- | A listbox can have a tooltip (only displayed if you are using tixwish).
instance HasTooltip (ListBox a)

-- | A listbox widget has an X selection.
instance HasXSelection (ListBox a)

-- --------------------------------------------------------
-- A list box widget can be a subwidget, e.g. in a combo box
-- --------------------------------------------------------

instance GUIValue a => CanBeSubwidget (ListBox a) where
  createAsSubwidget megaWidget
     = do lb <- createSubwidget (LISTBOX []) lboxMethods megaWidget
          return (ListBox lb)

-- -----------------------------------------------------------------------
-- ListBox configurations
-- -----------------------------------------------------------------------

-- | Sets the select mode of a listbox.
selectMode :: GUIValue a => SelectMode
   -- ^ the select mode to set.
   -> ListBox a
   -- ^ the concerned listbox.
   -> IO (ListBox a)
   -- ^ The concerned listbox.
selectMode sm lbox = cset lbox "selectmode" sm

-- | Gets the set select mode from a listbox.
getSelectMode :: GUIValue a => (ListBox a)
   -- ^ the concerned listbox.
   -> IO SelectMode
   -- ^ The current select mode.
getSelectMode lbox = cget lbox "selectmode"


-- -----------------------------------------------------------------------
-- BBox
-- -----------------------------------------------------------------------

-- | You can find out the bounding box of a list box element.
instance HasIndex (ListBox a) i Int => HasBBox (ListBox a) i  where
  -- Returns the bounding box of the element at the specified index.
  bbox w i =
    do
      binx <- getBaseIndex w i
      ans <- try (evalMethod w (\nm -> [tkBBox nm (binx::Int)]))
      case ans of Left (e :: SomeException)  -> return Nothing
                  Right v -> return (Just v)
    where tkBBox nm i = show nm ++ " bbox " ++ show i


-- -----------------------------------------------------------------------
-- Index
-- -----------------------------------------------------------------------

-- | The @ListBoxElem@ datatype.
data Eq a => ListBoxElem a = ListBoxElem a deriving Eq


-- -----------------------------------------------------------------------
-- Has Index
-- -----------------------------------------------------------------------

-- | An integer value is a valid index position inside a listbox widget.
instance HasIndex (ListBox a) Int Int where
  getBaseIndex lb i = return i

-- | The @EndOfText@ index is a valid index position inside a
-- listbox widget.
instance HasIndex (ListBox a) EndOfText Int where
  getBaseIndex lb _ = getIndexNumber lb "end"

-- | A position in pixels is a valid index position inside an editor widget.
instance HasIndex (ListBox a) Pixels Int where
  getBaseIndex lb p = getIndexNumber lb (show p)

-- | A listbox element is a valid index position inside an editor widget.
instance (Eq a,GUIValue a) => HasIndex (ListBox [a])
                                        (ListBoxElem a) Int where
  getBaseIndex lb (ListBoxElem val) =
    do
      kind <- getObjectKind (toGUIObject lb)
      case kind of
        LISTBOX elems ->
          case findIndex (\e -> show e == val') elems of
            Nothing  -> raise elemNotFound
            Just i -> return i
    where val' = show (toGUIValue val)

-- | Internal.
instance (Eq a, GUIValue a, GUIValue [a]) =>
         HasIndex (ListBox a) Int (ListBoxElem a) where
  getBaseIndex lb i =
    synchronize lb
      (do
         elems <- getValue lb
         (if (i >= 0) && (i <= (length elems - 1)) then
            return (ListBoxElem (elems !! i))
          else
            raise elemNotFound))

getIndexNumber :: ListBox a -> String -> IO Int
getIndexNumber lb i =
  evalMethod lb (\lnm -> [show lnm ++ " index "  ++ i])


-- -----------------------------------------------------------------------
-- ListBox selection
-- -----------------------------------------------------------------------

-- | You can select entries inside a listbox widget.
instance HasSelection (ListBox a) where
  -- Clears the listbox\'es selection.
  clearSelection lb = execMethod lb (\nm -> tkSelectionClearAll nm)

-- | A listbox\'es entries are selectable.
instance (HasIndex (ListBox a) i Int) =>
         HasSelectionIndex (ListBox a) i where
  -- Selects the element at the specified index.
  selection i lb =
    synchronize lb
      (do
         binx <- getBaseIndex lb i
         execMethod lb (\ nm -> tkSelectionSetItem nm binx)
         return lb)
  -- Queries if the element at the specified index is selected.
  isSelected lb i =
    synchronize lb
      (do
         binx <- getBaseIndex lb i
         evalMethod lb (\nm -> tkSelectionIncludes nm binx))

-- | You can select a range of elements inside a listbox widget.
instance HasSelectionBaseIndex (ListBox a) [Int] where
  -- Gets the selection range inside the listbox.
  getSelection lb =
    do
      sel <- evalMethod lb (\ nm -> tkCurSelection nm)
      case (((map read) .words) sel) of
        [] -> return Nothing
        l -> return (Just l)

-- | You can select a range of elements inside a listbox widget.
instance (HasIndex (ListBox a) i1 Int, HasIndex (ListBox a) i2 Int) =>
         HasSelectionIndexRange (ListBox a) i1 i2  where
  -- Sets the selection range inside the listbox widget.
  selectionRange start end lb =
    synchronize lb
      (do
         start' <- getBaseIndex lb start
         end' <- getBaseIndex lb end
         execMethod lb (\ nm -> tkSelectionSet nm start' end')
         return lb)

-- | You can select a range of entries inside a listbox widget.
instance HasSelectionBaseIndexRange (ListBox a) Int where
  -- Gets the start index of the listbox\'es selection.
  getSelectionStart lb =
    do
      sel <- getSelection lb
      case sel of
        Nothing -> return Nothing
        Just (v:_) -> return (Just v)
  -- Gets the end index of the listbox\'es selection.
  getSelectionEnd lb =
    do
      sel <- getSelection lb
      case sel of
        Nothing  -> return Nothing
        Just l -> (return . Just . head . reverse) l


-- -----------------------------------------------------------------------
-- Other ListBox operations
-- -----------------------------------------------------------------------

-- | Activates the specified line.
activateElem :: HasIndex (ListBox a) i Int => ListBox a
   -- ^ the concerned listbox.
   -> i
   -- ^ the index of the line to activate.
   -> IO ()
   -- ^ Nothing.
activateElem lb i  =
  synchronize lb
    (do
       binx <- getBaseIndex lb i
       execMethod lb (\ nm -> tkActivate nm binx))

-- | Anchors the selection at the specified line.
selectionAnchor :: HasIndex (ListBox a) i Int => ListBox a
   -- ^ the concerned listbox.
   -> i
   -- ^ the index of the line to anchor the selection at.
   -> IO ()
   -- ^ Nothing.
selectionAnchor lb i =
  synchronize lb
    (do
       binx <- getBaseIndex lb i
       execMethod lb (\nm -> tkSelectionAnchor nm binx)
       done)


-- -----------------------------------------------------------------------
-- SelectMode
-- -----------------------------------------------------------------------

data SelectMode =
  Single | Browse | Multiple | Extended deriving (Eq,Ord,Enum)

instance GUIValue SelectMode where
  cdefault = Single

instance Read SelectMode where
   readsPrec p b =
     case dropWhile (isSpace) b of
        's':'i':'n':'g':'l':'e':xs -> [(Single,xs)]
        'b':'r':'o':'w':'s':'e':xs -> [(Browse,xs)]
        'm':'u':'l':'t':'i':'p':'l':'e':xs -> [(Multiple,xs)]
        'e':'x':'t':'e':'n':'d':'e':'d':xs -> [(Extended,xs)]
        _ -> []

instance Show SelectMode where
   showsPrec d p r =
      (case p of
         Single -> "single"
         Browse -> "browse"
         Multiple -> "multiple"
         Extended -> "extended"
        ) ++ r


-- -----------------------------------------------------------------------
-- exceptions
-- -----------------------------------------------------------------------

elemNotFound :: IOError
elemNotFound = userError "listbox element not found"


-- -----------------------------------------------------------------------
-- ListBox methods
-- -----------------------------------------------------------------------

lboxMethods :: Methods
lboxMethods =
  defMethods{ cleanupCmd = tkCleanupListBox,
              createCmd = tkCreateListBox }

-- -----------------------------------------------------------------------
-- Tk commands
-- -----------------------------------------------------------------------

tkCreateListBox :: ObjectName -> ObjectKind -> ObjectName -> ObjectID ->
                   [ConfigOption] -> TclScript
tkCreateListBox parnm kind@(LISTBOX el) name oid confs =
  tkDeclVar ("sv" ++ show oid) (show name) ++
  (createCmd defMethods) parnm kind name oid confs ++
  tkCreateListBoxElems name el
{-# INLINE tkCreateListBox #-}

tkCleanupListBox :: ObjectID -> ObjectName -> TclScript
tkCleanupListBox oid _ = tkUndeclVar ("sv" ++ show oid)
{-# INLINE tkCleanupListBox #-}

tkCreateListBoxElems ::  ObjectName -> [GUIVALUE] -> TclScript
tkCreateListBoxElems name elems =
        [show name ++ " insert 0 " ++ showElements elems]
{-# INLINE tkCreateListBoxElems #-}

showElements :: [GUIVALUE] -> String
showElements = concatMap (++ " ") . (map show)
{-# INLINE showElements #-}

tkActivate :: ObjectName -> Int -> TclScript
tkActivate name inx = [show name ++ " activate " ++ show inx]
{-# INLINE tkActivate #-}

tkCurSelection :: ObjectName -> TclScript
tkCurSelection name = [show name ++ " curselection "]
{-# INLINE tkCurSelection #-}

tkDelete :: ObjectName -> String -> String -> TclCmd
tkDelete name first last = show name ++ " delete " ++ first ++ " " ++ last
{-# INLINE tkDelete #-}

tkInsert ::  ObjectName -> Int -> [GUIVALUE] -> TclScript
tkInsert name inx elems =
  [tkDelete name "0" "end",
   show name ++ " insert " ++ show inx ++ " " ++ showElements elems]
{-# INLINE tkInsert #-}

tkGet :: ObjectName -> TclScript
tkGet name = [show name ++ " get 0 end"]
{-# INLINE tkGet #-}

tkSelectionAnchor :: ObjectName -> Int -> TclScript
tkSelectionAnchor name inx =
  [show name ++ " selection anchor " ++ show inx]
{-# INLINE tkSelectionAnchor #-}

tkSelectionIncludes :: ObjectName -> Int -> TclScript
tkSelectionIncludes name inx =
  [show name ++ " selection includes " ++ show inx]
{-# INLINE tkSelectionIncludes #-}

tkSelectionClear :: ObjectName -> Int -> Int -> TclScript
tkSelectionClear name first last =
  [show name ++ " selection clear " ++ show first ++ " " ++ show last]
{-# INLINE tkSelectionClear #-}

tkSelectionClearAll :: ObjectName -> TclScript
tkSelectionClearAll name = [show name ++ " selection clear 0 end"]
{-# INLINE tkSelectionClearAll #-}

tkSelectionSet :: ObjectName -> Int -> Int -> TclScript
tkSelectionSet name first last =
  [show name ++ " selection set " ++ show first ++ " " ++ show last]
{-# INLINE tkSelectionSet #-}

tkSelectionSetItem :: ObjectName -> Int -> TclScript
tkSelectionSetItem name first =
  [show name ++ " selection set " ++ show first]
{-# INLINE tkSelectionSetItem #-}