{-# LANGUAGE Arrows #-}
# LANGUAGE LambdaCase #
# LANGUAGE FlexibleContexts #
# LANGUAGE FlexibleInstances #
{-# LANGUAGE Rank2Types #-}
# LANGUAGE StandaloneDeriving #
{-# LANGUAGE TypeSynonymInstances #-}

module Opaleye.Test.Fields where

import           Prelude hiding (compare)

import           Wrapped (constructor, asSumProfunctor,
                          constructorDecidable, asDecidable)

import qualified Opaleye as O
import qualified Opaleye.Internal.MaybeFields as OM
import qualified Opaleye.Internal.Values as OV

import           Control.Arrow ((>>>))
import           Control.Applicative (pure)
import qualified Data.Profunctor.Product.Default as D
import qualified Data.Profunctor as P
import qualified Data.Profunctor.Product as PP
import qualified Data.Functor.Contravariant.Divisible as Divisible
import qualified Data.Maybe as Maybe

data Choice i b s = CInt i | CBool b | CString s deriving (Show, Eq, Ord)

chooseChoice :: Divisible.Decidable f
             => (a -> Choice i b s) -> f i -> f b -> f s -> f a
chooseChoice choose fi fb fs = asDecidable $ proc a -> case choose a of
  CInt i    -> constructorDecidable fi -< i
  CBool b   -> constructorDecidable fb -< b
  CString s -> constructorDecidable fs -< s

newtype Choices m i b s =
  Choices { unChoices :: [Either (Choice i b s) (m (Choices m i b s))] }

deriving instance Show Haskells
deriving instance Eq Haskells
deriving instance Ord Haskells

type SimpleField = Choice (O.Field O.SqlInt4)
                          (O.Field O.SqlBool)
                          (O.Field O.SqlText)
type Fields = Choices O.MaybeFields (O.Field O.SqlInt4)
                                    (O.Field O.SqlBool)
                                    (O.Field O.SqlText)
type Haskells = Choices Maybe Int Bool String

emptyChoices :: Choices m i b s
emptyChoices = Choices []

appendChoices :: Choices m i b s -> Choices m i b s -> Choices m i b s
appendChoices c1 c2 = Choices (unChoices c1 ++ unChoices c2)

ppChoices :: (PP.SumProfunctor p, PP.ProductProfunctor p)
          => p (Choice i b s) (Choice i' b' s')
          -> (p (Choices m i b s) (Choices m' i' b' s')
             -> p (m (Choices m i b s)) (m' (Choices m' i' b' s')))
          -> p (Choices m i b s) (Choices m' i' b' s')
ppChoices p f = ps
  where ps = P.dimap unChoices Choices (PP.list (p PP.+++! f ps))

fieldsOfHaskells :: Haskells -> Fields
fieldsOfHaskells = O.toFieldsExplicit toFieldsFields

fieldsOfMaybeHaskells :: Maybe Haskells -> O.MaybeFields Fields
fieldsOfMaybeHaskells =
  O.toFieldsExplicit (O.toFieldsMaybeFields
                        (Choices <$> O.nullspecList)
                        toFieldsFields)

fieldsList :: Functor m => (a, b, m s) -> Choices m a b s
fieldsList (x, y, ms) =
  Choices [ Left (CInt x),
            Left (CBool y),
            Right (fmap (Choices . pure . Left . CString) ms)
          ]

type FieldsTuple = (O.Field O.SqlInt4,
                    O.Field O.SqlBool,
                    O.MaybeFields (O.Field O.SqlText))
type HaskellsTuple = (Int, Bool, Maybe String)

listFieldsG :: Functor m
            => Choices m i b s -> i -> b -> s -> m s -> (i, b, m s)
listFieldsG f i b s ms = (fst (firstIntOr i f),
                          fst (firstBoolOrTrue b f),
                          ms')
  where ms' = maybe ms (fmap (fst . firstStringOr s)) (firstMaybe f)

listFields :: Fields -> FieldsTuple
listFields f =
  listFieldsG f 1 (O.sqlBool True) (O.sqlString "xyz") O.nothingFields

listHaskells :: Haskells -> HaskellsTuple
listHaskells f = listFieldsG f 1 True "xyz" Nothing

fieldsToMaybeFields :: Applicative m => Choices m i b s -> m (Choices m i b s)
fieldsToMaybeFields fs = case Maybe.listToMaybe (subMaybeFields fs) of
  Nothing -> pure fs
  Just x  -> x

unpackFields :: O.Unpackspec Fields Fields
unpackFields = defChoicesPP O.unpackspecMaybeFields

distinctNullsFields :: OM.WithNulls O.Distinctspec Fields Fields
distinctNullsFields =
  ppChoices defChoicePP (OM.mapMaybeFieldsWithNulls D.def)

distinctFields :: O.Distinctspec Fields Fields
distinctFields = P.dimap unChoices Choices (PP.list
    (defChoicePP PP.+++! OM.unWithNulls D.def distinctNullsFields))

nullspecFields :: O.Nullspec a Fields
nullspecFields = fmap Choices O.nullspecList

fromFieldsFields :: O.FromFields Fields Haskells
fromFieldsFields = defChoicesPP O.fromFieldsMaybeFields

toFieldsFields :: O.ToFields Haskells Fields
toFieldsFields =
  defChoicesPP (O.toFieldsMaybeFields (fmap Choices OV.nullspecList))

choicePP :: PP.SumProfunctor p
         => p i1 i2 -> p b1 b2 -> p s1 s2
         -> p (Choice i1 b1 s1) (Choice i2 b2 s2)
choicePP p1 p2 p3 = asSumProfunctor $ proc choice -> case choice of
  CInt i    -> constructor CInt    p1 -< i
  CBool b   -> constructor CBool   p2 -< b
  CString s -> constructor CString p3 -< s

defChoicesPP :: (D.Default p a a', D.Default p b b', D.Default p s s',
                 PP.SumProfunctor p, PP.ProductProfunctor p)
             => (p (Choices m a b s) (Choices m' a' b' s')
                -> p (m (Choices m a b s)) (m' (Choices m' a' b' s')))
             -> p (Choices m a b s) (Choices m' a' b' s')
defChoicesPP = ppChoices defChoicePP

defChoicePP :: (D.Default p a a', D.Default p b b', D.Default p s s',
                PP.SumProfunctor p, PP.ProductProfunctor p)
            => p (Choice a b s) (Choice a' b' s')
defChoicePP = choicePP D.def D.def D.def

  We could try to be clever and look inside the  , but this
-- will do for now.
firstBoolOrTrue :: b -> Choices m a b s -> (b, Choices m a b s)
firstBoolOrTrue true c = (b, c)
  where b = case Maybe.mapMaybe (either isBool (const Nothing)) (unChoices c) of
          []    -> true
          (x:_) -> x

  We could try to be clever and look inside the  , but this
-- will do for now.
firstIntOr :: a -> Choices m a b s -> (a, Choices m a b s)
firstIntOr else_ c = (b, c)
  where b = case Maybe.mapMaybe (either isInt (const Nothing)) (unChoices c) of
          []    -> else_
          (x:_) -> x

  We could try to be clever and look inside the  , but this
-- will do for now.
firstStringOr :: s -> Choices m a b s -> (s, Choices m a b s)
firstStringOr else_ c = (b, c)
  where b = case Maybe.mapMaybe (either isString (const Nothing)) (unChoices c) of
          []    -> else_
          (x:_) -> x

firstMaybe :: Choices m a b s
           -> Maybe (m (Choices m a b s))
firstMaybe c = case Maybe.mapMaybe (either (const Nothing) Just) (unChoices c) of
          []    -> Nothing
          (x:_) -> Just x

isBool :: Choice a b s -> Maybe b
isBool (CInt _)  = Nothing
isBool (CBool l) = Just l
isBool (CString _) = Nothing

isInt :: Choice a b s -> Maybe a
isInt (CInt a)  = Just a
isInt (CBool _) = Nothing
isInt (CString _) = Nothing

isString :: Choice a b s -> Maybe s
isString (CInt _)  = Nothing
isString (CBool _) = Nothing
isString (CString s) = Just s

pairColumns :: Choices m i b s -> (Choices m i b s, Choices m i b s)
pairColumns cs = (evens cs, odds cs)

unpairColums :: (Choices m i b s, Choices m i b s) -> Choices m i b s
unpairColums = uncurry appendChoices

odds :: Choices m i b s -> Choices m i b s
odds (Choices [])     = Choices []
odds (Choices (x:xs)) = Choices (x : unChoices (evens (Choices xs)))

evens :: Choices m i b s -> Choices m i b s
evens (Choices [])     = Choices []
evens (Choices (_:xs)) = odds (Choices xs)

subMaybeFields :: Choices m i b s -> [m (Choices m i b s)]
subMaybeFields = unChoices >>> Maybe.mapMaybe (\case Left _  -> Nothing
                                                     Right r -> Just r)

# LANGUAGE Arrows #
# LANGUAGE Rank2Types #
# LANGUAGE TypeSynonymInstances #
 will do for now.
 will do for now.
 will do for now.
# LANGUAGE LambdaCase #
# LANGUAGE FlexibleContexts #
# LANGUAGE FlexibleInstances #
# LANGUAGE StandaloneDeriving #

module Opaleye.Test.Fields where

import           Prelude hiding (compare)

import           Wrapped (constructor, asSumProfunctor,
                          constructorDecidable, asDecidable)

import qualified Opaleye as O
import qualified Opaleye.Internal.MaybeFields as OM
import qualified Opaleye.Internal.Values as OV

import           Control.Arrow ((>>>))
import           Control.Applicative (pure)
import qualified Data.Profunctor.Product.Default as D
import qualified Data.Profunctor as P
import qualified Data.Profunctor.Product as PP
import qualified Data.Functor.Contravariant.Divisible as Divisible
import qualified Data.Maybe as Maybe

data Choice i b s = CInt i | CBool b | CString s deriving (Show, Eq, Ord)

chooseChoice :: Divisible.Decidable f
             => (a -> Choice i b s) -> f i -> f b -> f s -> f a
chooseChoice choose fi fb fs = asDecidable $ proc a -> case choose a of
  CInt i    -> constructorDecidable fi -< i
  CBool b   -> constructorDecidable fb -< b
  CString s -> constructorDecidable fs -< s

newtype Choices m i b s =
  Choices { unChoices :: [Either (Choice i b s) (m (Choices m i b s))] }

deriving instance Show Haskells
deriving instance Eq Haskells
deriving instance Ord Haskells

type SimpleField = Choice (O.Field O.SqlInt4)
                          (O.Field O.SqlBool)
                          (O.Field O.SqlText)
type Fields = Choices O.MaybeFields (O.Field O.SqlInt4)
                                    (O.Field O.SqlBool)
                                    (O.Field O.SqlText)
type Haskells = Choices Maybe Int Bool String

emptyChoices :: Choices m i b s
emptyChoices = Choices []

appendChoices :: Choices m i b s -> Choices m i b s -> Choices m i b s
appendChoices c1 c2 = Choices (unChoices c1 ++ unChoices c2)

ppChoices :: (PP.SumProfunctor p, PP.ProductProfunctor p)
          => p (Choice i b s) (Choice i' b' s')
          -> (p (Choices m i b s) (Choices m' i' b' s')
             -> p (m (Choices m i b s)) (m' (Choices m' i' b' s')))
          -> p (Choices m i b s) (Choices m' i' b' s')
ppChoices p f = ps
  where ps = P.dimap unChoices Choices (PP.list (p PP.+++! f ps))

fieldsOfHaskells :: Haskells -> Fields
fieldsOfHaskells = O.toFieldsExplicit toFieldsFields

fieldsOfMaybeHaskells :: Maybe Haskells -> O.MaybeFields Fields
fieldsOfMaybeHaskells =
  O.toFieldsExplicit (O.toFieldsMaybeFields
                        (Choices <$> O.nullspecList)
                        toFieldsFields)

fieldsList :: Functor m => (a, b, m s) -> Choices m a b s
fieldsList (x, y, ms) =
  Choices [ Left (CInt x),
            Left (CBool y),
            Right (fmap (Choices . pure . Left . CString) ms)
          ]

type FieldsTuple = (O.Field O.SqlInt4,
                    O.Field O.SqlBool,
                    O.MaybeFields (O.Field O.SqlText))
type HaskellsTuple = (Int, Bool, Maybe String)

listFieldsG :: Functor m
            => Choices m i b s -> i -> b -> s -> m s -> (i, b, m s)
listFieldsG f i b s ms = (fst (firstIntOr i f),
                          fst (firstBoolOrTrue b f),
                          ms')
  where ms' = maybe ms (fmap (fst . firstStringOr s)) (firstMaybe f)

listFields :: Fields -> FieldsTuple
listFields f =
  listFieldsG f 1 (O.sqlBool True) (O.sqlString "xyz") O.nothingFields

listHaskells :: Haskells -> HaskellsTuple
listHaskells f = listFieldsG f 1 True "xyz" Nothing

fieldsToMaybeFields :: Applicative m => Choices m i b s -> m (Choices m i b s)
fieldsToMaybeFields fs = case Maybe.listToMaybe (subMaybeFields fs) of
  Nothing -> pure fs
  Just x  -> x

unpackFields :: O.Unpackspec Fields Fields
unpackFields = defChoicesPP O.unpackspecMaybeFields

distinctNullsFields :: OM.WithNulls O.Distinctspec Fields Fields
distinctNullsFields =
  ppChoices defChoicePP (OM.mapMaybeFieldsWithNulls D.def)

distinctFields :: O.Distinctspec Fields Fields
distinctFields = P.dimap unChoices Choices (PP.list
    (defChoicePP PP.+++! OM.unWithNulls D.def distinctNullsFields))

nullspecFields :: O.Nullspec a Fields
nullspecFields = fmap Choices O.nullspecList

fromFieldsFields :: O.FromFields Fields Haskells
fromFieldsFields = defChoicesPP O.fromFieldsMaybeFields

toFieldsFields :: O.ToFields Haskells Fields
toFieldsFields =
  defChoicesPP (O.toFieldsMaybeFields (fmap Choices OV.nullspecList))

choicePP :: PP.SumProfunctor p
         => p i1 i2 -> p b1 b2 -> p s1 s2
         -> p (Choice i1 b1 s1) (Choice i2 b2 s2)
choicePP p1 p2 p3 = asSumProfunctor $ proc choice -> case choice of
  CInt i    -> constructor CInt    p1 -< i
  CBool b   -> constructor CBool   p2 -< b
  CString s -> constructor CString p3 -< s

defChoicesPP :: (D.Default p a a', D.Default p b b', D.Default p s s',
                 PP.SumProfunctor p, PP.ProductProfunctor p)
             => (p (Choices m a b s) (Choices m' a' b' s')
                -> p (m (Choices m a b s)) (m' (Choices m' a' b' s')))
             -> p (Choices m a b s) (Choices m' a' b' s')
defChoicesPP = ppChoices defChoicePP

defChoicePP :: (D.Default p a a', D.Default p b b', D.Default p s s',
                PP.SumProfunctor p, PP.ProductProfunctor p)
            => p (Choice a b s) (Choice a' b' s')
defChoicePP = choicePP D.def D.def D.def

  We could try to be clever and look inside the  , but this
firstBoolOrTrue :: b -> Choices m a b s -> (b, Choices m a b s)
firstBoolOrTrue true c = (b, c)
  where b = case Maybe.mapMaybe (either isBool (const Nothing)) (unChoices c) of
          []    -> true
          (x:_) -> x

  We could try to be clever and look inside the  , but this
firstIntOr :: a -> Choices m a b s -> (a, Choices m a b s)
firstIntOr else_ c = (b, c)
  where b = case Maybe.mapMaybe (either isInt (const Nothing)) (unChoices c) of
          []    -> else_
          (x:_) -> x

  We could try to be clever and look inside the  , but this
firstStringOr :: s -> Choices m a b s -> (s, Choices m a b s)
firstStringOr else_ c = (b, c)
  where b = case Maybe.mapMaybe (either isString (const Nothing)) (unChoices c) of
          []    -> else_
          (x:_) -> x

firstMaybe :: Choices m a b s
           -> Maybe (m (Choices m a b s))
firstMaybe c = case Maybe.mapMaybe (either (const Nothing) Just) (unChoices c) of
          []    -> Nothing
          (x:_) -> Just x

isBool :: Choice a b s -> Maybe b
isBool (CInt _)  = Nothing
isBool (CBool l) = Just l
isBool (CString _) = Nothing

isInt :: Choice a b s -> Maybe a
isInt (CInt a)  = Just a
isInt (CBool _) = Nothing
isInt (CString _) = Nothing

isString :: Choice a b s -> Maybe s
isString (CInt _)  = Nothing
isString (CBool _) = Nothing
isString (CString s) = Just s

pairColumns :: Choices m i b s -> (Choices m i b s, Choices m i b s)
pairColumns cs = (evens cs, odds cs)

unpairColums :: (Choices m i b s, Choices m i b s) -> Choices m i b s
unpairColums = uncurry appendChoices

odds :: Choices m i b s -> Choices m i b s
odds (Choices [])     = Choices []
odds (Choices (x:xs)) = Choices (x : unChoices (evens (Choices xs)))

evens :: Choices m i b s -> Choices m i b s
evens (Choices [])     = Choices []
evens (Choices (_:xs)) = odds (Choices xs)

subMaybeFields :: Choices m i b s -> [m (Choices m i b s)]
subMaybeFields = unChoices >>> Maybe.mapMaybe (\case Left _  -> Nothing
                                                     Right r -> Just r)
