{-# language OverloadedStrings, DataKinds, TemplateHaskell #-}
module Scope (scopeTests) where

import Hedgehog

import Control.Lens ((#), has)
import Data.Function ((&))
import Data.Functor (($>))
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Validation (Validation(..), _Success)

import Language.Python.Validate
import Language.Python.DSL
import Language.Python.Optics
import Language.Python.Syntax.Ann
import Language.Python.Syntax.Whitespace

scopeTests :: Group
scopeTests = $$discover

fullyValidate
  :: Statement '[] ()
  -> PropertyT IO
       (Validation
          (NonEmpty (ScopeError ()))
          (Statement '[Scope, Syntax, Indentation] ()))
fullyValidate x =
  case runValidateIndentation $ validateStatementIndentation x of
    Failure errs -> do
      annotateShow (errs :: NonEmpty (IndentationError ()))
      failure
    Success a ->
      case runValidateSyntax (validateStatementSyntax a) of
        Failure errs -> do
          annotateShow (errs :: NonEmpty (SyntaxError ()))
          failure
        Success a' -> pure $ runValidateScope (validateStatementScope a')

prop_scope_1 :: Property
prop_scope_1 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" [p_ "a", p_ "b"]
          [ line_ $ if_ true_ [ line_ (var_ "c" .= 2) ]
          , line_ . return_ $ var_ "a" .+ var_ "b" .+ var_ "c"
          ]
    res <- fullyValidate expr
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "c" []) :| [])

prop_scope_2 :: Property
prop_scope_2 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" [p_ "a", p_ "b"]
          [ line_ (var_ "c" .= 0)
          , line_ $ if_ true_ [ line_ (var_ "c" .= 2) ]
          , line_ . return_ $ var_ "a" .+ var_ "b" .+ var_ "c"
          ]
    res <- fullyValidate expr
    annotateShow res
    assert $ has _Success res

prop_scope_3 :: Property
prop_scope_3 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" [p_ "a", p_ "b"]
          [ line_ . return_ $ var_ "a" .+ var_ "b" .+ var_ "c" ]
    res <- fullyValidate expr
    annotateShow res
    res === Failure (NotInScope (MkIdent (Ann ()) "c" []) :| [])

prop_scope_4 :: Property
prop_scope_4 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" [p_ "a", p_ "b"]
          [ line_ $ def_ "f" [] [ line_ $ def_ "g" [] [ line_ pass_ ] ]
          , line_ $ call_ (var_ "g") []
          ]
    res <- fullyValidate expr
    res === Failure (NotInScope (MkIdent (Ann ()) "g" []) :| [])

prop_scope_5 :: Property
prop_scope_5 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" [p_ "a"]
          [ line_ $ def_ "f" [k_ "b" (var_ "c")] [ line_ pass_ ]
          ]
    res <- fullyValidate expr
    annotateShow res
    res === Failure (NotInScope (MkIdent (Ann ()) "c" []) :| [])

prop_scope_6 :: Property
prop_scope_6 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" []
          [ line_ $
              if_ true_ [ line_ (var_ "x" .= 2) ] &
              else_ [ line_ pass_ ]
          , line_ $ var_ "x"
          ]
    res <- fullyValidate expr
    annotateShow res
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "x" []) :| [])

prop_scope_7 :: Property
prop_scope_7 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" []
          [ line_ $
              if_ true_ [ line_ pass_ ] &
              else_ [ line_ (var_ "x" .= 3) ]
          , line_ $ var_ "x"
          ]
    res <- fullyValidate expr
    annotateShow res
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "x" []) :| [])

prop_scope_8 :: Property
prop_scope_8 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" []
          [ line_ $
              if_ true_ [ line_ pass_ ] &
              else_ [ line_ (var_ "x" .= 3) ]
          , line_ (var_ "x" .= 1)
          , line_ $ var_ "x"
          ]
    res <- fullyValidate expr
    annotateShow res
    (res $> ()) === Success ()

prop_scope_9 :: Property
prop_scope_9 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" []
          [ line_ $ for_ ("x" `in_` [ list_ [li_ $ int_ 1] ]) [ line_ pass_ ]
          , line_ $ var_ "x"
          ]
    res <- fullyValidate expr
    annotateShow res
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "x" []) :| [])

prop_scope_10 :: Property
prop_scope_10 =
  withTests 1 . property $ do
    let
      expr =
        _Fundef #
        def_ "test" []
          [ line_ $ for_ ("x" `in_` [ list_ [li_ $ int_ 1] ]) [ line_ $ var_ "x" ]
          ]
    res <- fullyValidate expr
    annotateShow res
    (res $> ()) === Success ()

prop_scope_11 :: Property
prop_scope_11 =
  withTests 1 . property $ do
    let
      st =
        _Fundef #
        def_ "test" []
          [ line_ ("x" .= 2)
          , line_ $ for_ ("x" `in_` [ list_ [li_ $ int_ 1] ]) [ line_ pass_ ]
          ]
    res <- fullyValidate st
    annotateShow res
    res === Failure (BadShadowing (MkIdent (Ann ()) "x" [Space]) :| [])

prop_scope_12 :: Property
prop_scope_12 =
  withTests 1 . property $ do
    let
      st =
        _If #
        (if_ none_ [line_ (var_ "x" .= 2)] &
         else_ [line_ (var_ "y" .= var_ "x")])
    res <- fullyValidate st
    annotateShow res
    res === Failure (NotInScope (MkIdent (Ann ()) "x" []) :| [])

prop_scope_13 :: Property
prop_scope_13 =
  withTests 1 . property $ do
    let
      st =
        _Fundef #
        def_ "test" []
        [ line_ $
          if_ none_ [line_ (var_ "x" .= 1)] &
          else_ [line_ (var_ "y" .= 2)]
        , line_ $ var_ "x"
        ]
    res <- fullyValidate st
    annotateShow res
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "x" []) :| [])

prop_scope_14 :: Property
prop_scope_14 =
  withTests 1 . property $ do
    let
      st =
        _Fundef #
        def_ "test" []
        [ line_ $
          if_ none_ [line_ (var_ "x" .= 1)] &
          else_ [line_ (var_ "y" .= 2)]
        , line_ $ var_ "y"
        ]
    res <- fullyValidate st
    annotateShow res
    res === Failure (FoundDynamic () (MkIdent (Ann ()) "y" []) :| [])
