{-# LANGUAGE QuasiQuotes #-}

module Main (main) where



#include "prelude.inc"

import Test.Hspec
import Test.Hspec.Runner ( hspecWith, defaultConfig, configConcurrentJobs )

import NeatInterpolation

import qualified Text.Parsec as Parsec
import Text.Parsec.Text ( Parser )

import Data.Char ( isSpace )
import Data.List ( groupBy )

import Language.Haskell.Brittany.Internal

import Language.Haskell.Brittany.Internal.Config.Types
import Language.Haskell.Brittany.Internal.Config

import Data.Coerce ( coerce )

import qualified Data.Text.IO as Text.IO
import           System.FilePath ( (</>) )



data InputLine
  = GroupLine Text
  | HeaderLine Text
  | PendingLine
  | NormalLine Text
  | CommentLine
  deriving Show


main :: IO ()
main = do
  files <- System.Directory.listDirectory "src-literatetests/"
  let blts =
        List.sort
          $ filter (\x -> not ("tests-context-free.blt" `isSuffixOf` x))
          $ filter (".blt"`isSuffixOf`) files
  inputs <- blts `forM` \blt -> Text.IO.readFile ("src-literatetests" </> blt)
  let groups = createChunks =<< inputs
  inputCtxFree <- Text.IO.readFile "src-literatetests/30-tests-context-free.blt"
  let groupsCtxFree = createChunks inputCtxFree
  hspec $ do
    groups `forM_` \(groupname, tests) -> do
      describe (Text.unpack groupname) $ tests `forM_` \(name, pend, inp) -> do
        (if pend then before_ pending else id)
          $ it (Text.unpack name)
          $ roundTripEqual defaultTestConfig inp
    groupsCtxFree `forM_` \(groupname, tests) -> do
      describe ("context free: " ++ Text.unpack groupname)
        $       tests
        `forM_` \(name, pend, inp) -> do
                  (if pend then before_ pending else id)
                    $ it (Text.unpack name)
                    $ roundTripEqual contextFreeTestConfig inp
 where
  -- this function might be implemented in a weirdly complex fashion; the
  -- reason being that it was copied from a somewhat more complex variant.
  createChunks :: Text -> [(Text, [(Text, Bool, Text)])]
  createChunks input =
--    fmap (\case
--        HeaderLine n:PendingLine:rest | Just rlines <- mapM extractNormal rest -> (n, True, Text.unlines rlines)
--        HeaderLine n:rest | Just rlines <- mapM extractNormal rest -> (n, False, Text.unlines rlines)
--        l -> error $ "first non-empty line must start with #test footest\n" ++ show l
--      )
--      $   fmap (groupBy grouperT)
    fmap
        ( \case
          GroupLine g:grouprest ->
            (,) g
              $ fmap
                  ( \case
                    HeaderLine n:PendingLine:rest | Just rlines <- mapM
                      extractNormal
                      rest ->
                      (n, True, Text.unlines rlines)
                    HeaderLine n:rest | Just rlines <- mapM extractNormal rest ->
                      (n, False, Text.unlines rlines)
                    l ->
                      error
                        $ "first non-empty line must start with #test footest\n"
                        ++ show l
                  )
              $ groupBy grouperT
              $ filter (not . lineIsSpace)
              $ grouprest
          l -> error $ "first non-empty line must be a #group\n" ++ show l
        )
      $   groupBy grouperG
      $   filter (not . lineIsSpace)
      $   lineMapper
      <$> Text.lines input
   where
    extractNormal (NormalLine l) = Just l
    extractNormal _              = Nothing
    specialLineParser :: Parser InputLine
    specialLineParser = Parsec.choice
      [ [ GroupLine $ Text.pack name
        | _    <- Parsec.try $ Parsec.string "#group"
        , _    <- Parsec.many1 $ Parsec.oneOf " \t"
        , name <- Parsec.many1 $ Parsec.noneOf "\r\n:"
        , _    <- Parsec.eof
        ]
      , [ HeaderLine $ Text.pack name
        | _    <- Parsec.try $ Parsec.string "#test"
        , _    <- Parsec.many1 $ Parsec.oneOf " \t"
        , name <- Parsec.many1 $ Parsec.noneOf "\r\n:"
        , _    <- Parsec.eof
        ]
      , [ PendingLine
        | _ <- Parsec.try $ Parsec.string "#pending"
        , _ <- Parsec.optional $ many (Parsec.noneOf "\r\n")
        , _ <- Parsec.eof
        ]
      , [ CommentLine
        | _ <- Parsec.many $ Parsec.oneOf " \t"
        , _ <-
          Parsec.optional $ Parsec.string "##" <* many (Parsec.noneOf "\r\n")
        , _ <- Parsec.eof
        ]
      ]
    lineMapper :: Text -> InputLine
    lineMapper line = case Parsec.runParser specialLineParser () "" line of
      Left  _e -> NormalLine line
      Right l  -> l
    lineIsSpace :: InputLine -> Bool
    lineIsSpace CommentLine = True
    lineIsSpace _           = False
    grouperG :: InputLine -> InputLine -> Bool
    grouperG _ GroupLine{} = False
    grouperG _ _           = True
    grouperT :: InputLine -> InputLine -> Bool
    grouperT _ HeaderLine{} = False
    grouperT _ _            = True


--------------------
-- past this line:  copy-pasta from other test (meh..)
--------------------
roundTripEqual :: Config -> Text -> Expectation
roundTripEqual c t =
  fmap (fmap PPTextWrapper)
       (parsePrintModuleTests c "TestFakeFileName.hs" t)
    `shouldReturn` Right (PPTextWrapper t)

newtype PPTextWrapper = PPTextWrapper Text
  deriving Eq

instance Show PPTextWrapper where
  show (PPTextWrapper t) = "\n" ++ Text.unpack t


defaultTestConfig :: Config
defaultTestConfig = Config
  { _conf_version                   = _conf_version staticDefaultConfig
  , _conf_debug                     = _conf_debug staticDefaultConfig
  , _conf_layout                    = LayoutConfig
    { _lconfig_cols                      = coerce (80 :: Int)
    , _lconfig_indentPolicy              = coerce IndentPolicyFree
    , _lconfig_indentAmount              = coerce (2 :: Int)
    , _lconfig_indentWhereSpecial        = coerce True
    , _lconfig_indentListSpecial         = coerce True
    , _lconfig_importColumn              = coerce (60 :: Int)
    , _lconfig_importAsColumn            = coerce (60 :: Int)
    , _lconfig_altChooser                = coerce $ AltChooserBoundedSearch 3
    , _lconfig_columnAlignMode           = coerce (ColumnAlignModeMajority 0.7)
    , _lconfig_alignmentLimit            = coerce (30 :: Int)
    , _lconfig_alignmentBreakOnMultiline = coerce True
    , _lconfig_hangingTypeSignature      = coerce False
    , _lconfig_reformatModulePreamble    = coerce True
    , _lconfig_allowSingleLineExportList = coerce True
    , _lconfig_allowHangingQuasiQuotes   = coerce True
    }
  , _conf_errorHandling             = (_conf_errorHandling staticDefaultConfig)
    { _econf_omit_output_valid_check = coerce True
    }
  , _conf_preprocessor              = _conf_preprocessor staticDefaultConfig
  , _conf_forward = ForwardOptions {_options_ghc = Identity []}
  , _conf_roundtrip_exactprint_only = coerce False
  , _conf_obfuscate = coerce False
  }

contextFreeTestConfig :: Config
contextFreeTestConfig =
  defaultTestConfig
  { _conf_layout = (_conf_layout defaultTestConfig)
    {_lconfig_indentPolicy = coerce IndentPolicyLeft
    ,_lconfig_alignmentLimit = coerce (1 :: Int)
    ,_lconfig_columnAlignMode = coerce ColumnAlignModeDisabled
    }
  }