{-# LINE 1 "src/OpenCV/HighGui.hsc" #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

{- |
While OpenCV was designed for use in full-scale applications and can be used
within functionally rich UI frameworks (such as Qt*, WinForms*, or Cocoa*) or
without any UI at all, sometimes there it is required to try functionality
quickly and visualize the results. This is what the "OpenCV.HighGui" module has
been designed for.

It provides easy interface to:

 * Create and manipulate windows that can display images and “remember” their
   content (no need to handle repaint events from OS).

 * Add trackbars to the windows, handle simple mouse events as well as keyboard
   commands.
-}
module OpenCV.HighGui
    ( -- * Window management
      Window
    , makeWindow
    , destroyWindow
    , withWindow
    , resizeWindow

      -- * Event handling

      -- ** Keyboard
    , waitKey

      -- ** Mouse
    , Event(..)
    , EventFlags

    , hasLButton
    , hasRButton
    , hasMButton
    , hasCtrlKey
    , hasShiftKey
    , hasAltKey

    , EventFlagsRec(..)
    , flagsToRec

    , MouseCallback
    , setMouseCallback

      -- * Trackbars
    , TrackbarCallback
    , createTrackbar

      -- * Drawing
    , imshow
    , imshowM
    ) where

import "base" Control.Concurrent.MVar
import "base" Control.Exception ( mask_, bracket )
import "base" Data.Bits ( (.&.) )
import "base" Data.Int ( Int32 )
import "base" Data.Monoid ( (<>) )
import "base" Data.Unique ( newUnique, hashUnique )
import "base" Foreign.C.String ( CString, newCString, withCString )
import "base" Foreign.Ptr ( Ptr, FunPtr, freeHaskellFunPtr )
import "base" Foreign.Marshal.Alloc ( free )
import "base" Foreign.Marshal.Utils ( new )
import "containers" Data.Map ( Map )
import qualified "containers" Data.Map as M
import qualified "inline-c" Language.C.Inline as C
import qualified "inline-c-cpp" Language.C.Inline.Cpp as C
import "primitive" Control.Monad.Primitive ( PrimState )
import "this" OpenCV.Internal.C.Inline ( openCvCtx )
import "this" OpenCV.Internal.C.Types
import "this" OpenCV.Core.Types.Mat
import "this" OpenCV.Internal.Mutable
import "this" OpenCV.TypeLevel

--------------------------------------------------------------------------------

C.context (C.cppCtx <> openCvCtx)

C.include "opencv2/core.hpp"
C.include "opencv2/highgui.hpp"
C.using "namespace cv"








--------------------------------------------------------------------------------
-- Window management

data TrackbarState
   = TrackbarState
     { trackbarCallback :: !(FunPtr C'TrackbarCallback)
     , trackbarValuePtr :: !(Ptr Int32)
     }

data Window
   = Window
     { windowName          :: !CString
     , windowMouseCallback :: !(MVar (Maybe (FunPtr C'MouseCallback)))
     , windowTrackbars     :: !(MVar (Map String TrackbarState))
     }

freeTrackbar :: TrackbarState -> IO ()
freeTrackbar trackbar = do
    freeHaskellFunPtr $ trackbarCallback trackbar
    free $ trackbarValuePtr trackbar

-- #num WINDOW_NORMAL
-- #num WINDOW_AUTOSIZE
-- #num WINDOW_OPENGL
-- #num WINDOW_FREERATIO
-- #num WINDOW_KEEPRATIO

-- marshalWindowFlags :: WindowFlags -> Int32
-- marshalWindowFlags WindowFlags{..} =

-- | Create a window with the specified title.
--
-- Make sure to free the window when you're done with it using 'destroyWindow'
-- or better yet: use 'withWindow'.
makeWindow :: String -> IO Window
makeWindow title = do
    name <- show . hashUnique <$> newUnique
    c'name <- newCString name
    mouseCallback <- newMVar Nothing
    trackbars <- newMVar M.empty
    withCString title $ \c'title ->
      [C.block| void {
        char * cname = $(char * c'name);
        cv::namedWindow(cname, cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO);
        cv::setWindowTitle(cname, $(char * c'title));
      }|]
    pure Window
         { windowName          = c'name
         , windowMouseCallback = mouseCallback
         , windowTrackbars     = trackbars
         }

-- | Close the window and free up all resources associated with the window.
destroyWindow :: Window -> IO ()
destroyWindow window = mask_ $ do
    [C.exp| void { cv::destroyWindow($(char * c'name)); }|]
    free c'name
    modifyMVar_ (windowMouseCallback window) $ \mbMouseCallback -> do
      mapM_ freeHaskellFunPtr mbMouseCallback
      pure Nothing
    modifyMVar_ (windowTrackbars window) $ \trackbars -> do
      mapM_ freeTrackbar trackbars
      pure M.empty
  where
    c'name :: CString
    c'name = windowName window

-- | @withWindow title act@ makes a window with the specified @title@ and passes
-- the resulting 'Window' to the computation @act@. The window will be destroyed
-- on exit from @withWindow@ whether by normal termination or by raising an
-- exception. Make sure not to use the @Window@ outside the @act@ computation!
withWindow :: String -> (Window -> IO a) -> IO a
withWindow title = bracket (makeWindow title) destroyWindow

-- | Resize a window to the specified size.
resizeWindow :: Window -> Int32 -> Int32 -> IO ()
resizeWindow window width height =
  [C.exp| void { cv::resizeWindow($(char * c'name), $(int32_t width), $(int32_t height)); }|]
    where
      c'name :: CString
      c'name = windowName window


--------------------------------------------------------------------------------
-- Keyboard

waitKey :: Int32 -> IO Int32
waitKey delay = [C.exp| int32_t { cv::waitKey($(int32_t delay)) }|]


--------------------------------------------------------------------------------
-- Mouse

data Event
   = EventMouseMove
   | EventLButtonDown
   | EventRButtonDown
   | EventMButtonDown
   | EventLButtonUp
   | EventRButtonUp
   | EventMButtonUp
   | EventLButtonDbClick
   | EventRButtonDbClick
   | EventMButtonDbClick
   | EventMouseWheel
   | EventMouseHWheel
     deriving Show

-- | Context for a mouse 'Event'
--
-- Information about which buttons and modifier keys where pressed during the
-- event.
newtype EventFlags = EventFlags Int32

matchEventFlag :: Int32 -> EventFlags -> Bool
matchEventFlag flag = \(EventFlags flags) -> flags .&. flag /= 0

hasLButton  :: EventFlags -> Bool
hasRButton  :: EventFlags -> Bool
hasMButton  :: EventFlags -> Bool
hasCtrlKey  :: EventFlags -> Bool
hasShiftKey :: EventFlags -> Bool
hasAltKey   :: EventFlags -> Bool

c'EVENT_FLAG_LBUTTON = 1
c'EVENT_FLAG_LBUTTON :: (Num a) => a

{-# LINE 219 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_FLAG_RBUTTON = 2
c'EVENT_FLAG_RBUTTON :: (Num a) => a

{-# LINE 220 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_FLAG_MBUTTON = 4
c'EVENT_FLAG_MBUTTON :: (Num a) => a

{-# LINE 221 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_FLAG_CTRLKEY = 8
c'EVENT_FLAG_CTRLKEY :: (Num a) => a

{-# LINE 222 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_FLAG_SHIFTKEY = 16
c'EVENT_FLAG_SHIFTKEY :: (Num a) => a

{-# LINE 223 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_FLAG_ALTKEY = 32
c'EVENT_FLAG_ALTKEY :: (Num a) => a

{-# LINE 224 "src/OpenCV/HighGui.hsc" #-}

hasLButton  = matchEventFlag c'EVENT_FLAG_LBUTTON
hasRButton  = matchEventFlag c'EVENT_FLAG_RBUTTON
hasMButton  = matchEventFlag c'EVENT_FLAG_MBUTTON
hasCtrlKey  = matchEventFlag c'EVENT_FLAG_CTRLKEY
hasShiftKey = matchEventFlag c'EVENT_FLAG_SHIFTKEY
hasAltKey   = matchEventFlag c'EVENT_FLAG_ALTKEY

-- | More convenient representation of 'EventFlags'
data EventFlagsRec
   = EventFlagsRec
     { flagsLButton  :: !Bool
     , flagsRButton  :: !Bool
     , flagsMButton  :: !Bool
     , flagsCtrlKey  :: !Bool
     , flagsShiftKey :: !Bool
     , flagsAltKey   :: !Bool
     } deriving Show

flagsToRec :: EventFlags -> EventFlagsRec
flagsToRec flags =
    EventFlagsRec
    { flagsLButton  = hasLButton  flags
    , flagsRButton  = hasRButton  flags
    , flagsMButton  = hasMButton  flags
    , flagsCtrlKey  = hasCtrlKey  flags
    , flagsShiftKey = hasShiftKey flags
    , flagsAltKey   = hasAltKey   flags
    }

c'EVENT_MOUSEMOVE = 0
c'EVENT_MOUSEMOVE :: (Num a) => a

{-# LINE 255 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_LBUTTONDOWN = 1
c'EVENT_LBUTTONDOWN :: (Num a) => a

{-# LINE 256 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_RBUTTONDOWN = 2
c'EVENT_RBUTTONDOWN :: (Num a) => a

{-# LINE 257 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_MBUTTONDOWN = 3
c'EVENT_MBUTTONDOWN :: (Num a) => a

{-# LINE 258 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_LBUTTONUP = 4
c'EVENT_LBUTTONUP :: (Num a) => a

{-# LINE 259 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_RBUTTONUP = 5
c'EVENT_RBUTTONUP :: (Num a) => a

{-# LINE 260 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_MBUTTONUP = 6
c'EVENT_MBUTTONUP :: (Num a) => a

{-# LINE 261 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_LBUTTONDBLCLK = 7
c'EVENT_LBUTTONDBLCLK :: (Num a) => a

{-# LINE 262 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_RBUTTONDBLCLK = 8
c'EVENT_RBUTTONDBLCLK :: (Num a) => a

{-# LINE 263 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_MBUTTONDBLCLK = 9
c'EVENT_MBUTTONDBLCLK :: (Num a) => a

{-# LINE 264 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_MOUSEWHEEL = 10
c'EVENT_MOUSEWHEEL :: (Num a) => a

{-# LINE 265 "src/OpenCV/HighGui.hsc" #-}
c'EVENT_MOUSEHWHEEL = 11
c'EVENT_MOUSEHWHEEL :: (Num a) => a

{-# LINE 266 "src/OpenCV/HighGui.hsc" #-}

unmarshalEvent :: Int32 -> Event
unmarshalEvent event
   | event == c'EVENT_MOUSEMOVE     = EventMouseMove
   | event == c'EVENT_LBUTTONDOWN   = EventLButtonDown
   | event == c'EVENT_RBUTTONDOWN   = EventRButtonDown
   | event == c'EVENT_MBUTTONDOWN   = EventMButtonDown
   | event == c'EVENT_LBUTTONUP     = EventLButtonUp
   | event == c'EVENT_RBUTTONUP     = EventRButtonUp
   | event == c'EVENT_MBUTTONUP     = EventMButtonUp
   | event == c'EVENT_LBUTTONDBLCLK = EventLButtonDbClick
   | event == c'EVENT_RBUTTONDBLCLK = EventRButtonDbClick
   | event == c'EVENT_MBUTTONDBLCLK = EventMButtonDbClick
   | event == c'EVENT_MOUSEWHEEL    = EventMouseWheel
   | event == c'EVENT_MOUSEHWHEEL   = EventMouseHWheel
   | otherwise = error $ "unmarshalEvent - unknown event " <> show event

-- | Callback function for mouse events
type MouseCallback
   =  Event -- ^ What happened to cause the callback to be fired.
   -> Int32 -- ^ The x-coordinate of the mouse event.
   -> Int32 -- ^ The y-coordinate of the mouse event.
   -> EventFlags
      -- ^ Context for the event, such as buttons and modifier keys pressed
      -- during the event.
   -> IO ()

setMouseCallback :: Window -> MouseCallback -> IO ()
setMouseCallback window callback =
    modifyMVar_ (windowMouseCallback window) $ \mbPrevCallback -> do
      callbackPtr <- $(C.mkFunPtr [t| C'MouseCallback |]) c'callback
      [C.exp| void { cv::setMouseCallback($(char * c'name), $(MouseCallback callbackPtr)) }|]
      mapM_ freeHaskellFunPtr mbPrevCallback
      pure $ Just callbackPtr
  where
    c'name :: CString
    c'name = windowName window

    c'callback :: C'MouseCallback
    c'callback c'event x y c'flags _c'userDataPtr = callback event x y flags
      where
        event = unmarshalEvent c'event
        flags = EventFlags $ fromIntegral c'flags

--------------------------------------------------------------------------------
-- Trackbars

-- | Callback function for trackbars
type TrackbarCallback
   =  Int32 -- ^ Current position of the specified trackbar.
   -> IO ()

createTrackbar
    :: Window
    -> String -- ^ Trackbar name
    -> Int32  -- ^ Initial value
    -> Int32  -- ^ Maximum value
    -> TrackbarCallback
    -> IO ()
createTrackbar window trackbarName value count callback =
    modifyMVar_ (windowTrackbars window) $ \trackbars ->
    withCString trackbarName $ \c'trackbarName -> mask_ $ do
      valuePtr <- new value
      callbackPtr <- $(C.mkFunPtr [t| C'TrackbarCallback |]) c'callback
      [C.exp| void {
        (void)cv::createTrackbar
          ( $(char * c'trackbarName)
          , $(char * c'name)
          , $(int32_t * valuePtr)
          , $(int32_t count)
          , $(TrackbarCallback callbackPtr)
          )
      }|]

      let (mbPrevCallback, trackbars') =
              M.updateLookupWithKey (\_k _v -> Just trackbar)
                                    trackbarName
                                    trackbars
          trackbar = TrackbarState
                     { trackbarCallback = callbackPtr
                     , trackbarValuePtr = valuePtr
                     }
      mapM_ freeTrackbar mbPrevCallback
      pure trackbars'
  where
    c'name :: CString
    c'name = windowName window

    c'callback :: C'TrackbarCallback
    c'callback pos _c'userDataPtr = callback pos


--------------------------------------------------------------------------------
-- Drawing

imshow
    :: Window -- ^
    -> Mat ('S [height, width]) channels depth
    -> IO ()
imshow window mat =
    withPtr mat $ \matPtr ->
      [C.exp| void { cv::imshow($(char * c'name), *$(Mat * matPtr)); }|]
  where
    c'name :: CString
    c'name = windowName window

imshowM
    :: Window -- ^
    -> Mut (Mat ('S [height, width]) channels depth) (PrimState IO)
    -> IO ()
imshowM window mat = imshow window =<< unsafeFreeze mat