-- | Turn the validation functions into actual 'Validator's.
--
-- This is frankly a lot of busywork. It can perhaps be moved into the
-- validator modules themselves once we're sure this is the right design.

module JSONSchema.Validator.Draft4
    ( module JSONSchema.Validator.Draft4
    , module Export
    ) where

import           Import

import qualified Data.HashMap.Strict                as HM
import qualified Data.List.NonEmpty                 as NE

import           JSONSchema.Validator.Draft4.Any    as Export
import           JSONSchema.Validator.Draft4.Array  as Export
import           JSONSchema.Validator.Draft4.Number as Export
import           JSONSchema.Validator.Draft4.Object as Export
import           JSONSchema.Validator.Draft4.String as Export
import           JSONSchema.Validator.Reference     (BaseURI(..), Scope(..))
import           JSONSchema.Validator.Types         (Validator(..))

-- | For internal use.
--
-- Take a validation function, a possibly existing validator, and some data.
-- If the validator is exists and can validate the type of data we have,
-- attempt to do so and return any failures.
run :: FromJSON b => (a -> b -> Maybe c) -> Maybe a -> Value -> [c]
run _ Nothing _  = mempty
run f (Just a) b =
    case fromJSONEither b of
        Left _  -> mempty
        Right c -> maybeToList (f a c)

-- | For internal use.
noEmbedded :: a -> ([b], [b])
noEmbedded = const (mempty, mempty)

--------------------------------------------------
-- * Numbers
--------------------------------------------------

multipleOfValidator :: Validator a (Maybe MultipleOf) MultipleOfInvalid
multipleOfValidator = Validator noEmbedded (run multipleOfVal)

maximumValidator :: Validator a (Maybe Maximum) MaximumInvalid
maximumValidator = Validator noEmbedded (run maximumVal)

minimumValidator :: Validator a (Maybe Minimum) MinimumInvalid
minimumValidator = Validator noEmbedded (run minimumVal)

--------------------------------------------------
-- * Strings
--------------------------------------------------

maxLengthValidator :: Validator a (Maybe MaxLength) MaxLengthInvalid
maxLengthValidator = Validator noEmbedded (run maxLengthVal)

minLengthValidator :: Validator a (Maybe MinLength) MinLengthInvalid
minLengthValidator = Validator noEmbedded (run minLengthVal)

patternValidator :: Validator a (Maybe PatternValidator) PatternInvalid
patternValidator = Validator noEmbedded (run patternVal)

--------------------------------------------------
-- * Arrays
--------------------------------------------------

maxItemsValidator :: Validator a (Maybe MaxItems) MaxItemsInvalid
maxItemsValidator = Validator noEmbedded (run maxItemsVal)

minItemsValidator :: Validator a (Maybe MinItems) MinItemsInvalid
minItemsValidator = Validator noEmbedded (run minItemsVal)

uniqueItemsValidator :: Validator a (Maybe UniqueItems) UniqueItemsInvalid
uniqueItemsValidator = Validator noEmbedded (run uniqueItemsVal)

-- TODO: Add tests to the language agnostic test suite to
-- make sure @"additionalItems"@ subschemas are embedded correctly.
itemsRelatedValidator
    :: (schema -> Value -> [err])
    -> Validator schema (ItemsRelated schema) (ItemsRelatedInvalid err)
itemsRelatedValidator f =
    Validator
        (\a -> ( mempty
               ,  case _irItems a of
                      Just (ItemsObject b) -> pure b
                      Just (ItemsArray cs) -> cs
                      Nothing              -> mempty
               <> case _irAdditional a of
                      Just (AdditionalObject b) -> pure b
                      _                         -> mempty
               ))
        (\a b -> case fromJSONEither b of
                     Left _  -> mempty
                     Right c -> itemsRelatedVal f a c)

--------------------------------------------------
-- * Objects
--------------------------------------------------

maxPropertiesValidator
    :: Validator
           a
           (Maybe MaxProperties)
           MaxPropertiesInvalid
maxPropertiesValidator = Validator noEmbedded (run maxPropertiesVal)

minPropertiesValidator
    :: Validator
           a
           (Maybe MinProperties)
           MinPropertiesInvalid
minPropertiesValidator = Validator noEmbedded (run minPropertiesVal)

