{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}

module Database.PostgreSQL.Opium.ToField
  ( ToField (..)
  ) where

import Data.Bits (Bits (..))
import Data.ByteString (ByteString)
import Data.List (singleton)
import Data.Text (Text)
import Data.Word (Word32)
import Database.PostgreSQL.LibPQ (Format (..), Oid)
import Unsafe.Coerce (unsafeCoerce)

import qualified Data.ByteString as BS
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Encoding
import qualified Database.PostgreSQL.Opium.Oid as Oid

class ToField a where
  toField :: a -> Maybe (Oid, ByteString, Format)

instance ToField ByteString where
  toField x = Just (Oid.bytea, x, Binary)

instance ToField Text where
  toField x = Just (Oid.text, Encoding.encodeUtf8 x, Binary)

instance ToField String where
  toField = toField . Text.pack

instance ToField Char where
  toField = toField . singleton

-- Potentially slow, but good enough for now
encodeBigEndian :: (Integral a, Bits a) => Int -> a -> ByteString
encodeBigEndian n = BS.pack . go [] n
  where
    go acc 0 _ = acc
    go acc i x = go (fromIntegral (x .&. 0xff) : acc) (i - 1) (x `shiftR` 8)

instance ToField Int where
  toField x = Just (Oid.bigint, encodeBigEndian 8 x, Binary)

instance ToField Float where
  toField x = Just (Oid.real, encodeBigEndian @Word32 4 $ unsafeCoerce x, Binary)

instance ToField Double where
  toField x = Just (Oid.doublePrecision, encodeBigEndian @Word 8 $ unsafeCoerce x, Binary)