{-# LANGUAGE OverloadedLists #-}

module Database.Seakale.ToRow
  ( ToRow(..)
  ) where

import           GHC.Generics
import           GHC.Int

import           Data.Monoid
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy  as BSL
import qualified Data.Text             as T
import qualified Data.Text.Encoding    as TE
import qualified Data.Text.Lazy        as TL

import           Database.Seakale.Types

class ToRow backend n a | a -> n where
  toRow :: backend -> a -> QueryData n

  default toRow :: (Generic a, GToRow backend WithCon n (Rep a))
                => backend -> a -> QueryData n
  toRow backend =
    fst . gtoRow backend WithCon . toValueProxy backend WithCon . Just . from

data WithCon    = WithCon
data WithoutCon = WithoutCon

class GToRow backend con n f | f -> n where
  data ValueProxy con f a
  toValueProxy :: backend -> con -> Maybe (f a) -> ValueProxy con f a
  gtoRow :: backend -> con -> ValueProxy con f a -> (QueryData n, Maybe String)

instance GToRow backend con Zero V1 where
  data ValueProxy con V1 a = ProxyV1
  toValueProxy _ _ _ = ProxyV1
  gtoRow _ _ _ = (Nil, Nothing)

instance GToRow backend con Zero U1 where
  data ValueProxy con U1 a = ProxyU1
  toValueProxy _ _ _ = ProxyU1
  gtoRow _ _ _ = (Nil, Nothing)

instance (GToRow backend con n a, GToRow backend con m b, (n :+ m) ~ i)
  => GToRow backend con i (a :*: b) where

  data ValueProxy con (a :*: b) c
    = ProxyProduct (ValueProxy con a c) (ValueProxy con b c)

  toValueProxy backend con = \case
    Just (x :*: y) -> ProxyProduct (toValueProxy backend con (Just x))
                                   (toValueProxy backend con (Just y))
    Nothing        -> ProxyProduct (toValueProxy backend con Nothing)
                                   (toValueProxy backend con Nothing)

  gtoRow backend con (ProxyProduct a b) =
    let vec = fst (gtoRow backend con a) `vappend` fst (gtoRow backend con b)
    in (vec, Nothing)

instance ( GToRow backend WithoutCon n a, GToRow backend WithoutCon m b
         , (n :+ m) ~ i ) => GToRow backend WithoutCon i (a :+: b) where

  data ValueProxy WithoutCon (a :+: b) c
    = ProxySumNone (ValueProxy WithoutCon a c) (ValueProxy WithoutCon b c)
    | ProxySumLeft (a c) (ValueProxy WithoutCon b c)
    | ProxySumRight (ValueProxy WithoutCon a c) (b c)

  toValueProxy backend con = \case
    Just (L1 x) -> ProxySumLeft x (toValueProxy backend con Nothing)
    Just (R1 x) -> ProxySumRight (toValueProxy backend con Nothing) x
    Nothing     -> ProxySumNone (toValueProxy backend con Nothing)
                                (toValueProxy backend con Nothing)

  gtoRow backend con = \case
    ProxySumNone proxyA proxyB ->
      let vec = fst (gtoRow backend con proxyA)
                `vappend` fst (gtoRow backend con proxyB)
      in (vec, Nothing)
    ProxySumLeft a proxyB ->
      let (vecA, mConName) =
            gtoRow backend con (toValueProxy backend con (Just a))
          vec = vecA `vappend` fst (gtoRow backend con proxyB)
      in (vec, mConName)
    ProxySumRight proxyA b ->
      let (vecB, mConName) =
            gtoRow backend con (toValueProxy backend con (Just b))
          vec = fst (gtoRow backend con proxyA) `vappend` vecB
      in (vec, mConName)

instance ( GToRow backend WithoutCon n a, GToRow backend WithoutCon m b
         , 'S (n :+ m) ~ i ) => GToRow backend WithCon i (a :+: b) where

  data ValueProxy WithCon (a :+: b) c
    = ProxySumCon (ValueProxy WithoutCon (a :+: b) c)

  toValueProxy backend _ = ProxySumCon . toValueProxy backend WithoutCon

  gtoRow backend WithCon (ProxySumCon proxy) =
    case gtoRow backend WithoutCon proxy of
      (_, Nothing) -> error "GToRow _ WithCon _ (_ :+: _): no constructor name"
      (vec, Just name) -> (Cons (formatString (BS.pack name)) vec, Nothing)

instance (Backend backend, NTimes (Vector n), ToRow backend n a)
  => GToRow backend con n (K1 i a) where
  data ValueProxy con (K1 i a) b = ProxyConst (Maybe a)
  toValueProxy _ _ = ProxyConst . fmap unK1
  gtoRow backend _ = (,Nothing) . \case
    ProxyConst Nothing  -> ntimes "NULL"
    ProxyConst (Just x) -> toRow backend x

instance (Constructor c, GToRow backend con n a)
  => GToRow backend con n (M1 C c a) where

  data ValueProxy con (M1 C c a) b
    = ProxyMetaC (ValueProxy con a b) (Maybe String)

  toValueProxy backend con mM1 =
    ProxyMetaC (toValueProxy backend con (fmap unM1 mM1))
               (fmap conName mM1)

  gtoRow backend con (ProxyMetaC vp mConName) =
    let vec = fst $ gtoRow backend con vp
    in (vec, mConName)

instance GToRow backend con n a => GToRow backend con n (M1 D c a) where
  data ValueProxy con(M1 D c a) b = ProxyMetaD (ValueProxy con a b)
  toValueProxy backend con = ProxyMetaD . toValueProxy backend con . fmap unM1
  gtoRow backend con (ProxyMetaD a) = gtoRow backend con a

instance GToRow backend con n a => GToRow backend con n (M1 S c a) where
  data ValueProxy con (M1 S c a) b = ProxyMetaS (ValueProxy con a b)
  toValueProxy backend con = ProxyMetaS . toValueProxy backend con . fmap unM1
  gtoRow backend con (ProxyMetaS a) = gtoRow backend con a

instance ToRow backend Zero ()

instance ToRow backend One Null where
  toRow _ Null = ["NULL"]

formatString :: BS.ByteString -> BS.ByteString
formatString str = "'" <> escapeQuotes "" str <> "'"
  where
    escapeQuotes :: BS.ByteString -> BS.ByteString -> BS.ByteString
    escapeQuotes _ "" = ""
    escapeQuotes prefix s =
      let (start, end) = fmap (BS.drop 1) $ BS.break (=='\'') s
      in prefix <> start <> escapeQuotes "''" end

instance ToRow backend One BS.ByteString where
  toRow _ s = [formatString s]

instance ToRow backend One BSL.ByteString where
  toRow _ s = [formatString $ BSL.toStrict s]

instance ToRow backend One T.Text where
  toRow _ s = [formatString $ TE.encodeUtf8 s]

instance ToRow backend One TL.Text where
  toRow _ s = [formatString $ TE.encodeUtf8 $ TL.toStrict s]

instance ToRow backend One Int where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Int8 where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Int16 where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Int32 where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Int64 where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Integer where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Double where
  toRow _ n = [BS.pack $ show n]

instance ToRow backend One Float where
  toRow _ n = [BS.pack $ show n]

instance (NTimes (Vector n), Backend backend, ToRow backend n a)
  => ToRow backend n (Maybe a) where
  toRow backend = \case
    Nothing -> ntimes "NULL"
    Just x  -> toRow backend x

instance ( NTimes (Vector k), NTimes (Vector l), Backend backend
         , ToRow backend k a, ToRow backend l b, (k :+ l) ~ i )
  => ToRow backend i (a, b)

instance ( NTimes (Vector k), NTimes (Vector l), NTimes (Vector i)
         , Backend backend, ToRow backend k a, ToRow backend l b
         , ToRow backend i c, (k :+ (l :+ i)) ~ j )
  => ToRow backend j (a, b, c)