requiredValidator :: Validator a (Maybe Required) RequiredInvalid
requiredValidator = Validator noEmbedded (run requiredVal)

dependenciesValidator
    :: (schema -> Value -> [err])
    -> Validator
           schema
           (Maybe (DependenciesValidator schema))
           (DependenciesInvalid err)
dependenciesValidator f =
    Validator
        (maybe mempty ( (\a -> (mempty, a))
                      . catMaybes . fmap checkDependency
                      . HM.elems . _unDependenciesValidator
                      ))
        (run (dependenciesVal f))
  where
    checkDependency :: Dependency schema -> Maybe schema
    checkDependency (PropertyDependency _) = Nothing
    checkDependency (SchemaDependency s)   = Just s

propertiesRelatedValidator
    :: (schema -> Value -> [err])
    -> Validator
           schema
           (PropertiesRelated schema)
           (PropertiesRelatedInvalid err)
propertiesRelatedValidator f =
    Validator
        (\a -> ( mempty
               ,  HM.elems (fromMaybe mempty (_propProperties a))
               <> HM.elems (fromMaybe mempty (_propPattern a))
               <> case _propAdditional a of
                      Just (AdditionalPropertiesObject b) -> [b]
                      _ -> mempty
               ))
        (\a b -> case fromJSONEither b of
                     Left _  -> mempty
                     Right c -> maybeToList (propertiesRelatedVal f a c))

newtype Definitions schema
    = Definitions { _unDefinitions :: HashMap Text schema }
    deriving (Eq, Show)

instance FromJSON schema => FromJSON (Definitions schema) where
    parseJSON = withObject "Definitions" $ \o ->
        Definitions <$> o .: "definitions"

definitionsEmbedded
    :: Validator
           schema
           (Maybe (Definitions schema))
           err
definitionsEmbedded =
    Validator
        (\a -> case a of
                 Just (Definitions b) -> (mempty, HM.elems b)
                 Nothing              -> (mempty, mempty))
        (const (const mempty))

--------------------------------------------------
-- * Any
--------------------------------------------------

refValidator
    :: (FromJSON schema, ToJSON schema)
    => (Text -> Maybe schema)
    -> (BaseURI -> schema -> BaseURI)
    -> (VisitedSchemas -> Scope schema -> schema -> Value -> [err])
    -> VisitedSchemas
    -> Scope schema
    -> Validator a (Maybe Ref) (RefInvalid err)
refValidator getRef updateScope f visited scope =
    Validator
        noEmbedded
        (run (refVal getRef updateScope f visited scope))

enumValidator :: Validator a (Maybe EnumValidator) EnumInvalid
enumValidator = Validator noEmbedded (run enumVal)

typeValidator :: Validator a (Maybe TypeContext) TypeValidatorInvalid
typeValidator = Validator noEmbedded (run typeVal)

allOfValidator
    :: (schema -> Value -> [err])
    -> Validator schema (Maybe (AllOf schema)) (AllOfInvalid err)
allOfValidator f =
    Validator
        (\a -> case a of
                   Just (AllOf b) -> (NE.toList b, mempty)
                   Nothing        -> (mempty, mempty))
        (run (allOfVal f))

anyOfValidator
    :: (schema -> Value -> [err])
    -> Validator schema (Maybe (AnyOf schema)) (AnyOfInvalid err)
anyOfValidator f =
    Validator
        (\a -> case a of
                 Just (AnyOf b) -> (NE.toList b, mempty)
                 Nothing        -> (mempty, mempty))
        (run (anyOfVal f))

oneOfValidator
    :: ToJSON schema
    => (schema -> Value -> [err])
    -> Validator schema (Maybe (OneOf schema)) (OneOfInvalid err)
oneOfValidator f =
    Validator
        (\a -> case a of
                   Just (OneOf b) -> (NE.toList b, mempty)
                   Nothing        -> (mempty, mempty))
        (run (oneOfVal f))

notValidator
    :: ToJSON schema
    => (schema -> Value -> [err])
    -> Validator
           schema
           (Maybe (NotValidator schema))
           NotValidatorInvalid
notValidator f =
    Validator
        (\a -> case a of
                   Just (NotValidator b) -> (pure b, mempty)
                   Nothing               -> (mempty, mempty))
        (run (notVal f))