-- | A "panel" to be shown on the Function Editor, for adjusting the
-- arguments of the function being edited -- adding and deleting arguments,
-- or modifying their "kind" (number of inlets, needed for higher order
-- functions)

module Graphics.UI.Sifflet.EditArgsPanel
    (
     ArgSpecAction
    , EditArgsPanel
    , makeEditArgsPanel
    , editArgsPanelRoot
    , expandToFit
    )

where

import Data.IORef

import Graphics.UI.Gtk (EventBox, hButtonBoxNew, 
                        containerRemove,
                        ButtonBoxClass, buttonActivated,
                        widgetGetParent,
                        widgetQueueResize)

import Language.Sifflet.Expr
import Graphics.UI.Sifflet.Callback
import Graphics.UI.Sifflet.LittleGtk
import Language.Sifflet.Util (SuccFail(..), parseInt)

type ArgSpecAction = [ArgSpec] -> IO ()

type PanelRoot = EventBox

data EditArgsPanel = 
    EditArgsPanel {editArgsPanelRoot :: PanelRoot,
                   editArgsPanelAction :: ArgSpecAction}

type StateRef = IORef State

data State = State Model UI

newtype Model = Model [ArgSpec]
           deriving (Show)

data UI = UI PanelRoot Table Label [ArgRow]

data ArgRow = ArgRow Entry Entry Button


-- | Create an EditArgsPanel
-- It currently returns a EditArgsPanel object, 
-- and you can put the root of it into a container, e.g.,
--     set window [containerChild := editArgsPanelRoot panel]

-- I think it needs to return some more information than this, though,
-- and maybe know about its parent or larger context, so that when
-- applied, it can have a "global" effect.
-- Maybe just in the form of IO () actions, though.
-- 
-- It has this structure (for n existing arguments):

-- EventBox ( = editArgsPanelRoot panel)
-- +----------------------------------+
-- | Frame                            |
-- | +------------------------------+ |
-- | | VBox                         | |
-- | | +--------------------------+ | |
-- | | | Table (n+2 rows)x(3 cols)| | |
-- | | | +----------------------+ | | |
-- | | | |                      | | | |
-- | | | | (arguments)          | | | |
-- | | | +----------------------+ | | |
-- | | |                          | | |
-- | | | HButtonBox               | | |
-- | | | +----------------------+ | | |
-- | | | | (buttons)            | | | |
-- | | | +----------------------+ | | |
-- | | |                          | | |
-- | | | Label                    | | |
-- | | | +----------------------+ | | |
-- | | | | (status messages)    | | | |
-- | | | +----------------------+ | | |
-- | | +--------------------------+ | |
-- | +------------------------------+ |
-- +----------------------------------+

makeEditArgsPanel :: CBMgr -> [ArgSpec] -> ArgSpecAction
                  -> IO EditArgsPanel
makeEditArgsPanel cbMgr argSpecs okayAction = do
  root <- eventBoxNew         -- needed for everything to be visible!
  frame <- frameNew
  frameSetLabel frame "Edit Arguments"

  let panel = EditArgsPanel {editArgsPanelRoot = root,
                             editArgsPanelAction = okayAction}

  vbox <- vBoxNew False 5 -- non-homogeneous sizes,  5 pixels separation

  -- Create a table of n + 2 rows x 3 cols, non-homogeneous sizes
  table <- tableNew (length argSpecs + 2) 3 False 
  status <- labelNew (Just "")

  stateRef <- newIORef (State (Model argSpecs) (UI root table status []))
  dressTable stateRef

  btnBox <- hButtonBoxNew
  fillButtonBox cbMgr btnBox stateRef panel

  containerAdd root frame
  containerAdd frame vbox
  boxPackStartDefaults vbox table
  boxPackStartDefaults vbox btnBox
  boxPackStartDefaults vbox status

  widgetShowAll root
  return panel

-- | Fill the table with rows for each argument in the model and more

dressTable :: StateRef -> IO ()
dressTable sref = do
  -- read state
  State (Model args) ui@(UI root table status _rows) <- readIORef sref

  -- add "headings"
  argLabel <- labelNew (Just "Name")
  inputsLabel <- labelNew (Just "Inlets")
  tableAttachCell table argLabel 0 0
  tableAttachCell table inputsLabel 0 1

-- add rows for existing and new arguments
  let n = length args
  argRows <- mapM (uncurry (argRowNew sref)) 
                  (zip (args ++ [ArgSpec "" 0]) [0 .. n])
  mapM_ (uncurry (attachRow table)) 
        (zip argRows [1 .. n + 1])
  setStatusOK ui
  widgetShowAll table

  -- write state
  writeIORef sref (State (Model args) (UI root table status argRows))


-- | Resize a widget to be at least as big as a(nother) widget.
-- Normally the widget being resized is one that contains the other.

expandToFit :: (WidgetClass v, WidgetClass w) => v -> w -> IO ()
expandToFit container widget = do
  Requisition w1 h1 <- widgetSizeRequest container
  Requisition w2 h2 <- widgetSizeRequest widget
  let (w, h) = (max w1 w2, max h1 h2)
  widgetSetSizeRequest container w h
  widgetQueueResize container

-- | Remove from table and destroy all arg rows
-- (does not alter model)

stripTable :: StateRef -> IO ()
stripTable sref = do
  State model ui@(UI root table status _rows) <- readIORef sref
  let stripWidget widget = 
          do
            containerRemove table widget
            widgetDestroy widget

  widgets <- containerGetChildren table
  mapM_ stripWidget widgets
  -- Why is this /not/ equivalent to above two statements?
  --   liftM (mapM_ stripWidget) (containerGetChildren table)
  setStatusOK ui

  writeIORef sref (State model (UI root table status []))

