module OpenCV.HighGui
    ( 
      Window
    , makeWindow
    , destroyWindow
    , withWindow
    , resizeWindow
      
      
    , waitKey
      
    , Event(..)
    , EventFlags
    , hasLButton
    , hasRButton
    , hasMButton
    , hasCtrlKey
    , hasShiftKey
    , hasAltKey
    , EventFlagsRec(..)
    , flagsToRec
    , MouseCallback
    , setMouseCallback
      
    , TrackbarCallback
    , createTrackbar
      
    , 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"
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
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
         }
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 :: String -> (Window -> IO a) -> IO a
withWindow title = bracket (makeWindow title) destroyWindow
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
waitKey :: Int32 -> IO Int32
waitKey delay = [C.exp| int32_t { cv::waitKey($(int32_t delay)) }|]
data Event
   = EventMouseMove
   | EventLButtonDown
   | EventRButtonDown
   | EventMButtonDown
   | EventLButtonUp
   | EventRButtonUp
   | EventMButtonUp
   | EventLButtonDbClick
   | EventRButtonDbClick
   | EventMButtonDbClick
   | EventMouseWheel
   | EventMouseHWheel
     deriving Show
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
c'EVENT_FLAG_RBUTTON = 2
c'EVENT_FLAG_RBUTTON :: (Num a) => a
c'EVENT_FLAG_MBUTTON = 4
c'EVENT_FLAG_MBUTTON :: (Num a) => a
c'EVENT_FLAG_CTRLKEY = 8
c'EVENT_FLAG_CTRLKEY :: (Num a) => a
c'EVENT_FLAG_SHIFTKEY = 16
c'EVENT_FLAG_SHIFTKEY :: (Num a) => a
c'EVENT_FLAG_ALTKEY = 32
c'EVENT_FLAG_ALTKEY :: (Num a) => a
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
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
c'EVENT_LBUTTONDOWN = 1
c'EVENT_LBUTTONDOWN :: (Num a) => a
c'EVENT_RBUTTONDOWN = 2
c'EVENT_RBUTTONDOWN :: (Num a) => a
c'EVENT_MBUTTONDOWN = 3
c'EVENT_MBUTTONDOWN :: (Num a) => a
c'EVENT_LBUTTONUP = 4
c'EVENT_LBUTTONUP :: (Num a) => a
c'EVENT_RBUTTONUP = 5
c'EVENT_RBUTTONUP :: (Num a) => a
c'EVENT_MBUTTONUP = 6
c'EVENT_MBUTTONUP :: (Num a) => a
c'EVENT_LBUTTONDBLCLK = 7
c'EVENT_LBUTTONDBLCLK :: (Num a) => a
c'EVENT_RBUTTONDBLCLK = 8
c'EVENT_RBUTTONDBLCLK :: (Num a) => a
c'EVENT_MBUTTONDBLCLK = 9
c'EVENT_MBUTTONDBLCLK :: (Num a) => a
c'EVENT_MOUSEWHEEL = 10
c'EVENT_MOUSEWHEEL :: (Num a) => a
c'EVENT_MOUSEHWHEEL = 11
c'EVENT_MOUSEHWHEEL :: (Num a) => a
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
type MouseCallback
   =  Event 
   -> Int32 
   -> Int32 
   -> EventFlags
      
      
   -> 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
type TrackbarCallback
   =  Int32 
   -> IO ()
createTrackbar
    :: Window
    -> String 
    -> Int32  
    -> Int32  
    -> 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
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