{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}

module DearImGui.Structs where

-- base
import Data.Word
  ( Word32
#ifndef IMGUI_USE_WCHAR32
  , Word16
#endif
  )

import Foreign
  ( Storable(..), castPtr, plusPtr, Ptr, Int16, nullPtr )
import Foreign.C
  ( CInt, CBool )

import DearImGui.Enums
import Data.Bits ((.&.))

--------------------------------------------------------------------------------
data ImVec2 = ImVec2 { x, y :: {-# unpack #-} !Float }
  deriving (Show)


instance Storable ImVec2 where
  sizeOf ~ImVec2{x, y} = sizeOf x + sizeOf y

  alignment _ = 0

  poke ptr ImVec2{ x, y } = do
    poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
    poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y

  peek ptr = do
    x <- peek (castPtr ptr                         )
    y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
    return ImVec2{ x, y  }


data ImVec3 = ImVec3 { x, y, z :: {-# unpack #-} !Float }
  deriving (Show)


instance Storable ImVec3 where
  sizeOf ~ImVec3{x, y, z} = sizeOf x + sizeOf y + sizeOf z

  alignment _ = 0

  poke ptr ImVec3{ x, y, z } = do
    poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
    poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
    poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z

  peek ptr = do
    x <- peek (castPtr ptr                         )
    y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
    z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
    return ImVec3{ x, y, z }


data ImVec4 = ImVec4 { x, y, z, w :: {-# unpack #-} !Float }
  deriving (Show)


instance Storable ImVec4 where
  sizeOf ~ImVec4{x, y, z, w} = sizeOf x + sizeOf y + sizeOf z + sizeOf w

  alignment _ = 0

  poke ptr ImVec4{ x, y, z, w } = do
    poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
    poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
    poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z
    poke (castPtr ptr `plusPtr` (sizeOf x * 3)) w

  peek ptr = do
    x <- peek (castPtr ptr                         )
    y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
    z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
    w <- peek (castPtr ptr `plusPtr` (sizeOf x * 3))
    return ImVec4{ x, y, z, w }

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

-- | DearImGui context handle.
data ImGuiContext

-- | Individual font handle.
data ImFont

-- | Font configuration handle.
data ImFontConfig

-- | Glyph ranges builder handle.
data ImFontGlyphRangesBuilder

-- | Opaque DrawList handle.
data ImDrawList

-- | 'DearImGui.Raw.ListClipper.ListClipper' pointer tag.
data ImGuiListClipper

-- | A unique ID used by widgets (typically the result of hashing a stack of string)
--   unsigned Integer (same as ImU32)
type ImGuiID = ImU32

-- | 32-bit unsigned integer (often used to store packed colors).
type ImU32 = Word32

type ImS16 = Int16

-- | Single wide character (used mostly in glyph management)
#ifdef IMGUI_USE_WCHAR32
type ImWchar = Word32
#else
type ImWchar = Word16
#endif

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

-- | Sorting specifications for a table (often handling sort specs for a single column, occasionally more)
--   Obtained by calling TableGetSortSpecs().
--   When @SpecsDirty == true@ you can sort your data. It will be true with sorting specs have changed since last call, or the first time.
--   Make sure to set @SpecsDirty = false@ after sorting, else you may wastefully sort your data every frame!
data ImGuiTableSortSpecs = ImGuiTableSortSpecs
  { specs      :: Ptr ImGuiTableColumnSortSpecs
  , specsCount :: CInt
  , specsDirty :: CBool
  } deriving (Show, Eq)

instance Storable ImGuiTableSortSpecs where
  sizeOf _ =
    sizeOf (undefined :: Ptr ImGuiTableColumnSortSpecs) +
    sizeOf (undefined :: CInt) +
    sizeOf (undefined :: CBool)

  alignment _ =
    alignment nullPtr

  poke ptr ImGuiTableSortSpecs{..} = do
    let specsPtr = castPtr ptr
    poke specsPtr specs

    let specsCountPtr = castPtr $ specsPtr `plusPtr` sizeOf specs
    poke specsCountPtr specsCount

    let specsDirtyPtr = castPtr $ specsCountPtr `plusPtr` sizeOf specsCount
    poke specsDirtyPtr specsDirty

  peek ptr = do
    let specsPtr = castPtr ptr
    specs <- peek specsPtr

    let specsCountPtr = castPtr $ specsPtr `plusPtr` sizeOf specs
    specsCount <- peek specsCountPtr

    let specsDirtyPtr = castPtr $ specsCountPtr `plusPtr` sizeOf specsCount
    specsDirty <- peek specsDirtyPtr

    pure ImGuiTableSortSpecs{..}

-- | Sorting specification for one column of a table
data ImGuiTableColumnSortSpecs = ImGuiTableColumnSortSpecs
  { columnUserID  :: ImGuiID            -- ^ User id of the column (if specified by a TableSetupColumn() call)
  , columnIndex   :: ImS16              -- ^ Index of the column
  , sortOrder     :: ImS16              -- ^ Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here)
  , sortDirection :: ImGuiSortDirection -- ^ 'ImGuiSortDirection_Ascending' or 'ImGuiSortDirection_Descending'
  } deriving (Show, Eq)

instance Storable ImGuiTableColumnSortSpecs where
  sizeOf _ = 12
  alignment _ = 4

  poke ptr ImGuiTableColumnSortSpecs{..} = do
    let columnUserIDPtr = castPtr ptr
    poke columnUserIDPtr columnUserID

    let columnIndexPtr = castPtr $ columnUserIDPtr `plusPtr` sizeOf columnUserID
    poke columnIndexPtr columnIndex

    let sortOrderPtr = castPtr $ columnIndexPtr `plusPtr` sizeOf columnIndex
    poke sortOrderPtr sortOrder

    let sortDirectionPtr = castPtr $ sortOrderPtr `plusPtr` sizeOf sortOrder
    poke sortDirectionPtr sortDirection

  peek ptr = do
    let columnUserIDPtr = castPtr ptr
    columnUserID <- peek columnUserIDPtr

    let columnIndexPtr = castPtr $ columnUserIDPtr `plusPtr` sizeOf columnUserID
    columnIndex <- peek columnIndexPtr

    let sortOrderPtr = castPtr $ columnIndexPtr `plusPtr` sizeOf columnIndex
    sortOrder <- peek sortOrderPtr

    let sortDirectionPtr = castPtr $ sortOrderPtr `plusPtr` sizeOf sortOrder
    sortDirection' <- peek sortDirectionPtr :: IO CInt
    -- XXX: Specs struct uses trimmed field: @SortDirection : 8@
    let sortDirection = case sortDirection' .&. 0xFF of
          0 ->
            ImGuiSortDirection_None
          1 ->
            ImGuiSortDirection_Ascending
          2 ->
            ImGuiSortDirection_Descending
          _ ->
            error $ "Unexpected value for ImGuiSortDirection: " <> show sortDirection

    pure ImGuiTableColumnSortSpecs{..}