attachRow :: Table -> ArgRow -> Int -> IO ()
attachRow table (ArgRow nameEntry nEntry btn) nrow = do
  tableAttachCell table nameEntry nrow 0
  tableAttachCell table nEntry nrow 1
  tableAttachCell table btn nrow 2

-- | Attach a widget to a table in a single cell, specified by its
-- row column (top lef = 0 0) coordinates

tableAttachCell :: (WidgetClass w) => Table -> w -> Int -> Int -> IO ()
tableAttachCell t w top left = 
    tableAttachDefaults t w left (left + 1) top (top + 1)


fillButtonBox :: (ButtonBoxClass b) => 
                 CBMgr -> b -> StateRef -> EditArgsPanel -> IO ()
fillButtonBox cbMgr btnBox sref panel = 
    let addButton (label, action) = do
          b <- buttonNewWithLabel label
          containerAdd btnBox b
          --   on b buttonActivated action
          cbMgr (AfterButtonClicked b action)                   
        addButton' label action = do
          b <- buttonNewWithLabel label
          containerAdd btnBox b
          _ <- on b buttonActivated action
          return ()

        applyAction :: IO ()
        applyAction = applyArgRows sref (editArgsPanelAction panel)

        okayAction = 
            applyAction >> closePanel panel

    in do
      -- addButton' "OK" (cbMgr (WithUIRef okayAction))
      addButton' "OK" okayAction
      mapM_ addButton [
                       -- ("Strip", \ _ -> stripTable sref),
                       -- ("Dress", \ _ -> dressTable sref),
                       ("Cancel", \ _ -> closePanel panel)]

-- | Close (destroy) the panel.
-- I hope this also removes it from its parent.

closePanel :: EditArgsPanel -> IO ()
closePanel panel = widgetDestroy (editArgsPanelRoot panel)

-- | Create a new ArgRow
argRowNew :: StateRef -> ArgSpec -> Int -> IO ArgRow
argRowNew sref (ArgSpec name inlets) n = do
  e1 <- makeEntry name
  e2 <- makeEntry (show inlets)
  -- Create and set up button
  let (label, action) = 
          case name of
            "" -> ("Add", addArg sref n)
            _ -> ("Remove", removeArg sref n)
  b <- buttonNewWithLabel label
  _ <- on b buttonActivated action
  return (ArgRow e1 e2 b)

-- | Add a new argument, nth in the model, from the (n+1)th row in the table

-- Still to do: make sure the name is non-blank, and doesn't duplicate
-- an existing argument (or global variable??)
-- and is a valid symbol name (not a number, etc.)?

addArg :: StateRef -> Int -> IO ()
addArg sref n = do
  State (Model argSpecs) ui@(UI root _table _status argRows) <- readIORef sref
  readResult <- readArgRow (argRows !! n)

  case readResult of

    Succ argTool -> 
        do
          writeIORef sref (State (Model (argSpecs ++ [argTool])) ui)
          stripTable sref
          dressTable sref
          -- Make sure the container (layout) is big enough 
          -- to show the enlarged panel
          mparent <- widgetGetParent root
          case mparent of
            Nothing -> return ()       -- shouldn't happen
            Just parent -> expandToFit parent root
          setStatusOK ui

    Fail msg -> setStatus ui $ "Add: " ++ msg

readArgRow :: ArgRow -> IO (SuccFail ArgSpec)
readArgRow (ArgRow e1 e2 _) = do
  name <- entryGetText e1
  case name of
    "" -> return $ Fail "blank name is not allowed"
    _ -> do
      inletsStr <- entryGetText e2
      return $ case parseInt ("Inlets for " ++ name) inletsStr of
                 Succ inlets -> Succ (ArgSpec name inlets)
                 Fail msg -> Fail msg

-- | Remove an argument, nth in the model, (n+1)th row in the table
removeArg :: StateRef -> Int -> IO ()
removeArg sref n = do
  State (Model argSpecs) ui <- readIORef sref
  writeIORef sref (State (Model (listRemove argSpecs n)) ui)
  stripTable sref
  dressTable sref
  setStatusOK ui

-- | Remove the nth element of a list (0 is the head)
listRemove :: [a] -> Int -> [a]
listRemove [] _ = error "listRemove: empty"
listRemove (_:xs) 0 = xs
listRemove (x:xs) n = x : (listRemove xs (n - 1))

makeEntry :: String -> IO Entry
makeEntry text = do
  entry <- entryNew
  entrySetText entry text
  return entry

applyArgRows :: StateRef -> ArgSpecAction -> IO ()
applyArgRows sref action = do
  State _model ui@(UI _root _table _status argRows) <- readIORef sref
  readResult <- readArgRows argRows
  case readResult of
    Succ newSpecs -> 
        do
          setStatusOK ui
          writeIORef sref (State (Model newSpecs) ui)
          action newSpecs
    Fail msg ->
        setStatus ui ("Apply: " ++ msg) >> 
        return ()


-- Use a monad transformer or "lift" here???

readArgRows :: [ArgRow] -> IO (SuccFail [ArgSpec])
readArgRows [] = return (Succ []) -- shouldn't happen!
readArgRows (_:[]) = return (Succ []) -- last row is for the "add" button
readArgRows (row:rows) = do
  results <- readArgRows rows
  case results of
    Fail msg -> return $ Fail msg
    Succ specs -> 
        do
          result <- readArgRow row
          case result of
            Fail msg -> return $ Fail msg
            Succ spec -> return $ Succ (spec:specs)
   
setStatusOK :: UI -> IO ()
setStatusOK ui = setStatus ui ""
           
setStatus :: UI -> String -> IO ()
setStatus (UI _ _ status _) msg = 
    labelSetText status msg