-- Copyright (c) Facebook, Inc. and its affiliates.
--
-- This source code is licensed under the MIT license found in the
-- LICENSE file in the root directory of this source tree.
--
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module CPP (cppTest) where

import Control.Monad
import Data.List (intersperse, sort)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Retrie.CPP
import Retrie.ExactPrint
import Retrie.Util
import Test.HUnit

data CPPTest = CPPTest
  { name :: String
  , code :: Text
  , results :: [[Text]]
  }

cppTest :: Test
cppTest = TestLabel "cpp" $ TestList $
  map cppForkTest testList ++
  map roundTripTest testList

testList :: [CPPTest]
testList =
  [ CPPTest "no cpp" noCPPCode [Text.lines noCPPCode]
  , CPPTest "define" defineCode
      [ ["a","","b"]
      ]
  , CPPTest "one if" oneIfCode
      [ ["a","","b","","c"]
      , ["a","","","","c"]
      ]
  , CPPTest "if else" ifElseCode
      [ ["a","b","","","","","e","f","","g","h"]
      , ["a","b","","c","d","","","","","g","h"]
      ]
  , CPPTest "if elif else" elIfCode
      [ ["a","b","","c","d","","","","","","","","i","j"]
      , ["a","b","","","","","e","f","","","","","i","j"]
      , ["a","b","","","","","","","","g","h","","i","j"]
      ]
  , CPPTest "if elif elif else" twoElIfCode
      [ ["a","b","","c","d","","","","","","","","","","","k","l"]
      , ["a","b","","","","","e","f","","","","","","","","k","l"]
      , ["a","b","","","","","","","","g","h","","","","","k","l"]
      , ["a","b","","","","","","","","","","","i","j","","k","l"]
      ]
  , CPPTest "if elif" elIfNoElseCode
      [ ["a","b","","c","d","","","","","i","j"]
      , ["a","b","","","","","e","f","","i","j"]
      , ["a","b","","","","","","","","i","j"]
      ]
  , CPPTest "nested if" nestedIfCode
      [ ["a","","b","","","","","e","","f","g","","h"]
      , ["a","","b","","c","d","","","","f","g","","h"]
      , ["a","","","","","","","","","","","","h"]
      ]
  , CPPTest "imports" importCode
      [ importExpected1, importExpected2 ]
  ]

noCPPCode :: Text
noCPPCode = Text.unlines
  [ "a"
  , "b"
  , "c"
  ]

defineCode :: Text
defineCode = Text.unlines
  [ "a"
  , "#define FOO 1"
  , "b"
  ]

oneIfCode :: Text
oneIfCode = Text.unlines
  [ "a"
  , "#if FOO"
  , "b"
  , "#endif"
  , "c"
  ]

ifElseCode :: Text
ifElseCode = Text.unlines
  [ "a"
  , "b"
  , "#if FOO"
  , "c"
  , "d"
  , "#else"
  , "e"
  , "f"
  , "#endif"
  , "g"
  , "h"
  ]

elIfCode :: Text
elIfCode = Text.unlines
  [ "a"
  , "b"
  , "#if FOO"
  , "c"
  , "d"
  , "#elif BAR"
  , "e"
  , "f"
  , "#else"
  , "g"
  , "h"
  , "#endif"
  , "i"
  , "j"
  ]

twoElIfCode :: Text
twoElIfCode = Text.unlines
  [ "a"
  , "b"
  , "#if FOO"
  , "c"
  , "d"
  , "#elif BAR"
  , "e"
  , "f"
  , "#elif BAZ"
  , "g"
  , "h"
  , "#else"
  , "i"
  , "j"
  , "#endif"
  , "k"
  , "l"
  ]

elIfNoElseCode :: Text
elIfNoElseCode = Text.unlines
  [ "a"
  , "b"
  , "#if FOO"
  , "c"
  , "d"
  , "#elif BAR"
  , "e"
  , "f"
  , "#endif"
  , "i"
  , "j"
  ]

nestedIfCode :: Text
nestedIfCode = Text.unlines
  [ "a"
  , "#if FOO"
  , "b"
  , "#if BAR"
  , "c"
  , "d"
  , "#else"
  , "e"
  , "#endif"
  , "f"
  , "g"
  , "#endif"
  , "h"
  ]

importCode :: Text
importCode = Text.unlines
  [ "module Foo where"
  , ""
  , "import Bar"
  , "#if BAZ"
  , "import Baz"
  , "#endif"
  , "import Quux"
  , "#if QUUX"
  , "import Something"
  , ""
  , "myDecl :: Int"
  , "myDecl = 54"
  , "#else"
  , "import Something.Else"
  , "#endif"
  , ""
  , "thats all = folks"
  ]

importExpected2 :: [Text]
importExpected2 =
  [ "module Foo where"
  , ""
  , "import Bar"
  , ""
  , "import Baz"
  , ""
  , "import Quux"
  , ""
  , "import Something"
  , ""
  , "myDecl :: Int"
  , "myDecl = 54"
  , ""
  , ""
  , ""
  , ""
  , "thats all = folks"
  ]

importExpected1 :: [Text]
importExpected1 =
  [ "module Foo where"
  , ""
  , "import Bar"
  , ""
  , "import Baz"
  , ""
  , "import Quux"
  , ""
  , ""
  , ""
  , ""
  , ""
  , ""
  , "import Something.Else"
  , ""
  , ""
  , "thats all = folks"
  ]

cppForkTest :: CPPTest -> Test
cppForkTest CPPTest{..} = TestLabel ("cpp fork: " ++ name) $ TestCase $ do
  let
    sorted = sort $ map Text.unlines results
    fork = sort $ cppFork code
  unless (sorted == fork) $ do
    putStrLn "Expected:"
    mapM_ Text.putStrLn (intersperse "===" sorted)
    putStrLn "But Got:"
    mapM_ Text.putStrLn (intersperse "===" fork)
    assertFailure "cppFork does not give expected result"

roundTripTest :: CPPTest -> Test
roundTripTest CPPTest{..} = TestLabel ("roundtrip: " ++ name) $ TestCase $ do
  r <- trySync $ parseCPP (parseContentNoFixity "roundTripTest") code
  case r of
    Left msg -> assertFailure (show msg)
    Right cpp -> assertEqual "cpp did not roundtrip correctly"
      (Text.unpack code)
      (printCPP [] cpp)