{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DefaultSignatures          #-}
{-# LANGUAGE DeriveAnyClass             #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE DeriveTraversable          #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE PartialTypeSignatures      #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}


module Shpadoinkle.Widgets.Types.Form
  ( module Shpadoinkle.Widgets.Types.Form
  ) where


import           Control.Applicative            (Alternative (empty),
                                                 Applicative (liftA2),
                                                 Const (Const, getConst))
import           Control.Monad.Except           (MonadError (..))
import           Data.Aeson                     (FromJSON, ToJSON)
import           Data.Kind                      (Type)
import           Data.String                    (IsString)
import           Data.Text                      (Text)
import           GHC.Generics
#ifdef TESTING
import           Test.QuickCheck                (Arbitrary (..), elements)
#endif

import           Shpadoinkle                    (NFData)
import           Shpadoinkle.Widgets.Types.Core (Hygiene)


data Input a = Input
  { _hygiene :: Hygiene
  , _value   :: a
  } deriving (Eq, Ord, Show, Read, Functor, Traversable, Foldable, Generic, ToJSON, FromJSON, NFData)


#ifdef TESTING
instance Arbitrary a => Arbitrary (Input a) where arbitrary = Input <$> arbitrary <*> arbitrary
#endif


class Control g where
  type Val g a :: Type
  type Val g a = a
  hygiene ::  Applicative f => (Hygiene -> f Hygiene) -> g a -> f (g a)
  value   :: (Applicative f, Ord a) => (Val g a -> f (Val g a)) -> g a -> f (g a)


getValue :: (Ord a, Monoid (Val g a), Control g) => g a -> Val g a
getValue = getConst . value Const


getHygiene :: Control g => g a -> Hygiene
getHygiene = getConst . hygiene Const


instance Control Input where
  hygiene f i = (\h -> i { _hygiene = h }) <$> f (_hygiene i)
  value   f i = (\a -> i { _value   = a }) <$> f (_value i)


instance Applicative Input where
  Input h fa <*> Input h' a = Input (h <> h') (fa a)
  pure = Input mempty


instance Monad Input where
  Input h a >>= f = let Input h' a' = f a in Input (h <> h') a'


instance Semigroup a => Semigroup (Input a) where
  Input h a <> Input h' a' = Input (h <> h') (a <> a')


instance Monoid a => Monoid (Input a) where
  mempty = Input mempty mempty


newtype Placeholder = Placeholder { unPlaceholder :: Text }
  deriving newtype (Eq, Ord, Show, Read, IsString, Semigroup, Monoid, ToJSON, FromJSON)
  deriving stock Generic
  deriving anyclass NFData


data Validated e a = Validated a | Invalid e [e]
  deriving (Eq, Ord, Show, Read, Generic, ToJSON, FromJSON, Functor, Foldable, Traversable, NFData)


instance Semigroup (Validated e a) where
  Validated a <> Validated _   = Validated a
  Validated _ <> x             = x
  x <> Validated _             = x
  Invalid x xs <> Invalid y ys = Invalid x (xs <> (y:ys))


instance Applicative (Validated e) where
  Validated f <*> Validated a = Validated (f a)
  Invalid x xs <*> _          = Invalid x xs
  _ <*> Invalid x xs          = Invalid x xs
  pure = Validated


instance Monad (Validated e) where
  Validated a >>= f  = f a
  Invalid x xs >>= _ = Invalid x xs


instance MonadError e (Validated e) where
  throwError e = Invalid e []
  catchError (Invalid x _) f = f x
  catchError v _             = v


data Status = Edit | Rules | Valid | Errors


type family Field (s :: Status) (e :: Type) (f :: Type -> Type) (x :: Type) :: Type where
  Field 'Valid    _ _ a = a
  Field 'Errors   e _ a = Validated e a
  Field 'Edit     _ f a = f a
  Field 'Rules    e f a = Val f a -> Validated e a


class ValidateG rules edit errs where
  validateg :: rules a -> edit a -> errs a
instance ValidateG U1 U1 U1 where
  validateg _ _ = U1
instance (ValidateG a b c, ValidateG d e f)
  => ValidateG (a :*: d) (b :*: e) (c :*: f) where
  validateg (a :*: b) (c :*: d) = validateg a c :*: validateg b d
instance (ValidateG a b c, ValidateG d e f, Alternative (c :+: f))
  => ValidateG (a :+: d) (b :+: e) (c :+: f) where
  validateg (L1 a) (L1 b) = L1 $ validateg a b
  validateg (R1 a) (R1 b) = R1 $ validateg a b
  validateg _ _           = empty
instance ValidateG a b c
  => ValidateG (M1 i x a) (M1 i' x' b) (M1 i'' x'' c) where
  validateg (M1 a) (M1 b) = M1 $ validateg a b
instance (Control c, Monoid v, Val c a ~ v, Ord a)
  => ValidateG (K1 i (v -> b)) (K1 i' (c a)) (K1 i'' b) where
  validateg (K1 f) (K1 x) = K1 (f $ getValue x)


class ValidG err valid where
  validg :: err a -> Maybe (valid a)
instance ValidG U1 U1 where
  validg _ = Just U1
instance (ValidG a c, ValidG b d) => ValidG (a :*: b) (c :*: d) where
  validg (a :*: b) = liftA2 (:*:) (validg a) (validg b)
instance (ValidG a c, ValidG b d) => ValidG (a :+: b) (c :+: d) where
  validg (L1 a) = L1 <$> validg a
  validg (R1 a) = R1 <$> validg a
instance (ValidG a b) => ValidG (M1 i c a) (M1 i' c' b) where
  validg (M1 a) = M1 <$> validg a
instance ValidG (K1 i (Validated t a)) (K1 i' a) where
  validg (K1 (Validated a)) = Just $ K1 a
  validg _                  = Nothing


class Validate (f :: Status -> Type) where
  validate :: f 'Edit -> f 'Errors
  default validate
    :: Generic (f 'Edit)
    => Generic (f 'Rules)
    => Generic (f 'Errors)
    => ValidateG (Rep (f 'Rules)) (Rep (f 'Edit)) (Rep (f 'Errors))
    => f 'Edit -> f 'Errors
  validate edit = to $ validateg (from (rules @ f)) (from edit)

  getValid :: f 'Errors -> Maybe (f 'Valid)
  default getValid
    :: Generic (f 'Errors)
    => Generic (f 'Valid)
    => ValidG (Rep (f 'Errors)) (Rep (f 'Valid))
    => f 'Errors -> Maybe (f 'Valid)
  getValid = fmap to . validg . from

  rules :: f 'Rules


#ifdef TESTING
instance (Arbitrary a, Arbitrary b) => Arbitrary (Validated a b) where
  arbitrary = do
    (e, es, a) <- (,,) <$> arbitrary <*> arbitrary <*> arbitrary
    elements [ Validated  a, Invalid  e es ]
#endif