{-# LANGUAGE CPP, OverloadedStrings #-}
#ifdef CALLSTACK_AVAILABLE
{-# LANGUAGE ImplicitParams #-}
#endif

module Graphics.UI.FLTK.LowLevel.Utils where
import Graphics.UI.FLTK.LowLevel.Fl_Types
import Graphics.UI.FLTK.LowLevel.Fl_Enumerations
import Graphics.UI.FLTK.LowLevel.Dispatch
import qualified Data.Text as T
import Data.List
import Data.Maybe
import qualified Data.Text.Foreign as TF
import qualified Data.Text.Encoding as E
import Foreign
import qualified Foreign.Concurrent as FC
import Foreign.C
import qualified Data.ByteString as B
import qualified System.IO.Unsafe as Unsafe
import Debug.Trace
#if defined(CALLSTACK_AVAILABLE) || defined(HASCALLSTACK_AVAILABLE)
import GHC.Stack
#endif

foreign import ccall "wrapper"
        mkWidgetCallbackPtr :: CallbackWithUserDataPrim -> IO (FunPtr CallbackWithUserDataPrim)
foreign import ccall "wrapper"
        mkCallbackPtr :: CallbackPrim -> IO (FunPtr CallbackPrim)
foreign import ccall "wrapper"
        mkCustomColorAveragePtr :: CustomColorAveragePrim -> IO (FunPtr CustomColorAveragePrim)
foreign import ccall "wrapper"
        mkGlobalEventHandlerPtr :: GlobalEventHandlerPrim -> IO (FunPtr GlobalEventHandlerPrim)
foreign import ccall "wrapper"
        mkDrawCallbackPrimPtr :: DrawCallbackPrim -> IO (FunPtr DrawCallbackPrim)
foreign import ccall "wrapper"
        mkCustomImageDrawPrimPtr :: CustomImageDrawPrim -> IO (FunPtr CustomImageDrawPrim)
foreign import ccall "wrapper"
        mkCustomImageCopyPrimPtr :: CustomImageCopyPrim -> IO (FunPtr CustomImageCopyPrim)
foreign import ccall "wrapper"
        mkUnfinishedStyleCbPrim :: UnfinishedStyleCbPrim -> IO (FunPtr UnfinishedStyleCbPrim)
foreign import ccall "wrapper"
        mkFinalizer :: (Ptr a -> IO ()) -> IO (FinalizerPtr a)
foreign import ccall "wrapper"
        mkFinalizerEnv :: (Ptr env -> Ptr a -> IO ()) -> IO (FinalizerEnvPtr env a)
foreign import ccall "wrapper"
        wrapBoxDrawFPrim :: BoxDrawFPrim -> IO (FunPtr BoxDrawFPrim)
foreign import ccall "dynamic"
        unwrapGlobalCallbackPtr :: FunPtr GlobalCallback -> GlobalCallback
foreign import ccall "dynamic"
        unwrapBoxDrawFPrim :: FunPtr BoxDrawFPrim -> BoxDrawFPrim
foreign import ccall "wrapper"
        mkTextModifyCb :: TextModifyCbPrim -> IO (FunPtr TextModifyCbPrim)
foreign import ccall "wrapper"
        mkTextPredeleteCb :: TextPredeleteCbPrim -> IO (FunPtr TextPredeleteCbPrim)
foreign import ccall "wrapper"
        mkFDHandlerPrim :: FDHandlerPrim -> IO (FunPtr FDHandlerPrim)
foreign import ccall "wrapper"
        mkGlobalCallbackPtr:: GlobalCallback -> IO (FunPtr GlobalCallback)
foreign import ccall "wrapper"
        mkMenuItemDrawFPtr :: MenuItemDrawF -> IO (FunPtr MenuItemDrawF)
foreign import ccall "wrapper"
        mkTabPositionsPrim :: TabPositionsPrim -> IO (FunPtr TabPositionsPrim)
foreign import ccall "wrapper"
        mkTabHeightPrim :: TabHeightPrim -> IO (FunPtr TabHeightPrim)
foreign import ccall "wrapper"
        mkTabWhichPrim :: TabWhichPrim -> IO (FunPtr TabWhichPrim)
foreign import ccall "wrapper"
        mkTabClientAreaPrim :: TabClientAreaPrim -> IO (FunPtr TabClientAreaPrim)
foreign import ccall "wrapper"
        mkGetDouble :: GetDoublePrim -> IO (FunPtr GetDoublePrim)
foreign import ccall "wrapper"
        mkGetInt :: GetIntPrim -> IO (FunPtr GetIntPrim)
foreign import ccall "wrapper"
        mkSetInt :: SetIntPrim -> IO (FunPtr SetIntPrim)
foreign import ccall "wrapper"
        mkColorSetPrim :: ColorSetPrim -> IO (FunPtr ColorSetPrim)
foreign import ccall "wrapper"
        mkDestroyCallbacksPtr :: DestroyCallbacksPrim -> IO (FunPtr DestroyCallbacksPrim)

toTabPositionsPrim :: (Ref a -> IO (Maybe AtIndex, Int, [(X,Width)])) -> IO (FunPtr TabPositionsPrim)
toTabPositionsPrim f =
  mkTabPositionsPrim (\tabPtr posPtr widthPtr -> do
                          pp <- wrapNonNull tabPtr "Null pointer. toTabPositionsPrim"
                          (selected, padding, posAndWidths) <- f (castTo (wrapInRef pp))
                          pokeArray posPtr ([fromIntegral padding] ++ (map (\(X x,_) -> fromIntegral x) posAndWidths))
                          pokeArray widthPtr (map (\(_,Width w) -> fromIntegral w) posAndWidths)
                          maybe (return (0 :: CInt)) (\(AtIndex i) -> return (fromIntegral i)) selected
                      )

toTabHeightPrim ::
  (Ref a -> IO Height) ->
  IO (FunPtr TabHeightPrim)
toTabHeightPrim f =
  mkTabHeightPrim (\ptr -> do
                       pp <- wrapNonNull ptr "Null pointer. toTabHeightPrim"
                       (Height res) <- f (castTo (wrapInRef pp))
                       return (fromIntegral res)
                   )

toCallbackPrim :: (Ref a -> IO ()) ->
                  IO (FunPtr (Ptr () -> IO ()))
toCallbackPrim f = mkCallbackPtr $ \ptr -> do
  pp <- wrapNonNull ptr "Null pointer. toCallbackPrim"
  f (castTo (wrapInRef pp))

toCallbackPrimWithUserData :: (Ref a -> IO ()) ->
                              IO (FunPtr (Ptr () -> Ptr () -> IO ()))
toCallbackPrimWithUserData f = mkWidgetCallbackPtr $ \ptr _ -> do
  pp <- wrapNonNull ptr "Null pointer: toWidgetCallbackPrim"
  f (castTo (wrapInRef pp))

toDestroyCallbacksPrim ::
  (Ref a -> [Maybe (FunPtr (IO ()))] -> IO ()) ->
  IO (FunPtr DestroyCallbacksPrim)
toDestroyCallbacksPrim f =
  let marshalFps :: Ptr (FunPtr (IO ())) -> CInt -> [Maybe (FunPtr (IO ()))] -> IO [Maybe (FunPtr (IO ()))]
      marshalFps arrayPtr numLeft accum =
        if (numLeft == 0) then return accum
        else do
          fp <- peek arrayPtr
          marshalFps
            (arrayPtr `plusPtr` (sizeOf (undefined :: FunPtr (IO ()))))
            (numLeft - 1)
            (if (fp == nullFunPtr)
             then  (accum ++ [Nothing])
             else (accum ++ [Just fp]))
  in
  mkDestroyCallbacksPtr $ \ptr fpts -> do
    pp <- wrapNonNull ptr "Null pointer. toDestroyCallbacksPrim"
    (numFps, fpArray) <- unpackFunctionPointerToFreeStruct fpts
    cbs <- marshalFps fpArray numFps []
    f (castTo (wrapInRef pp)) cbs

cFromEnum :: (Enum a, Integral b) => a -> b
cFromEnum = fromIntegral . fromEnum
cToEnum :: (Integral b, Enum a) => b -> a
cToEnum = toEnum . fromIntegral
cToBool :: (Eq a, Num a, Ord a) => a -> Bool
cToBool status =
  if (status > 0)
  then True
  else False

cFromBool :: (Eq a, Num a) => Bool -> a
cFromBool status = if status then 1 else 0

toFunPtr :: (a -> FunPtr a) -> a -> FunPtr a
toFunPtr f a = f a

extract :: (Enum a) => [a] -> CInt -> [a]
extract allCodes compoundCode
    = map cToEnum $
      filter (masks compoundCode) $
      map cFromEnum allCodes

combine :: (Enum a, Ord a) => [a] -> Int
combine = sum . map (fromEnum . head) . group . sort

masks :: CInt -> CInt -> Bool
masks compoundCode code = (compoundCode .&. code) == code

keySequenceToCInt :: [EventState] -> KeyType -> CInt
keySequenceToCInt modifiers char =
  let charCode = case char of
        SpecialKeyType c' -> fromIntegral $ fromEnum c'
        NormalKeyType c' -> fromIntegral $ castCharToCChar c'
    in (fromIntegral $ combine modifiers) + charCode

cIntToKeySequence :: CInt -> Maybe ShortcutKeySequence
cIntToKeySequence i =
  let evs = extract allEventStates i
      masked = (i .&. (fromIntegral $ fromEnum Kb_KeyMask))
      special = map cToEnum $ filter ((==) masked) allShortcutSpecialKeys
  in
    if (i == 0)
    then Nothing
    else if (null special)
         then Just (ShortcutKeySequence evs (NormalKeyType $ toEnum $ fromIntegral masked))
         else Just (ShortcutKeySequence evs (SpecialKeyType $ head special))

#ifdef CALLSTACK_AVAILABLE
wrapNonNull :: (?loc :: CallStack) => Ptr a -> String -> IO (ForeignPtr (Ptr a))
#elif defined(HASCALLSTACK_AVAILABLE)
wrapNonNull :: (HasCallStack) => Ptr a -> String -> IO (ForeignPtr (Ptr a))
#else
wrapNonNull :: Ptr a -> String -> IO (ForeignPtr (Ptr a))
#endif
wrapNonNull ptr msg = if (ptr == nullPtr)
                      then error msg
                      else do
                        pptr <- malloc
                        poke pptr ptr
                        FC.newForeignPtr pptr (free pptr)


toGlobalEventHandlerPrim :: GlobalEventHandlerF -> IO (FunPtr GlobalEventHandlerPrim)
toGlobalEventHandlerPrim f = mkGlobalEventHandlerPtr
                             (\eventNumber ->
                                let event = cToEnum (eventNumber :: CInt)
                                in f event >>= return . fromIntegral)

toGlobalCallbackPrim :: GlobalCallback -> IO (FunPtr CallbackPrim)
toGlobalCallbackPrim f = mkCallbackPtr (\_ -> f)

toDrawCallback :: DrawCallback -> IO (FunPtr DrawCallbackPrim)
toDrawCallback f = mkDrawCallbackPrimPtr
                   (\string' length' x' y' -> do
                      str' <- TF.peekCStringLen (string', fromIntegral length')
                      f str' (Position (X (fromIntegral x')) (Y (fromIntegral y'))))

toBoxDrawF :: BoxDrawFPrim -> BoxDrawF
toBoxDrawF boxDrawPrim =
    (\r c ->
       let (x_pos,y_pos,width,height) = fromRectangle r
           colorPrim = cFromColor c
       in
         boxDrawPrim ((fromIntegral x_pos) :: CInt)
                     ((fromIntegral y_pos) :: CInt)
                     ((fromIntegral width) :: CInt)
                     ((fromIntegral height) :: CInt)
                     colorPrim
    )

toBoxDrawFPrim :: BoxDrawF -> BoxDrawFPrim
toBoxDrawFPrim f =
    (\xPrim yPrim wPrim hPrim colorPrim ->
       let r = toRectangle (fromIntegral xPrim,
                            fromIntegral yPrim,
                            fromIntegral wPrim,
                            fromIntegral hPrim)
           c = cToColor colorPrim
       in
           f r c)

toTextModifyCbPrim :: TextModifyCb -> IO (FunPtr TextModifyCbPrim)
toTextModifyCbPrim f =
  mkTextModifyCb
    (
      \pos' nInserted' nDeleted' nRestyled' stringPtr _ ->
       cStringToText stringPtr >>= \deletedText ->
       f (AtIndex (fromIntegral pos'))
         (NumInserted (fromIntegral nInserted'))
         (NumDeleted (fromIntegral nDeleted'))
         (NumRestyled (fromIntegral nRestyled'))
         (DeletedText deletedText)
    )

toTextPredeleteCbPrim :: TextPredeleteCb -> IO (FunPtr TextPredeleteCbPrim)
toTextPredeleteCbPrim f =
  mkTextPredeleteCb
    (
      \pos' nDeleted' _ ->
       f (AtIndex (fromIntegral pos')) (NumDeleted (fromIntegral nDeleted'))
    )

toFDHandlerPrim :: FDHandler -> IO (FunPtr FDHandlerPrim)
toFDHandlerPrim f = mkFDHandlerPrim (\fd _ -> f (FlSocket fd))

toUnfinishedStyleCbPrim :: UnfinishedStyleCb -> IO (FunPtr UnfinishedStyleCbPrim)
toUnfinishedStyleCbPrim f =
    mkUnfinishedStyleCbPrim
     (
       \pos' _ -> f (AtIndex (fromIntegral pos'))
     )

orNullFunPtr :: (a -> IO (FunPtr b)) -> Maybe a -> IO (FunPtr b)
orNullFunPtr = maybe (return nullFunPtr)

arrayToRefs:: (Ptr (Ptr ())) -> Int -> IO [(Ref a)]
arrayToRefs arrayPtr numElements =
    go arrayPtr numElements []
    where
      go _ 0 accum =  return accum
      go currPtr numLeft accum = do
        curr <- peek currPtr
        ref <- toRef curr
        go (currPtr `plusPtr` (sizeOf (undefined :: Ptr (Ptr a))))
           (numLeft - 1)
           (accum ++ [ref])

staticArrayToRefs:: (Ptr ()) -> Int -> IO [(Ref a)]
staticArrayToRefs arrayPtr numElements =
    go arrayPtr numElements []
    where
      go _ 0 accum =  return accum
      go currPtr numLeft accum = do
        let nextPtr = currPtr `plusPtr` (sizeOf (undefined :: Ptr a))
        ref <- toRef currPtr
        go nextPtr
           (numLeft - 1)
           (accum ++ [ref])

refOrError :: String -> Ptr () -> IO (Ref b)
refOrError errorMessage p = wrapNonNull p errorMessage >>=
                               return . wrapInRef
toShortcut :: [KeyType] -> FlShortcut
toShortcut =
  fromIntegral .
  sum .
  map
   (\k -> case k of
     (SpecialKeyType sk') -> fromEnum sk'
     (NormalKeyType c') -> fromEnum c'
   )

cToKeyType :: CInt -> KeyType
cToKeyType cint =
  let findSpecialKey = find
                        (\sk -> cint == (fromIntegral $ fromEnum sk))
                        allSpecialKeys
  in
  case findSpecialKey of
    Just sk -> SpecialKeyType sk
    Nothing -> NormalKeyType (toEnum $ fromIntegral cint)

cFromKeyType :: KeyType -> CInt
cFromKeyType kt = case kt of
  SpecialKeyType sk -> fromIntegral $ fromEnum sk
  NormalKeyType nk -> fromIntegral $ fromEnum nk

toRef ::  Ptr () -> IO (Ref a)
toRef ptr = throwStackOnError $
                  do
                    pp <- wrapNonNull ptr "Null Pointer Error"
                    let result = wrapInRef pp
                    return $ result

#ifdef CALLSTACK_AVAILABLE
cStringToText :: (?loc :: CallStack) => CString -> IO T.Text
#elif defined(HASCALLSTACK_AVAILABLE)
cStringToText :: (HasCallStack) => CString -> IO T.Text
#else
cStringToText :: CString -> IO T.Text
#endif
cStringToText = fmap (fromMaybe "") . cStringToMaybeText

#ifdef CALLSTACK_AVAILABLE
cStringToMaybeText :: (?loc :: CallStack) => CString -> IO (Maybe T.Text)
#elif defined(HASCALLSTACK_AVAILABLE)
cStringToMaybeText :: (HasCallStack) => CString -> IO (Maybe T.Text)
#else
cStringToMaybeText :: CString -> IO (Maybe T.Text)
#endif
cStringToMaybeText cstring =
    if (cstring == nullPtr) then return Nothing
    else do
      byteString <- B.packCString cstring
      either (\e -> traceStack (show e) (error ""))
             (return . Just)
             (E.decodeUtf8' byteString)

toMaybeRef :: Ptr () -> IO (Maybe (Ref a))
toMaybeRef ptr' = if ptr' == nullPtr then return Nothing else toRef ptr' >>= return . Just

supressWarningAboutRes :: a -> ()
supressWarningAboutRes _ = ()

foldl1WithDefault :: a -> (a -> a -> a) -> [a] -> a
foldl1WithDefault emptyCase _ [] = emptyCase
foldl1WithDefault _ f as = foldl1 f as

integralToMaybe :: (Integral a, Integral b) => a -> Maybe b
integralToMaybe n = if (n == 0) then Nothing else (Just $ fromIntegral n)

countDirectionToCChar :: CountDirection -> CChar
countDirectionToCChar d =
  case d of
   CountUp -> 1
   CountDown -> 0

ccharToCountDirection :: CChar -> CountDirection
ccharToCountDirection c = if (c == 0) then CountDown else CountUp

oneKb :: Int
oneKb = 1024

alignmentsToInt :: Alignments -> Int
alignmentsToInt (Alignments aligntypes') = combine aligntypes'
intToAlignments :: Int -> Alignments
intToAlignments alignmentCode = Alignments (extract allAlignTypes $ fromIntegral alignmentCode)

menuItemFlagsToInt :: MenuItemFlags -> Int
menuItemFlagsToInt (MenuItemFlags menuItemFlags') = combine menuItemFlags'
intToMenuItemFlags :: Int -> Maybe MenuItemFlags
intToMenuItemFlags flags' =
  if (flags' == 0)
  then Nothing
  else Just $ (MenuItemFlags . extract allMenuItemFlags . fromIntegral) flags'

modesToInt :: Modes -> Int
modesToInt (Modes ms) = combine ms
intToModes :: Int -> Modes
intToModes modeCode = Modes (extract allModes (fromIntegral modeCode))

withPixmap :: PixmapHs -> ((Ptr (Ptr CChar)) -> IO a) -> IO a
withPixmap (PixmapHs strings) f = do
  cStrings <- sequence (map copyTextToCString strings)
  ptr <- newArray cStrings
  f ptr

withBitmap :: BitmapHs -> ((Ptr CChar) -> Int -> Int -> IO a) -> IO a
withBitmap (BitmapHs bitmap (Size (Width width') (Height height'))) f =
   B.useAsCString
     bitmap
     (\ptr -> f ptr width' height')

withStrings :: [T.Text] -> (Ptr (Ptr CChar) -> IO a) -> IO a
withStrings ss f = do
  copies <- mapM
             (
               \s ->
                 B.useAsCStringLen
                   (E.encodeUtf8 s)
                   (
                     \(ptr, len) -> do
                         arrPtr <- mallocArray len
                         copyArray arrPtr ptr len
                         return arrPtr
                   )
             )
             ss
  result <- allocaArray (length copies) f
  mapM_ free copies
  return result

copyByteStringToCString :: B.ByteString -> IO CString
copyByteStringToCString bs =
  B.useAsCStringLen bs
    (\(cstring,len) -> do
        dest <- mallocArray (len+1)
        copyArray dest cstring len
        pokeElemOff dest len (0 :: CChar)
        return dest
    )

copyTextToCString :: T.Text -> IO CString
copyTextToCString t =
  let bs = E.encodeUtf8 t
  in
    copyByteStringToCString bs

withText :: T.Text -> (CString -> IO a) -> IO a
withText t f = B.useAsCString (E.encodeUtf8 t) f

#ifdef CALLSTACK_AVAILABLE
drawShortcutFromC :: (?loc :: CallStack) => CChar -> Maybe DrawShortcut
#elif defined(HASCALLSTACK_AVAILABLE)
drawShortcutFromC :: (HasCallStack) => CChar -> Maybe DrawShortcut
#else
drawShortcutFromC ::  CChar -> Maybe DrawShortcut
#endif
drawShortcutFromC c =
  case c of
    0 -> Nothing
    1 -> Just NormalDrawShortcut
    2 -> Just ElideAmpersandDrawShortcut
    _ -> error "fl_draw_shortcut should be 0,1 or 2."

drawShortcutToC :: Maybe DrawShortcut -> CChar
drawShortcutToC ds =
  case ds of
    Nothing -> 0
    Just NormalDrawShortcut -> 1
    Just ElideAmpersandDrawShortcut -> 2