{-# language QuasiQuotes #-}
{-# language TemplateHaskell #-}

module OpenCV.Internal.Core.Types.Rect.TH
  ( mkRectType
  ) where

import "base" Data.Monoid ( (<>) )
import "base" Foreign.Marshal.Utils ( toBool )
import "base" System.IO.Unsafe ( unsafePerformIO )
import qualified "inline-c" Language.C.Inline.Unsafe as CU
import "linear" Linear.Vector ( (^+^) )
import "linear" Linear.V2 ( V2(..) )
import "template-haskell" Language.Haskell.TH
import "template-haskell" Language.Haskell.TH.Quote ( quoteExp )
import "this" OpenCV.Core.Types.Point
import "this" OpenCV.Core.Types.Size
import "this" OpenCV.Internal
import "this" OpenCV.Internal.Core.Types.Rect
import "this" OpenCV.Internal.C.PlacementNew.TH ( mkPlacementNewInstance )
import "this" OpenCV.Internal.C.Types

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

mkRectType
    :: String -- ^ Rectangle type name, for both Haskell and C
    -> Name   -- ^ Depth type name in Haskell
    -> String -- ^ Depth type name in C
    -> String -- ^ Point type name in C
    -> String -- ^ Size type name in C
    -> Q [Dec]
mkRectType rTypeNameStr depthTypeName cDepthTypeStr cPointTypeStr cSizeTypeStr =
    fmap concat . sequence $
      [ (:[]) <$> rectTySynD
      , fromPtrDs
      , isRectOpenCVInstanceDs
      , isRectHaskellInstanceDs
      , mkPlacementNewInstance rTypeName
      ]
  where
    rTypeName :: Name
    rTypeName = mkName rTypeNameStr

    cRectTypeStr :: String
    cRectTypeStr = rTypeNameStr

    rTypeQ :: Q Type
    rTypeQ = conT rTypeName

    depthTypeQ :: Q Type
    depthTypeQ = conT depthTypeName

    rectTySynD :: Q Dec
    rectTySynD =
        tySynD rTypeName [] ([t|Rect|] `appT` depthTypeQ)

    fromPtrDs :: Q [Dec]
    fromPtrDs =
        [d|
        instance FromPtr $(rTypeQ) where
          fromPtr = objFromPtr Rect $ $(finalizerExpQ)
        |]
      where
        finalizerExpQ :: Q Exp
        finalizerExpQ = do
          ptr <- newName "ptr"
          lamE [varP ptr] $
            quoteExp CU.exp $
              "void { delete $(" <> cRectTypeStr <> " * " <> nameBase ptr <> ") }"

    isRectOpenCVInstanceDs :: Q [Dec]
    isRectOpenCVInstanceDs =
        [d|
        instance IsRect Rect $(depthTypeQ) where
          toRect   = id
          fromRect = id

          rectTopLeft     rect = unsafePerformIO $ fromPtr $ withPtr rect $ $(rectTopLeftExpQ)
          rectBottomRight rect = unsafePerformIO $ fromPtr $ withPtr rect $ $(rectBottomRightExpQ)
          rectSize        rect = unsafePerformIO $ fromPtr $ withPtr rect $ $(rectSizeExpQ)
          rectArea        rect = unsafePerformIO $ withPtr rect $ $(rectAreaExpQ)
          rectContains         = $(rectContainsExpQ)
        |]
      where
        rectTopLeftExpQ :: Q Exp
        rectTopLeftExpQ = do
          rectPtr <- newName "rectPtr"
          lamE [varP rectPtr] $ quoteExp CU.exp $
            cPointTypeStr <> " * { new " <> cPointTypeStr <> "($(" <> cRectTypeStr <> " * rectPtr)->tl()) }"

        rectBottomRightExpQ :: Q Exp
        rectBottomRightExpQ = do
          rectPtr <- newName "rectPtr"
          lamE [varP rectPtr] $ quoteExp CU.exp $
            cPointTypeStr <> " * { new " <> cPointTypeStr <> "($(" <> cRectTypeStr <> " * rectPtr)->br()) }"

        rectSizeExpQ :: Q Exp
        rectSizeExpQ = do
          rectPtr <- newName "rectPtr"
          lamE [varP rectPtr] $ quoteExp CU.exp $
            cSizeTypeStr <> " * { new " <> cSizeTypeStr <> "($(" <> cRectTypeStr <> " * rectPtr)->size()) }"

        rectAreaExpQ :: Q Exp
        rectAreaExpQ = do
          rectPtr <- newName "rectPtr"
          lamE [varP rectPtr] $ quoteExp CU.exp $
            cDepthTypeStr <> " { $(" <> cRectTypeStr <> " * rectPtr)->area() }"

        rectContainsExpQ :: Q Exp
        rectContainsExpQ = do
          point <- newName "point"
          rect  <- newName "rect"
          pointPtr <- newName "pointPtr"
          rectPtr  <- newName "rectPtr"

          lamE [varP point, varP rect]
            $ appE [e|toBool|]
            $ appE [e|unsafePerformIO|]
            $ appE ([e|withPtr|] `appE` ([e|toPoint|] `appE` varE point))
            $ lamE [varP pointPtr]
            $ appE ([e|withPtr|] `appE` (varE rect))
            $ lamE [varP rectPtr]
            $ quoteExp CU.exp
            $ "int { $(" <> cRectTypeStr <> " * rectPtr)->contains(*$(" <> cPointTypeStr <> " * pointPtr)) }"

    isRectHaskellInstanceDs :: Q [Dec]
    isRectHaskellInstanceDs =
        [d|
        instance IsRect HRect $(depthTypeQ) where
          toRect hr = unsafePerformIO $ toRectIO hr
          fromRect rect = HRect
                          { hRectTopLeft = fromPoint $ rectTopLeft rect
                          , hRectSize    = fromSize  $ rectSize    rect
                          }

          toRectIO = $(toRectIOExpQ)

          rectTopLeft     hr = hRectTopLeft hr
          rectBottomRight hr = hRectTopLeft hr ^+^ hRectSize hr
          rectSize        hr = hRectSize hr

          rectArea hr = let V2 w h = hRectSize hr
                        in w * h

          rectContains (V2 px py) (HRect (V2 rx ry) (V2 rw rh)) =
                 px >= rx && px < rx + rw
              && py >= ry && py < ry + rh
        |]
      where
        toRectIOExpQ :: Q Exp
        toRectIOExpQ = do
          x <- newName "x"
          y <- newName "y"
          w <- newName "w"
          h <- newName "h"
          lamE [conP 'HRect [conP 'V2 [varP x, varP y], conP 'V2 [varP w, varP h]]] $
            appE [e|fromPtr|] $
              quoteExp CU.exp $ concat
                [ cRectTypeStr <> " * { "
                , "new cv::Rect_<" <> cDepthTypeStr <> ">("
                , "$(" <> cDepthTypeStr <> " x), "
                , "$(" <> cDepthTypeStr <> " y), "
                , "$(" <> cDepthTypeStr <> " w), "
                , "$(" <> cDepthTypeStr <> " h)"
                , ")}"
                ]
