{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

module Main (tests, main) where

import           Prelude hiding (FilePath)

import qualified Data.ByteString.Char8 as B8
import           Data.Char (toUpper)
import           Data.List (intercalate)
import qualified Data.Text as T
import           Test.Chell
import           Test.Chell.QuickCheck
import           Test.QuickCheck hiding (property)

import qualified Filesystem.Path as P
import           Filesystem.Path (FilePath, (</>), relative, stripPrefix, absolute, basename, empty)
import           Filesystem.Path.CurrentOS ()
import           Filesystem.Path.Rules

main :: IO ()
main = Test.Chell.defaultMain [tests]

tests :: Suite
tests = suite "tests" $
	-- Basic properties
	[ test_Empty
	, test_Root
	, test_Directory
	, test_Parent
	, test_Filename
	, test_Dirname
	, test_Basename
	, test_Absolute
	, test_Relative
	
	, test_LeadingDotSpecialCases
	
	-- Basic operations
	, test_Append
	, test_CommonPrefix
	, test_StripPrefix
	, property "stripPrefix" prop_StripPrefix
	, test_SplitExtension
	, test_Collapse
	, test_SplitDirectories
	, test_InvalidUtf8InDirectoryComponent
	, test_Utf8CharInGhcEscapeArea
	
	, test_SplitSearchPath
	, test_Parsing
	, test_EqualsIgnoresPosixEncoding
	, test_ShowRules
	] ++ suiteTests suite_EncodeString
	++ suiteTests suite_DecodeString
	++ suiteTests suite_SplitSearchPathString
	++ suiteTests (suite "to-from-bytes"
		[ test_Identity "posix" posix posixPaths
		, test_Identity "windows" windows windowsPaths
		, test_MixedValidityToBytes
		])
	++ suiteTests (suite "to-from-text"
		[ test_ToText
		, test_FromText
		])
	++ suiteTests (suite "validity"
		[ property "posix" (forAll posixPaths (valid posix))
		, property "windows" (forAll windowsPaths (valid windows))
		, test_UncValidity
		])

test_Empty :: Test
test_Empty = assertions "empty" $ do
	$expect $ P.null empty
	$expect $ equal (toChar8 empty) ""
	$expect $ equal (toString empty) ""

test_Root :: Test
test_Root = assertions "root" $ do
	let root x = toChar8 (P.root (fromChar8 x))
	
	$expect $ equal (root "") ""
	$expect $ equal (root "/") "/"
	$expect $ equal (root "foo") ""
	$expect $ equal (root "/foo") "/"

test_Directory :: Test
test_Directory = assertions "directory" $ do
	let directory x = toChar8 (P.directory (fromChar8 x))
	
	$expect $ equal (directory "") "./"
	$expect $ equal (directory "/") "/"
	$expect $ equal (directory "/foo/bar") "/foo/"
	$expect $ equal (directory "/foo/bar/") "/foo/bar/"
	$expect $ equal (directory ".") "./"
	$expect $ equal (directory "..") "../"
	$expect $ equal (directory "../foo") "../"
	$expect $ equal (directory "../foo/") "../foo/"
	$expect $ equal (directory "foo") "./"
	$expect $ equal (directory "foo/bar") "foo/"

test_Parent :: Test
test_Parent = assertions "parent" $ do
	let parent x = toChar8 (P.parent (fromChar8 x))
	
	$expect $ equal (parent "") "./"
	$expect $ equal (parent "/") "/"
	$expect $ equal (parent "/foo/bar") "/foo/"
	$expect $ equal (parent "/foo/bar/") "/foo/"
	$expect $ equal (parent ".") "./"
	$expect $ equal (parent "..") "./"
	$expect $ equal (parent "../foo/bar") "../foo/"
	$expect $ equal (parent "../foo/bar") "../foo/"
	$expect $ equal (parent "foo") "./"
	$expect $ equal (parent "foo/bar") "./foo/"

test_Filename :: Test
test_Filename = assertions "filename" $ do
	let filename x = toChar8 (P.filename (fromChar8 x))
	
	$expect $ equal (filename "") ""
	$expect $ equal (filename "/") ""
	$expect $ equal (filename "/foo/") ""
	$expect $ equal (filename "/foo/bar") "bar"
	$expect $ equal (filename "/foo/bar.txt") "bar.txt"

test_Dirname :: Test
test_Dirname = assertions "dirname" $ do
	let dirname x = toChar8 (P.dirname (fromChar8 x))
	
	$expect $ equal (dirname "") ""
	$expect $ equal (dirname "/") ""
	$expect $ equal (dirname "foo") ""
	$expect $ equal (dirname "foo/bar") "foo"
	$expect $ equal (dirname "foo/bar/") "bar"
	$expect $ equal (dirname "foo/bar/baz.txt") "bar"
	
	-- the directory name will be re-parsed to a file name.
	let dirnameExts x = P.extensions (P.dirname (fromChar8 x))
	$expect $ equal (dirnameExts "foo.d/bar") ["d"]
	
	-- reparsing preserves good/bad encoding state
	$expect $ equal (dirnameExts "foo.\xB1.\xDD\xAA/bar") ["\xB1", "\x76A"]

test_Basename :: Test
test_Basename = assertions "basename" $ do
	let basename_posix x = toChar8 (basename (fromChar8 x))
	let basename_windows x = toString (basename (fromString x))
	
	$expect $ equal (basename_posix "/foo/bar") "bar"
	$expect $ equal (basename_posix "/foo/bar.txt") "bar"
	
	$expect $ equal (basename_windows "c:\\foo\\bar") "bar"
	$expect $ equal (basename_windows "c:\\foo\\bar.txt") "bar"

test_Absolute :: Test
test_Absolute = assertions "absolute" $ do
	$expect $ absolute (fromChar8 "/")
	$expect $ absolute (fromChar8 "/foo/bar")
	$expect . not $ absolute (fromChar8 "")
	$expect . not $ absolute (fromChar8 "foo/bar")
	
	$expect $ absolute (fromString "c:\\")
	$expect $ absolute (fromString "c:\\foo\\bar")
	$expect . not $ absolute (fromString "")
	$expect . not $ absolute (fromString "foo\\bar")
	$expect . not $ absolute (fromString "\\foo\\bar")

test_Relative :: Test
test_Relative = assertions "relative" $ do
	$expect . not $ relative (fromChar8 "/")
	$expect . not $ relative (fromChar8 "/foo/bar")
	$expect $ relative (fromChar8 "")
	$expect $ relative (fromChar8 "foo/bar")
	
	$expect . not $ relative (fromString "c:\\")
	$expect . not $ relative (fromString "c:\\foo\\bar")
	$expect $ relative (fromString "")
	$expect $ relative (fromString "foo\\bar")
	$expect . not $ relative (fromString "\\foo\\bar")

test_LeadingDotSpecialCases :: Test
test_LeadingDotSpecialCases = assertions "leading-dot-special-cases" $ do
	let components_posix x = let p = fromChar8 x in
		(toChar8 (P.directory p), toChar8 (basename p), P.extensions p)
	let components_windows x = let p = fromString x in
		(toString (P.directory p), toString (basename p), P.extensions p)
	
	-- The filenames "." and ".." are always considered to be directory
	-- elements, because they are links to either the current or parent
	-- directories.
	--
	-- On POSIX, filenames starting with '.' are hidden files and have
	-- a basename starting with '.'.
	--
	-- On Windows there is no history of similar naming patterns. The case
	-- could be made that a filename like ".txt.gz" ought to have a
	-- basename of "", but different basename behavior in POSIX and Windows
	-- would greatly complicate the implementation of 'dirname'.
	$expect $ equal (components_posix ".") ("./", "", [])
	$expect $ equal (components_posix "..") ("../", "", [])
	$expect $ equal (components_posix "/foo/.") ("/foo/./", "", [])
	$expect $ equal (components_posix "/foo/..") ("/foo/../", "", [])
	$expect $ equal (components_posix "/foo/.foo.txt") ("/foo/", ".foo", ["txt"])
	
	$expect $ equal (components_windows ".") (".\\", "", [])
	$expect $ equal (components_windows "..") ("..\\", "", [])
	$expect $ equal (components_windows "\\foo\\.") ("\\foo\\.\\", "", [])
	$expect $ equal (components_windows "\\foo\\..") ("\\foo\\..\\", "", [])
	$expect $ equal (components_windows "\\foo\\.foo.txt") ("\\foo\\", ".foo", ["txt"])

test_Identity :: String -> Rules a -> Gen FilePath -> Test
test_Identity name r gen = property name $ forAll gen $ \p -> p == decode r (encode r p)

test_MixedValidityToBytes :: Test
test_MixedValidityToBytes = assertions "mixed-validity-to-bytes" $ do
	let p = fromChar8
	
	$expect $ equal (encode posix (p "\xB1.\xDD\xAA")) (B8.pack "\xB1.\xDD\xAA")
	$expect $ equal (encode posix (p "\xB1.\xDD\xAA" </> p "foo")) (B8.pack "\xB1.\xDD\xAA/foo")

test_ToText :: Test
test_ToText = assertions "toText" $ do
	let p = fromChar8
	
	$expect $ equal (toText posix (p "")) (Right (T.pack ""))
	$expect $ equal (toText posix (p "ascii")) (Right (T.pack "ascii"))
	$expect $ equal (toText posix (p "\xF0\x9D\x84\x9E")) (Right (T.pack "\x1D11E"))
	$expect $ equal (toText posix (p "\xED\xA0\x80")) (Left (T.pack "\xED\xA0\x80"))
	$expect $ equal (toText posix (p "\xF0\x9D\x84\x9E/\xED\xA0\x80")) (Left (T.pack "\x1D11E/\xED\xA0\x80"))
	$expect $ equal (toText posix (p "\xED.\xF0\x9D\x84\x9E.\xA0\x80")) (Left (T.pack "\xED.\x1D11E.\xA0\x80"))
	$expect $ equal (toText posix (p "\xB1.\xDD\xAA")) (Left (T.pack "\xB1.\x76A"))
	$expect $ equal (toText posix (p "\xB1.\xDD\xAA" </> p "foo")) (Left (T.pack "\xB1.\x76A/foo"))

test_FromText :: Test
test_FromText = assertions "fromText" $ do
	let pt x = fromText posix (T.pack x)
	let p = fromChar8
	
	$expect $ equal (pt "") (p "")
	$expect $ equal (pt "\x1D11E") (p "\xF0\x9D\x84\x9E")
	$expect $ equal (pt "\xED\xA0\x80") (p "\xC3\xAD\xC2\xA0\xC2\x80")

test_Append :: Test
test_Append = assertions "append" $ do
	let appendP x y = toChar8 (P.append (fromChar8 x) (fromChar8 y))
	let appendW x y = toString (P.append (fromString x) (fromString y))
	
	$expect $ equal (appendP "" "") ""
	$expect $ equal (appendP "" "b/") "b/"
	
	-- Relative to a directory
	$expect $ equal (appendP "a/" "") "a/"
	$expect $ equal (appendP "a/" "b/") "a/b/"
	$expect $ equal (appendP "a/" "b.txt") "a/b.txt"
	$expect $ equal (appendP "a.txt" "b.txt") "a.txt/b.txt"
	$expect $ equal (appendP "." "a") "./a"
	
	-- Relative to a file
	$expect $ equal (appendP "a" "") "a/"
	$expect $ equal (appendP "a" "b/") "a/b/"
	$expect $ equal (appendP "a/b" "c") "a/b/c"
	
	-- Absolute
	$expect $ equal (appendP "/a/" "") "/a/"
	$expect $ equal (appendP "/a/" "b") "/a/b"
	$expect $ equal (appendP "/a/" "b/") "/a/b/"
	
	-- Second parameter is absolute
	$expect $ equal (appendP "/a/" "/") "/"
	$expect $ equal (appendP "/a/" "/b") "/b"
	$expect $ equal (appendP "/a/" "/b/") "/b/"
	
	-- Windows: volume handling
	$expect $ equal (appendW "c:\\" "") "C:\\"
	$expect $ equal (appendW "c:\\foo" "bar\\baz") "C:\\foo\\bar\\baz"
	$expect $ equal (appendW "c:\\foo" "d:\\bar") "D:\\bar"
	$expect $ equal (appendW "c:\\foo" "\\bar") "C:\\bar"
	$expect $ equal (appendW "foo\\bar" "\\baz") "\\baz"

test_CommonPrefix :: Test
test_CommonPrefix = assertions "commonPrefix" $ do
	let commonPrefix xs = toChar8 (P.commonPrefix (map (fromChar8) xs))
	
	$expect $ equal (commonPrefix ["", ""]) ""
	$expect $ equal (commonPrefix ["/", ""]) ""
	$expect $ equal (commonPrefix ["/", "/"]) "/"
	$expect $ equal (commonPrefix ["foo/", "/foo/"]) ""
	$expect $ equal (commonPrefix ["/foo", "/foo/"]) "/"
	$expect $ equal (commonPrefix ["/foo/", "/foo/"]) "/foo/"
	$expect $ equal (commonPrefix ["/foo/bar/baz.txt.gz", "/foo/bar/baz.txt.gz.bar"]) "/foo/bar/baz.txt.gz"

test_StripPrefix :: Test
test_StripPrefix = assertions "stripPrefix" $ do
	let stripPrefix x y = fmap (toChar8) (P.stripPrefix (fromChar8 x) (fromChar8 y))
	
	$expect $ equal (stripPrefix "" "") (Just "")
	$expect $ equal (stripPrefix "" "/") (Just "/")
	$expect $ equal (stripPrefix "/" "/") (Just "")
	$expect $ equal (stripPrefix "/" "/foo") (Just "foo")
	$expect $ equal (stripPrefix "/" "/foo/bar") (Just "foo/bar")
	$expect $ equal (stripPrefix "/foo/" "/foo/bar") (Just "bar")
	$expect $ equal (stripPrefix "/foo/" "/foo/bar/baz") (Just "bar/baz")
	$expect $ equal (stripPrefix "/foo/bar" "/foo/bar.txt") (Just ".txt")
	$expect $ equal (stripPrefix "/foo/bar.txt" "/foo/bar.txt.gz") (Just ".gz")
	
	-- Test ignoring non-matching prefixes
	$expect $ equal (stripPrefix "/foo" "/foo/bar") Nothing
	$expect $ equal (stripPrefix "/foo/bar/baz" "/foo") Nothing
	$expect $ equal (stripPrefix "/foo/baz/" "/foo/bar/qux") Nothing
	$expect $ equal (stripPrefix "/foo/bar/baz" "/foo/bar/qux") Nothing
	$expect $ equal (stripPrefix "/foo/bar/baz" "/foo/bar/qux") Nothing

prop_StripPrefix :: Property
prop_StripPrefix =
	forAll posixPaths $ \x ->
	forAll posixPaths $ \suffix ->
	let prefix = x </> "" in
	let full = fromChar8 (toChar8 prefix ++ toChar8 suffix) in
	case stripPrefix prefix full of
		Nothing -> False
		Just stripped -> prefix </> stripped == full

test_SplitExtension :: Test
test_SplitExtension = assertions "splitExtension" $ do
	let splitExtension x = (toChar8 base, ext) where
		(base, ext) = P.splitExtension (fromChar8 x)
	
	$expect $ equal (splitExtension "") ("", Nothing)
	$expect $ equal (splitExtension "foo") ("foo", Nothing)
	$expect $ equal (splitExtension "foo.") ("foo", Just (T.pack ""))
	$expect $ equal (splitExtension "foo.a") ("foo", Just (T.pack "a"))
	$expect $ equal (splitExtension "foo.a/") ("foo.a/", Nothing)
	$expect $ equal (splitExtension "foo.a/bar") ("foo.a/bar", Nothing)
	$expect $ equal (splitExtension "foo.a/bar.b") ("foo.a/bar", Just (T.pack "b"))
	$expect $ equal (splitExtension "foo.a/bar.b.c") ("foo.a/bar.b", Just (T.pack "c"))

test_Collapse :: Test
test_Collapse = assertions "collapse" $ do
	let collapse x = toChar8 (P.collapse (fromChar8 x))
	
	$expect $ equal (collapse "./") "./"
	$expect $ equal (collapse "././") "./"
	$expect $ equal (collapse "../") "../"
	$expect $ equal (collapse ".././") "../"
	$expect $ equal (collapse "./../") "../"
	$expect $ equal (collapse "../../") "../../"
	$expect $ equal (collapse "parent/foo/baz/../bar") "parent/foo/bar"
	$expect $ equal (collapse "parent/foo/baz/../../bar") "parent/bar"
	$expect $ equal (collapse "parent/foo/..") "parent/"
	$expect $ equal (collapse "/parent/foo/../../../bar") "/bar"
	$expect $ equal (collapse "/./parent/foo") "/parent/foo"

test_SplitDirectories :: Test
test_SplitDirectories = assertions "splitDirectories" $ do
	let splitDirectories x = P.splitDirectories (fromChar8 x)
	    fromChar8' = map fromChar8

	$expect $ equal (splitDirectories "") (fromChar8' [])
	$expect $ equal (splitDirectories "/") (fromChar8' ["/"])
	$expect $ equal (splitDirectories "/a") (fromChar8' ["/", "a"])
	$expect $ equal (splitDirectories "/ab/cd") (fromChar8' ["/", "ab/", "cd"])
	$expect $ equal (splitDirectories "/ab/cd/") (fromChar8' ["/", "ab/", "cd/"])
	$expect $ equal (splitDirectories "ab/cd") (fromChar8' ["ab/", "cd"])
	$expect $ equal (splitDirectories "ab/cd/") (fromChar8' ["ab/", "cd/"])
	$expect $ equal (splitDirectories "ab/cd.txt") (fromChar8' ["ab/", "cd.txt"])
	$expect $ equal (splitDirectories "ab/cd/.txt") (fromChar8' ["ab/", "cd/", ".txt"])
	$expect $ equal (splitDirectories "ab/./cd") (fromChar8' ["ab/", ".", "cd"])

test_InvalidUtf8InDirectoryComponent :: Test
test_InvalidUtf8InDirectoryComponent = assertions "invalid-utf8-in-directory-component" $ do
	$expect $ equal (toText posix (fromChar8 "/\218\130.\137\141")) (Left (T.pack "/\1666.\137\141"))
	$expect $ equal (encode posix (fromChar8 "/\218\130.\137\141")) (B8.pack "/\218\130.\137\141")
	
	$expect $ equal (toText posix (fromChar8 "/\218\130.\137\141/")) (Left (T.pack "/\1666.\137\141/"))
	$expect $ equal (encode posix (fromChar8 "/\218\130.\137\141/")) (B8.pack "/\218\130.\137\141/")
	
	$expect $ equal (toText posix (fromChar8 "/\218\130.\137\141//baz")) (Left (T.pack "/\1666.\137\141/baz"))
	$expect $ equal (encode posix (fromChar8 "/\218\130.\137\141//baz")) (B8.pack "/\218\130.\137\141/baz")

test_Utf8CharInGhcEscapeArea :: Test
test_Utf8CharInGhcEscapeArea = assertions "utf8-char-in-ghc-escape-area" $ do
	let chars = "/a/\238\189\178/b"
	let path = fromChar8 chars
	$expect (equal (toChar8 path) chars)
	$expect (equal (toText posix path) (Right (T.pack "/a/\61298/b")))
	
	let chars = "/a/\xEE\xBC\x80/b"
	let path = fromChar8 chars
	$expect (equal (toChar8 path) chars)
	$expect (equal (toText posix path) (Right (T.pack "/a/\61184/b")))

test_Parsing :: Test
test_Parsing = assertions "parsing" $ do
	let p x = toChar8 (fromChar8 x)
	let w x = toString (fromString x)
	
	$expect $ equal (p "") ""
	$expect $ equal (p "/") "/"
	$expect $ equal (p "/a") "/a"
	$expect $ equal (p "/a/") "/a/"
	$expect $ equal (p "a") "a"
	$expect $ equal (p "a/") "a/"
	$expect $ equal (p "a/b") "a/b"
	$expect $ equal (p "a//b") "a/b"
	$expect $ equal (p "a/./b") "a/./b"
	$expect $ equal (p ".") "./"
	$expect $ equal (p "./") "./"
	$expect $ equal (p "..") "../"
	$expect $ equal (p "../") "../"
	
	$expect $ equal (w "") ""
	$expect $ equal (w "c:\\") "C:\\"
	$expect $ equal (w "c:\\a") "C:\\a"
	$expect $ equal (w "c:\\a\\") "C:\\a\\"
	$expect $ equal (w "a") "a"
	$expect $ equal (w "a/") "a\\"
	$expect $ equal (w "a\\") "a\\"
	$expect $ equal (w "a\\b") "a\\b"
	$expect $ equal (w "a\\\\b") "a\\b"
	$expect $ equal (w "a\\.\\b") "a\\.\\b"
	$expect $ equal (w ".") ".\\"
	$expect $ equal (w ".\\") ".\\"
	$expect $ equal (w "..") "..\\"
	$expect $ equal (w "..\\") "..\\"
	
	-- extended-length
	$expect $ equal (w "\\\\?\\C:\\") "\\\\?\\C:\\"
	$expect $ equal (w "\\\\?\\C:\\a") "\\\\?\\C:\\a"
	
	-- UNC
	$expect $ equal (w "\\\\server\\share") "\\\\server\\share"
	$expect $ equal (w "\\\\server\\share\\") "\\\\server\\share"
	$expect $ equal (w "\\\\server\\share\\a") "\\\\server\\share\\a"
	$expect $ equal (w "\\\\server\\share\\.") "\\\\server\\share\\."
	$expect $ equal (w "\\\\server\\share\\..") "\\\\server\\share\\.."
	$expect $ equal (w "\\\\server\\share\\a\\") "\\\\server\\share\\a"
	
	-- extended-length UNC
	$expect $ equal (w "\\\\?\\unc\\server\\share") "\\\\?\\UNC\\server\\share"
	$expect $ equal (w "\\\\?\\UNC\\server\\share") "\\\\?\\UNC\\server\\share"
	$expect $ equal (w "\\\\?\\UNC\\server\\share\\a") "\\\\?\\UNC\\server\\share\\a"
	
	-- magical \??\ and \\.\ Windows prefixes
	$expect $ equal (w "\\??\\Volume{12345}") "\\??\\Volume{12345}"
	$expect $ equal (w "\\\\.\\PhysicalDrive0") "\\\\.\\PhysicalDrive0"

test_UncValidity :: Test
test_UncValidity = assertions "unc-validity" $ do
	let invalid rules = not . valid rules
	$expect $ invalid windows (fromString "\\\\server")
	$expect $ invalid windows (fromString "\\\\server\\")
	$expect $ valid windows (fromString "\\\\server\\share")
	$expect $ valid windows (fromString "\\\\server\\share\\")
	$expect $ valid windows (fromString "\\\\server\\share\\a")
	$expect $ valid windows (fromString "\\??\\Volume{12345}")
	$expect $ valid windows (fromString "\\\\.\\PhysicalDrive0")

test_SplitSearchPath :: Test
test_SplitSearchPath = assertions "splitSearchPath" $ do
	let p x = map toChar8 (splitSearchPath posix (B8.pack x))
	let w x = map toString (splitSearchPath windows (T.pack x))
	
	$expect $ equal (p "a:b:c") ["a", "b", "c"]
	$expect $ equal (p "a::b:c") ["a", "./", "b", "c"]
	$expect $ equal (w "a;b;c") ["a", "b", "c"]
	$expect $ equal (w "a;;b;c") ["a", "b", "c"]

suite_SplitSearchPathString :: Suite
suite_SplitSearchPathString = suite "splitSearchPathString"
	[ test_SplitSearchPathString_Posix
	, test_SplitSearchPathString_Posix_Ghc702
	, test_SplitSearchPathString_Posix_Ghc704
	, test_SplitSearchPathString_Darwin
	, test_SplitSearchPathString_Darwin_Ghc702
	, test_SplitSearchPathString_Win32
	]

test_SplitSearchPathString_Posix :: Test
test_SplitSearchPathString_Posix = assertions "posix" $ do
	let split x = map (toText posix) (splitSearchPathString posix x)
	$expect $ equal (split "a::\xC2\xA1\xC2\xA2:\xA1\xA2") [Right "a", Right "./", Right "\xA1\xA2", Left "\xA1\xA2"]

test_SplitSearchPathString_Posix_Ghc702 :: Test
test_SplitSearchPathString_Posix_Ghc702 = assertions "posix_ghc702" $ do
	let split x = map (toText posix) (splitSearchPathString posix_ghc702 x)
	$expect $ equal (split "a::\xA1\xA2:\xEFA1\xEFA2") [Right "a", Right "./", Right "\xA1\xA2", Left "\xA1\xA2"]

test_SplitSearchPathString_Posix_Ghc704 :: Test
test_SplitSearchPathString_Posix_Ghc704 = assertions "posix_ghc704" $ do
	let split x = map (toText posix) (splitSearchPathString posix_ghc704 x)
	$expect $ equal (split "a::\xA1\xA2:\xDCA1\xDCA2") [Right "a", Right "./", Right "\xA1\xA2", Left "\xA1\xA2"]

test_SplitSearchPathString_Darwin :: Test
test_SplitSearchPathString_Darwin = assertions "darwin" $ do
	let split x = map (toText darwin) (splitSearchPathString darwin x)
	$expect $ equal (split "a::\xC2\xA1\xC2\xA2") [Right "a", Right "./", Right "\xA1\xA2"]

test_SplitSearchPathString_Darwin_Ghc702 :: Test
test_SplitSearchPathString_Darwin_Ghc702 = assertions "darwin_ghc702" $ do
	let split x = map (toText darwin) (splitSearchPathString darwin_ghc702 x)
	$expect $ equal (split "a::\xA1\xA2") [Right "a", Right "./", Right "\xA1\xA2"]

test_SplitSearchPathString_Win32 :: Test
test_SplitSearchPathString_Win32 = assertions "win32" $ do
	let split x = map (toText windows) (splitSearchPathString windows x)
	$expect $ equal (split "a;;\xA1\xA2") [Right "a", Right "\xA1\xA2"]

suite_EncodeString :: Suite
suite_EncodeString = suite "encodeString"
	[ test_EncodeString_Posix
	, test_EncodeString_Posix_Ghc702
	, test_EncodeString_Posix_Ghc704
	, test_EncodeString_Win32
	]

test_EncodeString_Posix :: Test
test_EncodeString_Posix = assertions "posix" $ do
	let enc = encodeString posix
	$expect $ equal (enc (fromChar8 "test")) "test"
	$expect $ equal (enc (fromChar8 "test\xC2\xA1\xC2\xA2")) "test\xC2\xA1\xC2\xA2"
	$expect $ equal (enc (fromChar8 "test\xA1\xA2")) "test\xA1\xA2"
	$expect $ equal (enc (fromChar8 "\xC2\xA1\xC2\xA2/test\xA1\xA2")) "\xC2\xA1\xC2\xA2/test\xA1\xA2"
	$expect $ equal (enc (fromText posix "test\xA1\xA2")) "test\xC2\xA1\xC2\xA2"

test_EncodeString_Posix_Ghc702 :: Test
test_EncodeString_Posix_Ghc702 = assertions "posix_ghc702" $ do
	let enc = encodeString posix_ghc702
	$expect $ equal (enc (fromChar8 "test")) "test"
	$expect $ equal (enc (fromChar8 "test\xA1\xA2")) "test\xEFA1\xEFA2"
	$expect $ equal (enc (fromChar8 "\xC2\xA1\xC2\xA2/test\xA1\xA2")) "\xA1\xA2/test\xEFA1\xEFA2"
	$expect $ equal (enc (fromText posix_ghc702 "test\xA1\xA2")) "test\xA1\xA2"

test_EncodeString_Posix_Ghc704 :: Test
test_EncodeString_Posix_Ghc704 = assertions "posix_ghc704" $ do
	let enc = encodeString posix_ghc704
	$expect $ equal (enc (fromChar8 "test")) "test"
	$expect $ equal (enc (fromChar8 "test\xA1\xA2")) "test\xDCA1\xDCA2"
	$expect $ equal (enc (fromChar8 "\xC2\xA1\xC2\xA2/test\xA1\xA2")) "\xA1\xA2/test\xDCA1\xDCA2"
	$expect $ equal (enc (fromText posix_ghc704 "test\xA1\xA2")) "test\xA1\xA2"

test_EncodeString_Win32 :: Test
test_EncodeString_Win32 = assertions "windows" $ do
	let enc = encodeString windows
	$expect $ equal (enc (fromString "test")) "test"
	$expect $ equal (enc (fromString "test\xA1\xA2")) "test\xA1\xA2"
	$expect $ equal (enc (fromText windows "test\xA1\xA2")) "test\xA1\xA2"

suite_DecodeString :: Suite
suite_DecodeString = suite "decodeString"
	[ test_DecodeString_Posix
	, test_DecodeString_Posix_Ghc702
	, test_DecodeString_Posix_Ghc704
	, test_DecodeString_Darwin
	, test_DecodeString_Darwin_Ghc702
	, test_DecodeString_Win32
	]

test_DecodeString_Posix :: Test
test_DecodeString_Posix = assertions "posix" $ do
	let r = posix
	let dec = decodeString
	$expect $ equal (dec r "test") (fromText r "test")
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xA1\xA2")
	$expect $ equal (dec r "test\xA1\xA2") (fromText r "test\xA1\xA2")

test_DecodeString_Posix_Ghc702 :: Test
test_DecodeString_Posix_Ghc702 = assertions "posix_ghc702" $ do
	let r = posix_ghc702
	let dec = decodeString
	$expect $ equal (dec r "test") (fromText r "test")
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xC2\xA1\xC2\xA2")
	$expect $ equal (dec r "test\xA1\xA2") (fromText r "test\xA1\xA2")
	$expect $ equal (dec r "test\xEFA1\xEFA2") (fromChar8 "test\xA1\xA2")
	$expect $ equal
		(toText r (dec r "test\xEFA1\xEFA2"))
		(Left "test\xA1\xA2")

test_DecodeString_Posix_Ghc704 :: Test
test_DecodeString_Posix_Ghc704 = assertions "posix_ghc704" $ do
	let r = posix_ghc704
	let dec = decodeString
	$expect $ equal (dec r "test") (fromText r "test")
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xC2\xA1\xC2\xA2")
	$expect $ equal (dec r "test\xA1\xA2") (fromText r "test\xA1\xA2")
	$expect $ equal (dec r "test\xDCA1\xDCA2") (fromChar8 "test\xA1\xA2")
	$expect $ equal
		(toText r (dec r "test\xDCA1\xDCA2"))
		(Left "test\xA1\xA2")

test_DecodeString_Darwin :: Test
test_DecodeString_Darwin = assertions "darwin" $ do
	let r = darwin
	let dec = decodeString
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xA1\xA2")

test_DecodeString_Darwin_Ghc702 :: Test
test_DecodeString_Darwin_Ghc702 = assertions "darwin_ghc702" $ do
	let r = darwin_ghc702
	let dec = decodeString
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xC2\xA1\xC2\xA2")
	$expect $ equal (dec r "test\xA1\xA2") (fromText r "test\xA1\xA2")

test_DecodeString_Win32 :: Test
test_DecodeString_Win32 = assertions "windows" $ do
	let r = windows
	let dec = decodeString
	$expect $ equal (dec r "test") (fromText r "test")
	$expect $ equal (dec r "test\xC2\xA1\xC2\xA2") (fromText r "test\xC2\xA1\xC2\xA2")
	$expect $ equal (dec r "test\xA1\xA2") (fromText r "test\xA1\xA2")

test_EqualsIgnoresPosixEncoding :: Test
test_EqualsIgnoresPosixEncoding = assertions "equals-ignores-posix-encoding" $ do
	$expect $ equal
		(fromChar8 "test\xA1\xA2")
		(fromText posix "test\xA1\xA2")

test_ShowRules :: Test
test_ShowRules = assertions "show-rules" $ do
	$expect $ equal (showsPrec 11 darwin "") "(Rules \"Darwin\")"
	$expect $ equal (showsPrec 11 darwin_ghc702 "") "(Rules \"Darwin (GHC 7.2)\")"
	$expect $ equal (showsPrec 11 posix "") "(Rules \"POSIX\")"
	$expect $ equal (showsPrec 11 posix_ghc702 "") "(Rules \"POSIX (GHC 7.2)\")"
	$expect $ equal (showsPrec 11 windows "") "(Rules \"Windows\")"

posixPaths :: Gen FilePath
posixPaths = sized $ fmap merge . genComponents where
	merge = fromChar8 . intercalate "/"
	validChar c = not $ elem c ['\x00', '/']
	component = do
		size <- choose (0, 10)
		vectorOf size $ arbitrary `suchThat` validChar
	genComponents n = do
		cs <- vectorOf n component
		frequency [(1, return cs), (9, return ([""] ++ cs))]

windowsPaths :: Gen FilePath
windowsPaths = oneof [dosPaths, uncPaths]

dosPaths :: Gen FilePath
dosPaths = sized $ \n -> genComponents n >>= merge where
	merge cs = do
		root <- genRoot
		let path = intercalate "\\" cs
		return $ fromString $ root ++ path
		
	reserved = ['\x00'..'\x1F'] ++ ['/', '\\', '?', '*', ':', '|', '"', '<', '>']
	reservedNames =
		[ "AUX", "CLOCK$", "COM1", "COM2", "COM3", "COM4"
		, "COM5", "COM6", "COM7", "COM8", "COM9", "CON"
		, "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6"
		, "LPT7", "LPT8", "LPT9", "NUL", "PRN"
		]
	validChar c = not (elem c reserved)
	validComponent c = not (elem (map toUpper c) reservedNames)
	component = do
		size <- choose (1, 10)
		vectorOf size $ arbitrary `suchThat` validChar
	genComponents n = vectorOf n (component `suchThat` validComponent)
	
	genRoot = do
		let upperChar = elements ['A'..'Z']
		label <- frequency [(1, return Nothing), (9, fmap Just upperChar)]
		return $ case label of
			Just c -> [c, ':', '\\']
			Nothing -> "\\"

uncPaths :: Gen FilePath
uncPaths = sized $ \n -> genComponents n >>= merge where
	merge cs = do
		root <- genRoot
		let path = intercalate "\\" cs
		return $ case cs of
			[] -> fromString (root ++ path)
			_ -> fromString (root ++ "\\" ++ path)
	validChar c = c /= '\x00' && c /= '\\'
	component = do
		size <- choose (1, 10)
		vectorOf size (arbitrary `suchThat` validChar)
	genComponents n = vectorOf n component
	
	genRoot = do
		host <- component
		share <- component
		return ("\\\\" ++ host ++ "\\" ++ share)

toChar8 :: FilePath -> String
toChar8 = B8.unpack . encode posix

fromChar8 :: String -> FilePath
fromChar8 = decode posix . B8.pack

toString :: FilePath -> String
toString = T.unpack . encode windows

fromString :: String -> FilePath
fromString = decode windows . T.pack
