{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE QuasiQuotes         #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}

-- | Test suite.

module Windows (spec) where

import Control.Applicative
import Control.Monad
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Function (on)
import Data.Maybe
import Path.Windows
import Path.Internal
import Test.Hspec

import Common (extensionOperations)

-- | Test suite (Windows version).
spec :: Spec
spec =
  do describe "Parsing: Path Abs Dir" parseAbsDirSpec
     describe "Parsing: Path Rel Dir" parseRelDirSpec
     describe "Parsing: Path Abs File" parseAbsFileSpec
     describe "Parsing: Path Rel File" parseRelFileSpec
     describe "Operations: (</>)" operationAppend
     describe "Operations: toFilePath" operationToFilePath
     describe "Operations: stripProperPrefix" operationStripProperPrefix
     describe "Operations: isProperPrefixOf" operationIsProperPrefixOf
     describe "Operations: parent" operationParent
     describe "Operations: filename" operationFilename
     describe "Operations: dirname" operationDirname
     describe "Operations: extensions" (extensionOperations "C:\\")
     describe "Restrictions" restrictions
     describe "Aeson Instances" aesonInstances
     describe "QuasiQuotes" quasiquotes

-- | Restricting the input of any tricks.
restrictions :: Spec
restrictions =
  do parseFails "..\\"
     parseFails ".."
     parseSucceeds "a.." (Path "a..\\")
     parseSucceeds "..a" (Path "..a\\")
     parseFails "\\.."
     parseFails "C:\\foo\\..\\bar\\"
     parseFails "C:\\foo\\bar\\.."
  where parseFails x =
          it (show x ++ " should be rejected")
             (isNothing (void (parseAbsDir x) <|>
                         void (parseRelDir x) <|>
                         void (parseAbsFile x) <|>
                         void (parseRelFile x)))
        parseSucceeds x with =
          parserTest parseRelDir x (Just with)

-- | The 'dirname' operation.
operationDirname :: Spec
operationDirname =
  do it "dirname ($(mkAbsDir parent) </> $(mkRelFile dirname)) == dirname $(mkRelFile dirname) (absolute)"
        (dirnamesShouldBeEqual
          ($(mkAbsDir "C:\\chris\\") </> $(mkRelDir "bar"))
          $(mkRelDir "bar"))
     it "dirname ($(mkRelDir parent) </> $(mkRelFile dirname)) == dirname $(mkRelFile dirname) (relative)"
        (dirnamesShouldBeEqual
          ($(mkRelDir "home\\chris\\") </> $(mkRelDir "bar"))
          $(mkRelDir "bar"))
     it "dirname ($(mkAbsDir parent) </> $(mkRelFile dirname)) == dirname $(mkRelFile dirname) (UNC)"
        (dirnamesShouldBeEqual
          ($(mkAbsDir "\\\\home\\chris\\") </> $(mkRelDir "bar"))
          $(mkRelDir "bar"))
     it "dirname ($(mkAbsDir parent) </> $(mkRelFile dirname)) == dirname $(mkRelFile dirname) (Unicode)"
        (dirnamesShouldBeEqual
          ($(mkAbsDir "\\\\?\\C:\\home\\chris\\") </> $(mkRelDir "bar"))
          $(mkRelDir "bar"))
     it "dirname $(mkRelDir .) == $(mkRelDir .)"
        (dirnamesShouldBeEqual
          $(mkRelDir ".")
          $(mkRelDir "."))
     it "dirname C:\\ must be a Rel path"
        ((parseAbsDir $ show $ dirname (fromJust (parseAbsDir "C:\\")) :: Maybe (Path Abs Dir)) == Nothing)
  where dirnamesShouldBeEqual = (==) `on` dirname

-- | The 'filename' operation.
operationFilename :: Spec
operationFilename =
  do it "filename ($(mkAbsDir parent) </> $(mkRelFile filename)) == filename $(mkRelFile filename) (absolute)"
        (filenamesShouldBeEqual
          ($(mkAbsDir "C:\\chris\\") </> $(mkRelFile "bar.txt"))
          $(mkRelFile "bar.txt"))
     it "filename ($(mkRelDir parent) </> $(mkRelFile filename)) == filename $(mkRelFile filename) (relative)"
        (filenamesShouldBeEqual
          ($(mkRelDir "home\\chris\\") </> $(mkRelFile "bar.txt"))
          $(mkRelFile "bar.txt"))
     it "filename ($(mkAbsDir parent) </> $(mkRelFile filename)) == filename $(mkRelFile filename) (UNC)"
        (filenamesShouldBeEqual
          ($(mkAbsDir "\\\\host\\share\\chris\\") </> $(mkRelFile "bar.txt"))
          $(mkRelFile "bar.txt"))
     it "filename ($(mkAbsDir parent) </> $(mkRelFile filename)) == filename $(mkRelFile filename) (Unicode)"
        (filenamesShouldBeEqual
          ($(mkAbsDir "\\\\?\\C:\\home\\chris\\") </> $(mkRelFile "bar.txt"))
          $(mkRelFile "bar.txt"))
  where filenamesShouldBeEqual = (==) `on` filename

-- | The 'parent' operation.
operationParent :: Spec
operationParent =
  do it "parent (parent </> child) == parent"
        (parent ($(mkAbsDir "C:\\foo") </> $(mkRelDir "bar")) == $(mkAbsDir "C:\\foo"))
     it "parent \"C:\\\" == \"C:\\\""
        (parent $(mkAbsDir "C:\\") == $(mkAbsDir "C:\\"))
     it "parent \"C:\\x\" == \"C:\\\""
        (parent $(mkAbsDir "C:\\x") == $(mkAbsDir "C:\\"))
     it "parent \"x\" == \".\""
        (parent $(mkRelDir "x") == $(mkRelDir "."))
     it "parent \".\" == \".\""
        (parent $(mkRelDir ".") == $(mkRelDir "."))

-- | The 'isProperPrefixOf' operation.
operationIsProperPrefixOf :: Spec
operationIsProperPrefixOf =
  do it "isProperPrefixOf parent (parent </> child) (absolute)"
        (isProperPrefixOf
          $(mkAbsDir "C:\\\\\\bar\\")
          ($(mkAbsDir "C:\\\\\\bar\\") </> $(mkRelFile "bar\\foo.txt")))
     it "isProperPrefixOf parent (parent </> child) (relative)"
        (isProperPrefixOf
          $(mkRelDir "bar\\")
          ($(mkRelDir "bar\\") </> $(mkRelFile "bob\\foo.txt")))
     it "isProperPrefixOf parent (parent </> child) (UNC)"
        (isProperPrefixOf
          $(mkAbsDir "\\\\host\\share\\")
          ($(mkAbsDir "\\\\host\\share\\") </> $(mkRelFile "bob\\foo.txt")))
     it "isProperPrefixOf parent (parent </> child) (Unicode)"
        (isProperPrefixOf
          $(mkAbsDir "\\\\?\\C:\\folder\\")
          ($(mkAbsDir "\\\\?\\C:\\folder\\") </> $(mkRelFile "bob\\foo.txt")))
     it "not (x `isProperPrefixOf` x)"
        (not (isProperPrefixOf $(mkRelDir "x") $(mkRelDir "x")))
     it "not (\\ `isProperPrefixOf` \\)"
        (not (isProperPrefixOf $(mkAbsDir "C:\\") $(mkAbsDir "C:\\")))

-- | The 'stripProperPrefix' operation.
operationStripProperPrefix :: Spec
operationStripProperPrefix =
  do it "stripProperPrefix parent (parent </> child) = child (absolute)"
        (remainingPathShouldBe
          $(mkAbsDir "C:\\\\\\bar\\")
          ($(mkAbsDir "C:\\\\\\bar\\") </> $(mkRelFile "bar\\foo.txt"))
          (Just $(mkRelFile "bar\\foo.txt")))
     it "stripProperPrefix parent (parent </> child) = child (relative)"
        (remainingPathShouldBe
          $(mkRelDir "bar\\")
          ($(mkRelDir "bar\\") </> $(mkRelFile "bob\\foo.txt"))
          (Just $(mkRelFile "bob\\foo.txt")))
     it "stripProperPrefix parent (parent </> child) = child (UNC)"
        (remainingPathShouldBe
          $(mkAbsDir "\\\\host\\share\\")
          ($(mkAbsDir "\\\\host\\share\\") </> $(mkRelFile "bob\\foo.txt"))
          (Just $(mkRelFile "bob\\foo.txt")))
     it "stripProperPrefix parent (parent </> child) = child (Unicode)"
        (remainingPathShouldBe
          $(mkAbsDir "\\\\?\\C:\\folder\\")
          ($(mkAbsDir "\\\\?\\C:\\folder\\") </> $(mkRelFile "bob\\foo.txt"))
          (Just $(mkRelFile "bob\\foo.txt")))
     it "stripProperPrefix parent parent = _|_"
        (remainingPathShouldBe
          $(mkAbsDir "C:\\home\\chris\\foo")
          $(mkAbsDir "C:\\home\\chris\\foo")
          Nothing)
  where remainingPathShouldBe prefix path suffix =
          stripProperPrefix prefix path == suffix

-- | The '</>' operation.
operationAppend :: Spec
operationAppend =
  do it "AbsDir + RelDir = AbsDir"
        (shouldBe
          ($(mkAbsDir "C:\\home\\") </> $(mkRelDir "chris"))
          $(mkAbsDir "C:\\home\\chris\\"))
     it "AbsDir + RelFile = AbsFile"
        (shouldBe
          ($(mkAbsDir "C:\\home\\") </> $(mkRelFile "chris\\test.txt"))
          $(mkAbsFile "C:\\home\\chris\\test.txt"))
     it "RelDir + RelDir = RelDir"
        (shouldBe
          ($(mkRelDir "home\\") </> $(mkRelDir "chris"))
          $(mkRelDir "home\\chris"))
     it ". + . = ."
        (shouldBe
          ($(mkRelDir ".\\") </> $(mkRelDir "."))
          $(mkRelDir "."))
     it ". + x = x"
        (shouldBe
          ($(mkRelDir ".") </> $(mkRelDir "x"))
          $(mkRelDir "x"))
     it "x + . = x"
        (shouldBe
          ($(mkRelDir "x") </> $(mkRelDir ".\\"))
          $(mkRelDir "x"))
     it "RelDir + RelFile = RelFile"
        (shouldBe
          ($(mkRelDir "home\\") </> $(mkRelFile "chris\\test.txt"))
          $(mkRelFile "home\\chris\\test.txt"))
     it "AbsDir(UNC) + RelDir = AbsDir(UNC)"
        (shouldBe
          ($(mkAbsDir "\\\\host\\share\\") </> $(mkRelDir "folder\\"))
          $(mkAbsDir "\\\\host\\share\\folder\\"))
     it "AbsDir(UNC) + RelFile = AbsFile(UNC)"
        (shouldBe
          ($(mkAbsDir "\\\\host\\share\\") </> $(mkRelFile "folder\\file.txt"))
          $(mkAbsFile "\\\\host\\share\\folder\\file.txt"))
     it "AbsDir(Unicode) + RelDir = AbsDir(Unicode)"
        (shouldBe
          ($(mkAbsDir "\\\\?\\C:\\folder\\") </> $(mkRelDir "another\\"))
          $(mkAbsDir "\\\\?\\C:\\folder\\another\\"))
     it "AbsDir(Unicode) + RelFile = AbsFile(Unicode)"
        (shouldBe
          ($(mkAbsDir "\\\\?\\C:\\folder\\") </> $(mkRelFile "file.txt"))
          $(mkAbsFile "\\\\?\\C:\\folder\\file.txt"))

operationToFilePath :: Spec
operationToFilePath =
  do it "toFilePath $(mkRelDir \".\") == \"./\""
        (toFilePath $(mkRelDir ".") == ".\\")
     it "show $(mkRelDir \".\") == \"\\\".\\\\\"\""
        (show $(mkRelDir ".") == "\".\\\\\"")

-- | Tests for the tokenizer.
parseAbsDirSpec :: Spec
parseAbsDirSpec =
  do failing ""
     failing ".\\"
     failing "foo.txt"
     failing "C:"
     succeeding "C:\\" (Path "C:\\")
     succeeding "C:\\\\" (Path "C:\\")
     succeeding "C:\\\\\\foo\\\\bar\\\\mu\\" (Path "C:\\foo\\bar\\mu\\")
     succeeding "C:\\\\\\foo\\\\bar\\\\mu" (Path "C:\\foo\\bar\\mu\\")
     succeeding "C:\\\\\\foo\\\\bar\\.\\\\mu" (Path "C:\\foo\\bar\\mu\\")
     succeeding "\\\\unchost\\share" (Path "\\\\unchost\\share\\")
     succeeding "\\/unchost\\share" (Path "\\\\unchost\\share\\")
     succeeding "\\\\unchost\\share\\\\folder\\" (Path "\\\\unchost\\share\\folder\\")
     succeeding "\\\\?\\C:\\" (Path "\\\\?\\C:\\")
     succeeding "/\\?\\C:\\" (Path "\\\\?\\C:\\")
     succeeding "\\\\?\\C:\\\\\\folder\\\\" (Path "\\\\?\\C:\\folder\\")

  where failing x = parserTest parseAbsDir x Nothing
        succeeding x with = parserTest parseAbsDir x (Just with)

-- | Tests for the tokenizer.
parseRelDirSpec :: Spec
parseRelDirSpec =
  do failing ""
     failing "/"
     failing "//"
     failing "\\"
     failing "\\\\"
     failing "\\\\\\foo\\\\bar\\\\mu\\"
     failing "\\\\\\foo\\\\bar\\\\\\\\mu"
     failing "\\\\\\foo\\\\bar\\.\\\\mu"
     failing "\\\\unchost\\share"
     failing "\\\\?\\C:\\"
     succeeding ".\\" (Path "")
     succeeding ".\\.\\" (Path "")
     succeeding "..." (Path "...\\")
     succeeding "foo.bak" (Path "foo.bak\\")
     succeeding ".\\foo" (Path "foo\\")
     succeeding ".\\.\\foo" (Path "foo\\")
     succeeding ".\\foo\\.\\bar" (Path "foo\\bar\\")
     succeeding "foo\\\\bar\\\\mu\\\\" (Path "foo\\bar\\mu\\")
     succeeding "foo\\\\bar////mu" (Path "foo\\bar\\mu\\")
     succeeding "foo\\\\bar\\.\\\\mu" (Path "foo\\bar\\mu\\")

  where failing x = parserTest parseRelDir x Nothing
        succeeding x with = parserTest parseRelDir x (Just with)

-- | Tests for the tokenizer.
parseAbsFileSpec :: Spec
parseAbsFileSpec =
  do failing ""
     failing ".\\"
     failing "\\."
     failing "\\foo\\bar\\."
     failing "~\\"
     failing ".\\foo.txt"
     failing "\\"
     failing "\\\\"
     failing "\\\\\\foo\\\\bar\\\\mu\\"
     failing "\\..."
     failing "\\foo.txt"
     succeeding "C:\\\\\\foo\\\\bar\\\\\\\\mu.txt" (Path "C:\\foo\\bar\\mu.txt")
     succeeding "C:\\\\\\foo\\\\bar\\.\\\\mu.txt" (Path "C:\\foo\\bar\\mu.txt")
     succeeding "\\\\unchost\\share\\\\file.txt" (Path "\\\\unchost\\share\\file.txt")
     succeeding "\\/unchost\\share\\\\file.txt" (Path "\\\\unchost\\share\\file.txt")
     succeeding "\\\\unchost\\share\\.\\folder\\\\\\file.txt" (Path "\\\\unchost\\share\\folder\\file.txt")
     succeeding "\\\\?\\C:\\file.txt" (Path "\\\\?\\C:\\file.txt")
     succeeding "/\\?\\C:\\file.txt" (Path "\\\\?\\C:\\file.txt")
     succeeding "\\\\?\\C:\\\\\\folder\\.\\\\file.txt" (Path "\\\\?\\C:\\folder\\file.txt")

  where failing x = parserTest parseAbsFile x Nothing
        succeeding x with = parserTest parseAbsFile x (Just with)

-- | Tests for the tokenizer.
parseRelFileSpec :: Spec
parseRelFileSpec =
  do failing ""
     failing "\\"
     failing "\\\\"
     failing "~\\"
     failing "\\"
     failing ".\\"
     failing "a\\."
     failing "a\\..\\b"
     failing "a\\.."
     failing "..\\foo.txt"
     failing "\\\\"
     failing "\\\\\\foo\\\\bar\\\\mu\\"
     failing "\\\\\\foo\\\\bar\\\\\\\\mu"
     failing "\\\\\\foo\\\\bar\\.\\\\mu"
     failing "\\\\unchost\\share\\\\file.txt"
     failing "\\\\?\\C:\\file.txt"
     succeeding "a.." (Path "a..")
     succeeding "..." (Path "...")
     succeeding "foo.txt" (Path "foo.txt")
     succeeding ".\\foo.txt" (Path "foo.txt")
     succeeding ".\\.\\foo.txt" (Path "foo.txt")
     succeeding ".\\foo\\.\\bar.txt" (Path "foo\\bar.txt")
     succeeding "foo\\\\bar\\\\mu.txt" (Path "foo\\bar\\mu.txt")
     succeeding "foo\\\\bar\\\\\\\\mu.txt" (Path "foo\\bar\\mu.txt")
     succeeding "foo\\\\bar\\.\\\\mu.txt" (Path "foo\\bar\\mu.txt")

  where failing x = parserTest parseRelFile x Nothing
        succeeding x with = parserTest parseRelFile x (Just with)

-- | Parser test.
parserTest :: (Show a1,Show a,Eq a1)
           => (a -> Maybe a1) -> a -> Maybe a1 -> SpecWith ()
parserTest parser input expected =
  it ((case expected of
         Nothing -> "Failing: "
         Just{} -> "Succeeding: ") ++
      "Parsing " ++
      show input ++
      " " ++
      case expected of
        Nothing -> "should fail."
        Just x -> "should succeed with: " ++ show x)
     (actual `shouldBe` expected)
  where actual = parser input

-- | Tests for the 'ToJSON' and 'FromJSON' instances
--
-- Can't use overloaded strings due to some weird issue with bytestring-0.9.2.1 / ghc-7.4.2:
-- https://travis-ci.org/sjakobi/path/jobs/138399072#L989
aesonInstances :: Spec
aesonInstances =
  do it "Decoding \"[\"C:\\\\foo\\\\bar\"]\" as a [Path Abs Dir] should succeed." $
       eitherDecode (LBS.pack "[\"C:\\\\foo\\\\bar\"]") `shouldBe` Right [Path "C:\\foo\\bar\\" :: Path Abs Dir]
     it "Decoding \"[\"C:\\foo\\bar\"]\" as a [Path Rel Dir] should fail." $
       decode (LBS.pack "[\"C:\\foo\\bar\"]") `shouldBe` (Nothing :: Maybe [Path Rel Dir])
     it "Encoding \"[\"C:\\foo\\bar\\mu.txt\"]\" should succeed." $
       encode [Path "C:\\foo\\bar\\mu.txt" :: Path Abs File] `shouldBe` (LBS.pack "[\"C:\\\\foo\\\\bar\\\\mu.txt\"]")

-- | Test QuasiQuoters. Make sure they work the same as the $(mk*) constructors.
quasiquotes :: Spec
quasiquotes =
  do it "[absdir|C:\\|] == $(mkAbsDir \"C:\\\")"
       ([absdir|C:\|] `shouldBe` $(mkAbsDir "C:\\"))
     it "[absdir|C:\\chris\\|] == $(mkAbsDir \"C:\\chris\\\")"
       ([absdir|C:\chris\|] `shouldBe` $(mkAbsDir "C:\\chris\\"))
     it "[reldir|foo|] == $(mkRelDir \"foo\")"
       ([reldir|foo|] `shouldBe` $(mkRelDir "foo"))
     it "[reldir|foo\\bar|] == $(mkRelDir \"foo\\bar\")"
       ([reldir|foo\bar|] `shouldBe` $(mkRelDir "foo\\bar"))
     it "[absfile|C:\\chris\\foo.txt|] == $(mkAbsFile \"C:\\chris\\foo.txt\")"
       ([absfile|C:\chris\foo.txt|] `shouldBe` $(mkAbsFile "C:\\chris\\foo.txt"))
     it "[relfile|foo.exe|] == $(mkRelFile \"foo.exe\")"
       ([relfile|foo.exe|] `shouldBe` $(mkRelFile "foo.exe"))
     it "[relfile|chris\\foo.txt|] == $(mkRelFile \"chris\\foo.txt\")"
       ([relfile|chris\foo.txt|] `shouldBe` $(mkRelFile "chris\\foo.txt"))
