module Data.Schematic.Verifier.Text where

import Control.Monad
import Data.Maybe
import {-# SOURCE #-} Data.Schematic.Schema
import Data.Schematic.Verifier.Common
import Data.Text (Text, unpack)
import Text.Regex.TDFA.Pattern
import Text.Regex.TDFA.ReadRegex (parseRegex)

toStrictTextLength :: [DemotedTextConstraint] -> [DemotedTextConstraint]
toStrictTextLength = map f
  where
    f (DTLe x) = DTLt (x + 1)
    f (DTGe x) = DTGt (x - 1)
    f x        = x

data VerifiedTextConstraint
  = VTEq Integer
  | VTBounds (Maybe Integer) (Maybe Integer)
  | VTRegex Text Integer (Maybe Integer)
  | VTEnum [Text]
  deriving (Show)

verifyTextLengthConstraints
  :: [DemotedTextConstraint]
  -> Maybe (Maybe VerifiedTextConstraint)
verifyTextLengthConstraints cs' = do
  let
    cs = toStrictTextLength cs'
    mlt = simplifyDNLs [x | DTLt x <- cs]
    mgt = simplifyDNGs [x | DTGt x <- cs]
  meq <- verifyDNEq [x | DTEq x <- cs]
  verifyEquations mgt meq mlt
  case all isNothing ([mgt, meq, mlt] :: [Maybe Integer]) of
    True -> Just Nothing
    _    ->
      Just $
      Just $
      case meq of
        Just eq -> VTEq eq
        Nothing -> VTBounds mgt mlt

regexLength :: Text -> Maybe (Int, Maybe Int)
regexLength regexp =
  case parseRegex . unpack $ regexp of
    Right (p, _) -> Just (minRegexLength p, maxRegexLength p)
    Left _       -> Nothing

minRegexLength :: Pattern -> Int
minRegexLength p =
  case p of
    PEmpty           -> 0
    PChar {..}       -> 1
    PAny {..}        -> 1
    PAnyNot {..}     -> 1
    PQuest _         -> 0
    PPlus sch        -> minRegexLength $ PBound 1 Nothing sch
    PStar _ sch      -> minRegexLength $ PBound 0 Nothing sch
    PBound low _ sch -> low * minRegexLength sch
    PConcat ps       -> sum $ fmap minRegexLength ps
    POr xs           -> minimum $ fmap minRegexLength xs
    PDot _           -> 1
    PEscape {..}     -> 1
    PCarat _         -> 0
    PDollar _        -> 0
    _                -> 0

maxRegexLength :: Pattern -> Maybe Int
maxRegexLength p =
  case p of
    PEmpty             -> Just 0
    PChar _ _          -> Just 1
    PAny _ _           -> Just 1
    PAnyNot _ _        -> Just 1
    PQuest _           -> Just 0
    PPlus _            -> Nothing
    PStar _ _          -> Nothing
    PBound _ mhigh sch -> (*) <$> mhigh <*> maxRegexLength sch
    PConcat ps         -> sum <$> mapM maxRegexLength ps
    POr xs             -> maximum <$> mapM maxRegexLength xs
    PDot _             -> Just 1
    PEscape _ _        -> Just 1
    PCarat _           -> Just 0
    PDollar _          -> Just 0
    _                  -> Just 0

verifyTextRegexConstraint
  :: [DemotedTextConstraint]
  -> Maybe (Maybe VerifiedTextConstraint)
verifyTextRegexConstraint cs = do
  let regexps = [x | DTRegex x <- cs]
  case regexps of
    []  -> Just Nothing
    [x] -> do
      (l, mh) <- regexLength x
      Just $ Just $ VTRegex x (fromIntegral l) (fromIntegral <$> mh)
    _   -> Nothing

verifyTextEnumConstraint
  :: [DemotedTextConstraint]
  -> Maybe (Maybe VerifiedTextConstraint)
verifyTextEnumConstraint cs = do
  let enums = concat [x | DTEnum x <- cs]
  case enums of
    [] -> Just Nothing
    x  -> Just $ Just $ VTEnum x

verifyTextConstraints
  :: [DemotedTextConstraint]
  -> Maybe [VerifiedTextConstraint]
verifyTextConstraints cs = do
  regexp <- verifyTextRegexConstraint cs
  void $
    case regexp of
      Just (VTRegex _ l mh) ->
        verifyTextLengthConstraints (DTGe l : cs ++ maybeToList (DTLe <$> mh))
      _                     -> pure Nothing
  lengths <- verifyTextLengthConstraints cs
  enums <- verifyTextEnumConstraint cs
  return $ catMaybes [lengths, enums, regexp]