/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package kotlin.jdk7.test

import java.io.IOException
import java.nio.file.*
import java.nio.file.attribute.*
import kotlin.io.path.*
import kotlin.random.Random
import kotlin.test.*

class PathExtensionsTest : AbstractPathTest() {
    private val isCaseInsensitiveFileSystem = Path("C:/") == Path("c:/")
    private val isBackslashSeparator = FileSystems.getDefault().separator == "\\"

    @Test
    fun filenameComponents() {
        fun check(path: String, name: String, nameNoExt: String, extension: String) {
            val p = Path(path)
            assertEquals(name, p.name, "name")
            assertEquals(nameNoExt, p.nameWithoutExtension, "nameWithoutExtension")
            assertEquals(extension, p.extension, "extension")
        }

        check(path = "aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb")
        check(path = "aaa", name = "aaa", nameNoExt = "aaa", extension = "")
        check(path = "aaa.", name = "aaa.", nameNoExt = "aaa", extension = "")
        check(path = ".aaa", name = ".aaa", nameNoExt = "", extension = "aaa")
        check(path = "/dir.ext/aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb")
        check(path = "/dir.ext/aaa", name = "aaa", nameNoExt = "aaa", extension = "")
        check(path = "/", name = "", nameNoExt = "", extension = "")
        check(path = "", name = "", nameNoExt = "", extension = "")
    }

    @Test
    fun invariantSeparators() {
        val path = Path("base") / "nested" / "leaf"
        assertEquals("base/nested/leaf", path.invariantSeparatorsPathString)

        val path2 = Path("base", "nested", "terminal")
        assertEquals("base/nested/terminal", path2.invariantSeparatorsPathString)
    }

    @Test
    fun createNewFile() {
        val dir = createTempDirectory().cleanupRecursively()

        val file = dir / "new-file"

        assertTrue(file.notExists())

        file.createFile()
        assertTrue(file.exists())
        assertTrue(file.isRegularFile())

        assertFailsWith<FileAlreadyExistsException> { file.createFile() }
    }

    @Test
    fun createParentDirectories() {
        val dir = createTempDirectory().cleanupRecursively()
        val file = dir / "test-dir" / "sub-dir" / "new-file"
        val parent = file.parent!!

        assertTrue(file.notExists())
        assertTrue(parent.notExists())

        val result = file.createParentDirectories()
        assertTrue(file.notExists())
        assertTrue(parent.isDirectory())
        assertEquals(file, result)

        file.createFile()
        file.createParentDirectories()
        assertTrue(file.exists())
        assertTrue(parent.isDirectory())
    }

    @Test
    fun createParentDirectoriesRelativePath() {
        run {
            val path = Path("test_path_without_parent")
            path.createParentDirectories()
            assertTrue(path.toAbsolutePath().parent!!.isDirectory())
        }

        run {
            val path = Path("build/test_subdirectory/test_path")
            val parent = path.parent!!
            assertTrue(parent.notExists())
            parent.cleanupRecursively()
            path.createParentDirectories()
            assertTrue(path.notExists())
            assertTrue(parent.isDirectory())
        }
    }

    @Test
    fun createParentDirectoriesOverExistingSymlink() {
        val root = createTempDirectory("createParent-root").cleanupRecursively()
        val dir = (root / "dir").createDirectory()
        val link = (root / "link").tryCreateSymbolicLinkTo(dir) ?: return
        val file = (link / "file")
        file.createParentDirectories().writeText("txt")
        assertTrue((dir / "file").isRegularFile())
    }

    @Test
    fun createTempFileDefaultDir() {
        val file1 = createTempFile().cleanup()
        val file2 = createTempFile(directory = null).cleanup()

        assertEquals(file1.parent, file2.parent)
    }

    @Test
    fun createTempDirectoryDefaultDir() {
        val dir1 = createTempDirectory().cleanup()
        val dir2 = createTempDirectory(directory = null).cleanupRecursively()
        val dir3 = createTempDirectory(dir2)

        assertEquals(dir1.parent, dir2.parent)
        assertNotEquals(dir2.parent, dir3.parent)
    }

    @Test
    fun copyTo() {
        val root = createTempDirectory("copyTo-root").cleanupRecursively()
        val srcFile = createTempFile(root, "src")
        val dstFile = createTempFile(root, "dst")

        srcFile.writeText("Hello, World!")
        assertFailsWith<FileAlreadyExistsException>("copy do not overwrite existing file") {
            srcFile.copyTo(dstFile)
        }

        var dst = srcFile.copyTo(dstFile, overwrite = true)
        assertSame(dst, dstFile)
        compareFiles(srcFile, dst, "copy with overwrite over existing file")

        srcFile.copyTo(srcFile)
        srcFile.copyTo(srcFile, overwrite = true)
        compareFiles(dst, srcFile, "copying file to itself leaves it intact")

        assertTrue(dstFile.deleteIfExists())
        dst = srcFile.copyTo(dstFile)
        compareFiles(srcFile, dst, "copy to new file")

        val subDst = dstFile.resolve("foo/bar")
        assertFailsWith<FileSystemException> { srcFile.copyTo(subDst) }
        assertFailsWith<FileSystemException> { srcFile.copyTo(subDst, overwrite = true) }
        assertTrue(dstFile.deleteIfExists())
        assertFailsWith<FileSystemException> { srcFile.copyTo(subDst) }

        dstFile.createDirectory()
        val child = dstFile.resolve("child").createFile()
        assertFailsWith<DirectoryNotEmptyException>("copy with overwrite do not overwrite non-empty dir") {
            srcFile.copyTo(dstFile, overwrite = true)
        }
        child.deleteExisting()

        srcFile.copyTo(dstFile, overwrite = true)
        assertEquals(srcFile.readText(), dstFile.readText(), "copy with overwrite over empty dir")

        assertTrue(srcFile.deleteIfExists())
        assertTrue(dstFile.deleteIfExists())

        assertFailsWith<NoSuchFileException> {
            srcFile.copyTo(dstFile)
        }

        srcFile.createDirectory()
        srcFile.resolve("somefile").writeText("some content")
        dstFile.writeText("")
        assertFailsWith<FileAlreadyExistsException>("copy dir do not overwrite file") {
            srcFile.copyTo(dstFile)
        }
        srcFile.copyTo(dstFile, overwrite = true)
        assertTrue(dstFile.isDirectory())
        assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content")

        assertFailsWith<FileAlreadyExistsException>("copy dir do not overwrite dir") {
            srcFile.copyTo(dstFile)
        }

        srcFile.copyTo(dstFile, overwrite = true)
        assertTrue(dstFile.isDirectory())
        assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content")

        dstFile.resolve("somefile2").writeText("some content2")
        assertFailsWith<DirectoryNotEmptyException>("copy dir do not overwrite non-empty dir") {
            srcFile.copyTo(dstFile, overwrite = true)
        }
    }

    @Test
    fun copyToRestrictedReadSource() {
        val root = createTempDirectory("copyTo-root").cleanupRecursively()

        // copy file
        val srcFile = createTempFile(root, "srcFile")
        val dstFile = root.resolve("dstFile")

        withRestrictedRead(srcFile, alsoReset = listOf(dstFile)) {
            assertFailsWith<AccessDeniedException> { srcFile.copyTo(dstFile) } // fails to copy restricted file
        }

        // copy directory
        val srcDirectory = createTempDirectory(root, "srcDirectory")
        val dstDirectory = root.resolve("dstDirectory")

        withRestrictedRead(srcDirectory, alsoReset = listOf(dstDirectory)) {
            srcDirectory.copyTo(dstDirectory) // successfully copies restricted directory
            assertFalse(dstDirectory.isReadable()) // copies access permissions
        }
    }

    @Test
    fun copyToRestrictedWriteDestination() {
        val root = createTempDirectory("copyTo-root").cleanupRecursively()

        // copy file
        val srcFile = createTempFile(root, "srcFile")
        val dstFile = createTempFile(root, "dstFile")

        withRestrictedWrite(dstFile) {
            assertFailsWith<FileAlreadyExistsException> { srcFile.copyTo(dstFile) }
            try {
                srcFile.copyTo(dstFile, overwrite = true) // successfully overwrites restricted file in Unix
                assertTrue(dstFile.isWritable()) // copies access permissions
            } catch (_: AccessDeniedException) {
                // Windows does not allow to overwrite readonly file
            }
        }

        // copy directory
        val srcDirectory = createTempDirectory(root, "srcDirectory")
        val dstDirectory = createTempDirectory(root, "dstDirectory")

        withRestrictedWrite(dstDirectory) {
            assertFailsWith<FileAlreadyExistsException> { srcDirectory.copyTo(dstDirectory) }
            srcDirectory.copyTo(dstDirectory, overwrite = true) // successfully overwrites restricted directory
            assertTrue(dstDirectory.isWritable()) // copies access permissions
        }
    }

    @Test
    fun copyToSymlinkDestination() {
        val root = createTempDirectory("copyTo-root").cleanupRecursively()
        val targetFile = createTempFile(root, "targetFile").also { it.writeText("target file") }
        val targetDirectory = createTempDirectory(root, "targetDirectory").also { it.resolve("a").createFile() }

        // copy file
        val srcFile = createTempFile(root, "srcFile").also { it.writeText("source file") }
        val dstFile = root.resolve("dstFile").tryCreateSymbolicLinkTo(targetFile) ?: return

        assertFailsWith<FileAlreadyExistsException> {
            srcFile.copyTo(dstFile)
        }
        assertFailsWith<FileAlreadyExistsException> {
            srcFile.copyTo(dstFile, LinkOption.NOFOLLOW_LINKS)
        }
        assertTrue(dstFile.isSymbolicLink())
        assertEquals("target file", dstFile.readText())

        // copy directory
        val srcDirectory = createTempDirectory(root, "srcDirectory")
        val dstDirectory = root.resolve("dstDirectory").tryCreateSymbolicLinkTo(targetDirectory) ?: return

        assertFailsWith<FileAlreadyExistsException> {
            srcDirectory.copyTo(dstDirectory)
        }
        assertFailsWith<FileAlreadyExistsException> {
            srcDirectory.copyTo(dstDirectory, LinkOption.NOFOLLOW_LINKS)
        }
        assertTrue(dstDirectory.isSymbolicLink())
        assertEquals("a", dstDirectory.listDirectoryEntries().single().name)

        // overwrite file
        srcFile.copyTo(dstFile, overwrite = true)
        assertFalse(dstFile.isSymbolicLink())
        assertEquals("source file", dstFile.readText())
        assertEquals("target file", targetFile.readText())

        dstFile.deleteExisting()
        dstFile.tryCreateSymbolicLinkTo(targetFile)!!
        srcFile.copyTo(dstFile, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS)
        assertFalse(dstFile.isSymbolicLink())
        assertEquals("source file", dstFile.readText())
        assertEquals("target file", targetFile.readText())

        // overwrite directory
        srcDirectory.copyTo(dstDirectory, overwrite = true)
        assertFalse(dstDirectory.isSymbolicLink())
        assertEquals(emptyList(), dstDirectory.listDirectoryEntries())
        assertEquals("a", targetDirectory.listDirectoryEntries().single().name)

        dstDirectory.deleteExisting()
        dstDirectory.tryCreateSymbolicLinkTo(targetDirectory)!!
        srcDirectory.copyTo(dstDirectory, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS)
        assertFalse(dstDirectory.isSymbolicLink())
        assertEquals(emptyList(), dstDirectory.listDirectoryEntries())
        assertEquals("a", targetDirectory.listDirectoryEntries().single().name)
    }

    @Test
    fun copyToBrokenSymlinkDestination() {
        val root = createTempDirectory("copyTo-root").cleanupRecursively()
        val symlinkTarget = root.resolve("target")

        // copy file
        val srcFile = createTempFile(root, "srcFile").also { it.writeText("source file") }
        val dstFile = root.resolve("dstFile").tryCreateSymbolicLinkTo(symlinkTarget) ?: return

        assertFailsWith<FileAlreadyExistsException> {
            srcFile.copyTo(dstFile)
        }
        assertFailsWith<FileAlreadyExistsException> {
            srcFile.copyTo(dstFile, LinkOption.NOFOLLOW_LINKS)
        }
        assertTrue(dstFile.isSymbolicLink())
        assertFailsWith<NoSuchFileException> {
            dstFile.readText()
        }

        // copy directory
        val srcDirectory = createTempDirectory(root, "srcDirectory")
        val dstDirectory = root.resolve("dstDirectory").tryCreateSymbolicLinkTo(symlinkTarget) ?: return

        assertFailsWith<FileAlreadyExistsException> {
            srcDirectory.copyTo(dstDirectory)
        }
        assertFailsWith<FileAlreadyExistsException> {
            srcDirectory.copyTo(dstDirectory, LinkOption.NOFOLLOW_LINKS)
        }
        assertTrue(dstDirectory.isSymbolicLink())
        assertFailsWith<FileSystemException> { // Fails with NoSuchFileException in Unix, NotDirectoryException in Windows.
            dstDirectory.listDirectoryEntries()
        }

        // overwrite file
        srcFile.copyTo(dstFile, overwrite = true)
        assertFalse(dstFile.isSymbolicLink())
        assertEquals("source file", dstFile.readText())

        dstFile.deleteExisting()
        dstFile.tryCreateSymbolicLinkTo(symlinkTarget)!!
        srcFile.copyTo(dstFile, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS)
        assertFalse(dstFile.isSymbolicLink())
        assertEquals("source file", dstFile.readText())

        // overwrite directory
        srcDirectory.copyTo(dstDirectory, overwrite = true)
        assertFalse(dstDirectory.isSymbolicLink())
        assertEquals(emptyList(), dstDirectory.listDirectoryEntries())

        dstDirectory.deleteExisting()
        dstDirectory.tryCreateSymbolicLinkTo(symlinkTarget)!!
        srcDirectory.copyTo(dstDirectory, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS)
        assertFalse(dstDirectory.isSymbolicLink())
        assertEquals(emptyList(), dstDirectory.listDirectoryEntries())
    }

    @Test
    fun copyToNameWithoutParent() {
        val currentDir = Path("").absolute()
        val srcFile = createTempFile().cleanup()
        val dstFile = createTempFile(directory = currentDir).cleanup()

        srcFile.writeText("Hello, World!", Charsets.UTF_8)
        dstFile.deleteExisting()

        val dstRelative = Path(dstFile.name)

        srcFile.copyTo(dstRelative)

        assertEquals(srcFile.readText(), dstFile.readText())
    }

    @Test
    fun copyToDstLinkPointingToSrc() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val src = root.resolve("src").createFile()
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(src) ?: return

            assertTrue(src.isSameFileAs(dstLink))
            assertTrue(dstLink.isSameFileAs(src))
            assertFailsWith<FileAlreadyExistsException> {
                src.copyTo(dstLink, *options)
            }
            assertTrue(dstLink.isSymbolicLink())
        }
    }

    @Test
    fun copyToDstLinkPointingToSrcOverwrite() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val src = root.resolve("src").createFile()
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(src) ?: return

            src.copyTo(dstLink, StandardCopyOption.REPLACE_EXISTING, *options)
            assertFalse(dstLink.isSymbolicLink())
        }
    }

    @Test
    fun copyToSrcLinkAndDstLinkPointingToSameFile() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val original = root.resolve("original").createFile()
            val srcLink = root.resolve("srcLink").tryCreateSymbolicLinkTo(original) ?: return
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(original) ?: return

            assertTrue(srcLink.isSameFileAs(dstLink))
            assertTrue(dstLink.isSameFileAs(srcLink))
            assertFailsWith<FileAlreadyExistsException> {
                srcLink.copyTo(dstLink, *options)
            }
            assertTrue(dstLink.isSymbolicLink())
        }
    }

    @Test
    fun copyToSrcLinkAndDstLinkPointingToSameFileOverwrite() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val original = root.resolve("original").createFile()
            val srcLink = root.resolve("srcLink").tryCreateSymbolicLinkTo(original) ?: return
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(original) ?: return

            srcLink.copyTo(dstLink, StandardCopyOption.REPLACE_EXISTING, *options)

            if (LinkOption.NOFOLLOW_LINKS in options) {
                assertTrue(dstLink.isSymbolicLink()) // src symlink was copied
            } else {
                assertFalse(dstLink.isSymbolicLink()) // src symlink target was copied
            }
        }
    }

    @Test
    fun copyToSameLinkDifferentRoute() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val original = root.resolve("original").createFile()
            val srcLink = root.resolve("srcLink").tryCreateSymbolicLinkTo(original) ?: return
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(root)?.resolve("srcLink") ?: return

            assertTrue(srcLink.isSameFileAs(dstLink))
            assertTrue(dstLink.isSameFileAs(srcLink))

            if (LinkOption.NOFOLLOW_LINKS in options) {
                srcLink.copyTo(dstLink, *options) // same file
            } else {
                assertFailsWith<FileAlreadyExistsException> {
                    srcLink.copyTo(dstLink, *options) // target of srcLink copied to srcLink location
                }
            }

            assertTrue(dstLink.isSymbolicLink())
        }
    }

    @Test
    fun copyToSameLinkDifferentRouteOverwrite() {
        for (options in listOf(arrayOf(), arrayOf<CopyOption>(LinkOption.NOFOLLOW_LINKS))) {
            val root = createTempDirectory().cleanupRecursively()
            val original = root.resolve("original").createFile()
            val srcLink = root.resolve("srcLink").tryCreateSymbolicLinkTo(original) ?: return
            val dstLink = root.resolve("dstLink").tryCreateSymbolicLinkTo(root)?.resolve("srcLink") ?: return

            if (LinkOption.NOFOLLOW_LINKS in options) {
                srcLink.copyTo(dstLink, StandardCopyOption.REPLACE_EXISTING, *options) // same file
            } else {
                // dstLink is deleted before srcLink gets copied.
                // Actually srcLink gets removed because dstLink is srcLink with different path.
                val error = assertFailsWith<NoSuchFileException> {
                    srcLink.copyTo(dstLink, StandardCopyOption.REPLACE_EXISTING, *options)
                }
                assertEquals(srcLink.toString(), error.file)
                assertFalse(srcLink.exists(LinkOption.NOFOLLOW_LINKS))
                assertFalse(dstLink.exists(LinkOption.NOFOLLOW_LINKS))
            }
        }
    }

    @Test
    fun copyToSameFileDifferentRoute() {
        val root = createTempDirectory().cleanupRecursively()
        val src = root.resolve("src").createFile()
        val dst = root.resolve("dstLink").tryCreateSymbolicLinkTo(root)?.resolve("src") ?: return

        assertTrue(src.isSameFileAs(dst))
        assertTrue(dst.isSameFileAs(src))
        src.copyTo(dst)
    }

    @Test
    fun copyToSameFileDifferentRouteOverwrite() {
        val root = createTempDirectory().cleanupRecursively()
        val src = root.resolve("src").createFile()
        val dst = root.resolve("dstLink").tryCreateSymbolicLinkTo(root)?.resolve("src") ?: return

        src.copyTo(dst, overwrite = true)
    }

    @Test
    fun moveTo() {
        val root = createTempDirectory("moveTo-root").cleanupRecursively()
        val original = createTempFile(root, "original")
        val srcFile = createTempFile(root, "src")
        val dstFile = createTempFile(root, "dst")
        fun restoreSrcFile() { original.copyTo(srcFile, overwrite = true) }
        original.writeText("Hello, World!")
        restoreSrcFile()

        assertFailsWith<FileAlreadyExistsException>("do not overwrite existing file") {
            srcFile.moveTo(dstFile)
        }

        var dst = srcFile.moveTo(dstFile, overwrite = true)
        assertSame(dst, dstFile)
        compareFiles(original, dst, "move with overwrite over existing file")
        assertTrue(srcFile.notExists())

        restoreSrcFile()
        srcFile.moveTo(srcFile)
        srcFile.moveTo(srcFile, overwrite = true)

        compareFiles(original, srcFile, "move file to itself leaves it intact")

        assertTrue(dstFile.deleteIfExists())
        dst = srcFile.moveTo(dstFile)
        compareFiles(original, dst, "move to new file")

        restoreSrcFile()
        val subDst = dstFile.resolve("foo/bar")
        assertFailsWith<FileSystemException> { srcFile.moveTo(subDst) }
        assertFailsWith<FileSystemException> { srcFile.moveTo(subDst, overwrite = true) }
        assertTrue(dstFile.deleteIfExists())
        assertFailsWith<FileSystemException> { srcFile.moveTo(subDst) }

        dstFile.createDirectory()
        val child = dstFile.resolve("child").createFile()
        assertFailsWith<DirectoryNotEmptyException>("move with overwrite do not overwrite non-empty dir") {
            srcFile.moveTo(dstFile, overwrite = true)
        }
        child.deleteExisting()

        srcFile.moveTo(dstFile, overwrite = true)
        compareFiles(original, dstFile, "move with overwrite over empty dir")

        assertTrue(srcFile.notExists())
        assertTrue(dstFile.deleteIfExists())

        assertFailsWith<NoSuchFileException> {
            srcFile.moveTo(dstFile)
        }

        srcFile.createDirectory()
        srcFile.resolve("somefile").writeText("some content")
        dstFile.writeText("")
        assertFailsWith<FileAlreadyExistsException>("move dir do not overwrite file") {
            srcFile.moveTo(dstFile)
        }
        srcFile.moveTo(dstFile, overwrite = true)
        assertTrue(dstFile.isDirectory())
        assertEquals(listOf(dstFile / "somefile"), dstFile.listDirectoryEntries(), "directory is moved with its content")
    }

    private fun compareFiles(src: Path, dst: Path, message: String? = null) {
        assertTrue(dst.exists())
        assertEquals(src.isRegularFile(), dst.isRegularFile(), message)
        assertEquals(src.isDirectory(), dst.isDirectory(), message)
        if (dst.isRegularFile()) {
            assertTrue(src.readBytes().contentEquals(dst.readBytes()), message)
        }
    }

    @Test
    fun fileSize() {
        val file = createTempFile().cleanup()
        assertEquals(0, file.fileSize())

        file.writeBytes(ByteArray(100))
        assertEquals(100, file.fileSize())

        file.appendText("Hello", Charsets.US_ASCII)
        assertEquals(105, file.fileSize())

        file.deleteExisting()
        assertFailsWith<NoSuchFileException> { file.fileSize() }
    }

    @Test
    fun deleteExisting() {
        val file = createTempFile().cleanup()
        file.deleteExisting()
        assertFailsWith<NoSuchFileException> { file.deleteExisting() }

        val dir = createTempDirectory().cleanup()
        dir.deleteExisting()
        assertFailsWith<NoSuchFileException> { dir.deleteExisting() }
    }

    @Test
    fun deleteIfExists() {
        val file = createTempFile().cleanup()
        assertTrue(file.deleteIfExists())
        assertFalse(file.deleteIfExists())

        val dir = createTempDirectory().cleanup()
        assertTrue(dir.deleteIfExists())
        assertFalse(dir.deleteIfExists())
    }

    @Test
    fun attributeGettersOnFile() {
        val file = createTempFile("temp", ".file").cleanup()
        assertTrue(file.exists())
        assertFalse(file.notExists())
        assertTrue(file.isRegularFile())
        assertFalse(file.isDirectory())
        assertFalse(file.isSymbolicLink())
        assertTrue(file.isReadable())
        assertTrue(file.isWritable())
        assertTrue(file.isSameFileAs(file))

        // The default value of these depends on the current operating system, so just check that
        // they don't throw an exception.
        file.isExecutable()
        file.isHidden()
    }

    @Test
    fun attributeGettersOnDirectory() {
        val file = createTempDirectory(".tmpdir").cleanup()
        assertTrue(file.exists())
        assertFalse(file.notExists())
        assertFalse(file.isRegularFile())
        assertTrue(file.isDirectory())
        assertFalse(file.isSymbolicLink())
        assertTrue(file.isReadable())
        assertTrue(file.isWritable())
        assertTrue(file.isSameFileAs(file))

        file.isExecutable()
        file.isHidden()
    }

    @Test
    fun attributeGettersOnNonExistentPath() {
        val file = createTempDirectory().cleanup().resolve("foo")
        assertFalse(file.exists())
        assertTrue(file.notExists())
        assertFalse(file.isRegularFile())
        assertFalse(file.isDirectory())
        assertFalse(file.isSymbolicLink())
        assertFalse(file.isReadable())
        assertFalse(file.isWritable())
        assertTrue(file.isSameFileAs(file))

        file.isExecutable()
        // This function will either throw an exception or return false,
        // depending on the operating system.
        try {
            assertFalse(file.isHidden())
        } catch (e: IOException) {
        }
    }

    private interface SpecialFileAttributesView : FileAttributeView
    private interface SpecialFileAttributes : BasicFileAttributes

    @Test
    fun readWriteAttributes() {
        val file = createTempFile().cleanup()
        val modifiedTime = file.getLastModifiedTime()
        assertEquals(modifiedTime, file.getAttribute("lastModifiedTime"))
        assertEquals(modifiedTime, file.getAttribute("basic:lastModifiedTime"))
        assertEquals(modifiedTime, file.readAttributes<BasicFileAttributes>().lastModifiedTime())
        assertEquals(modifiedTime, file.readAttributes("basic:lastModifiedTime,creationTime")["lastModifiedTime"])
        assertEquals(modifiedTime, file.readAttributes("*")["lastModifiedTime"])

        assertFailsWith<UnsupportedOperationException> { file.readAttributes<SpecialFileAttributes>() }
        assertFailsWith<UnsupportedOperationException> { file.readAttributes("really_unsupported_view:*") }
        assertFailsWith<IllegalArgumentException> { file.readAttributes("basic:really_unknown_attribute") }

        val newTime1 = FileTime.fromMillis(modifiedTime.toMillis() + 3600_000)
        file.setLastModifiedTime(newTime1)
        assertEquals(newTime1, file.getLastModifiedTime())

        val newTime2 = FileTime.fromMillis(modifiedTime.toMillis() + 2 * 3600_000)
        file.setAttribute("lastModifiedTime", newTime2)
        assertEquals(newTime2, file.getLastModifiedTime())

        val newTime3 = FileTime.fromMillis(modifiedTime.toMillis() + 3 * 3600_000)
        file.fileAttributesView<BasicFileAttributeView>().setTimes(newTime3, null, null)
        assertEquals(newTime3, file.getLastModifiedTime())

        assertFailsWith<UnsupportedOperationException> { file.fileAttributesView<SpecialFileAttributesView>() }
        assertNull(file.fileAttributesViewOrNull<SpecialFileAttributesView>())

        file.setAttribute("lastModifiedTime", null)
        assertEquals(newTime3, file.getLastModifiedTime())
    }

    @Test
    fun links() {
        val dir = createTempDirectory().cleanupRecursively()
        val original = createTempFile(dir)
        original.writeBytes(Random.nextBytes(100))

        val link = try {
            (dir / ("link-" + original.fileName)).createLinkPointingTo(original)
        } catch (e: IOException) {
            // may require a privilege
            println("Creating a link failed with $e")
            return
        }

        assertTrue(link.isRegularFile())
        assertTrue(link.isRegularFile(LinkOption.NOFOLLOW_LINKS))
        assertTrue(original.isSameFileAs(link))
        compareFiles(original, link)
        assertFailsWith<NotLinkException> { link.readSymbolicLink() }
    }

    @Test
    fun symlinks() {
        val dir = createTempDirectory().cleanupRecursively()
        val original = createTempFile(dir)
        original.writeBytes(Random.nextBytes(100))

        val symlink = try {
            (dir / ("symlink-" + original.fileName)).createSymbolicLinkPointingTo(original)
        } catch (e: IOException) {
            // may require a privilege
            println("Creating a symlink failed with $e")
            return
        }

        assertTrue(symlink.isRegularFile())
        assertFalse(symlink.isRegularFile(LinkOption.NOFOLLOW_LINKS))
        assertTrue(original.isSameFileAs(symlink))
        compareFiles(original, symlink)
        assertEquals(original, symlink.readSymbolicLink())
    }

    @Test
    fun directoryEntriesList() {
        val dir = createTempDirectory().cleanupRecursively()
        assertEquals(0, dir.listDirectoryEntries().size)

        val file = dir.resolve("f1").createFile()
        assertEquals(listOf(file), dir.listDirectoryEntries())

        val fileTxt = createTempFile(dir, suffix = ".txt")
        assertEquals(listOf(fileTxt), dir.listDirectoryEntries("*.txt"))

        assertFailsWith<NotDirectoryException> { file.listDirectoryEntries() }
    }

    @Test
    fun directoryEntriesUseSequence() {
        val dir = createTempDirectory().cleanupRecursively()
        assertEquals(0, dir.useDirectoryEntries { it.toList() }.size)

        val file = dir.resolve("f1").createFile()
        assertEquals(listOf(file), dir.useDirectoryEntries { it.toList() })

        val fileTxt = createTempFile(dir, suffix = ".txt")
        assertEquals(listOf(fileTxt), dir.useDirectoryEntries("*.txt") { it.toList() })

        assertFailsWith<NotDirectoryException> { file.useDirectoryEntries { error("shouldn't get here") } }
    }

    @Test
    fun directoryEntriesForEach() {
        val dir = createTempDirectory().cleanupRecursively()
        dir.forEachDirectoryEntry { error("shouldn't get here, but received $it") }

        val file = createTempFile(dir)
        dir.forEachDirectoryEntry { assertEquals(file, it) }

        val fileTxt = createTempFile(dir, suffix = ".txt")
        dir.forEachDirectoryEntry("*.txt") { assertEquals(fileTxt, it) }

        assertFailsWith<NotDirectoryException> { file.forEachDirectoryEntry { error("shouldn't get here, but received $it") } }
    }


    private fun testRelativeTo(expected: String?, path: String, base: String) =
        testRelativeTo(expected?.let { Path(it) }, Path(path), Path(base))
    private fun testRelativeTo(expected: String, path: Path, base: Path) =
        testRelativeTo(Path(expected), path, base)

    private fun testRelativeTo(expected: Path?, path: Path, base: Path) {
        val context = "path: '$path', base: '$base'"
        if (expected != null) {
            assertEquals(expected, path.relativeTo(base), context)
        } else {
            val e = assertFailsWith<IllegalArgumentException>(context) { path.relativeTo(base) }
            val message = assertNotNull(e.message)
            assertTrue(path.toString() in message, message)
            assertTrue(base.toString() in message, message)
        }
        assertEquals(expected, path.relativeToOrNull(base), context)
        assertEquals(expected ?: path, path.relativeToOrSelf(base), context)
    }

    @Test
    fun relativeToRooted() {
        val file1 = "/foo/bar/baz"
        val file2 = "/foo/baa/ghoo"

        testRelativeTo("../../bar/baz", file1, file2)

        val file3 = "/foo/bar"

        testRelativeTo("baz", file1, file3)
        testRelativeTo("..", file3, file1)

        val file4 = "/foo/bar/"

        testRelativeTo("baz", file1, file4)
        testRelativeTo("..", file4, file1)
        testRelativeTo("", file3, file4)
        testRelativeTo("", file4, file3)

        val file5 = "/foo/baran"

        testRelativeTo("../bar", file3, file5)
        testRelativeTo("../baran", file5, file3)
        testRelativeTo("../bar", file4, file5)
        testRelativeTo("../baran", file5, file4)

        if (isBackslashSeparator) {
            val file6 = "C:\\Users\\Me"
            val file7 = "C:\\Users\\Me\\Documents"

            testRelativeTo("..", file6, file7)
            testRelativeTo("Documents", file7, file6)

            val file8 = """\\my.host\home/user/documents/vip"""
            val file9 = """\\my.host\home/other/images/nice"""

            testRelativeTo("../../../user/documents/vip", file8, file9)
            testRelativeTo("../../../other/images/nice", file9, file8)
        }

        if (isCaseInsensitiveFileSystem) {
            testRelativeTo("bar", "C:/bar", "c:/")
        }
    }

    @Test
    fun relativeToRelative() {
        val nested = Path("foo/bar")
        val base = Path("foo")

        testRelativeTo("bar", nested, base)
        testRelativeTo("..", base, nested)

        val empty = Path("")
        val current = Path(".")
        val parent = Path("..")
        val outOfRoot = Path("../bar")

        testRelativeTo("../bar", outOfRoot, empty)
        testRelativeTo("../../bar", outOfRoot, base)
        testRelativeTo("bar", outOfRoot, parent)
        testRelativeTo("..", parent, outOfRoot)

        val root = Path("/root")
        val files = listOf(nested, base, empty, outOfRoot, current, parent)
        val bases = listOf(nested, base, empty, current)

        for (file in files)
            // file should have empty path relative to itself
            testRelativeTo("", file, file)

        for (file in files) {
            @Suppress("NAME_SHADOWING")
            for (base in bases) {
                val rootedFile = root.resolve(file)
                val rootedBase = root.resolve(base)
                assertEquals(rootedFile.relativeTo(rootedBase), file.relativeTo(base), "nested: $file, base: $base")
            }
        }
    }

    @Test
    fun relativeToFails() {
        val absolute = Path("/foo/bar/baz")
        val relative = Path("foo/bar")
        val networkShare1 = Path("""\\my.host\share1/folder""")
        val networkShare2 = Path("""\\my.host\share2\folder""")

        val allFiles = listOf(absolute, relative) + if (isBackslashSeparator) listOf(networkShare1, networkShare2) else emptyList()
        for (file in allFiles) {
            for (base in allFiles) {
                if (file != base) testRelativeTo(null, file, base)
            }
        }

        if (isBackslashSeparator) {
            testRelativeTo(null, "C:/dir1", "D:/dir2")
        }

        testRelativeTo(null, "foo", "..")
        testRelativeTo(null, "../foo", "../..")
    }

    @Test
    fun relativeTo() {
        testRelativeTo("kotlin", "src/kotlin", "src")
        testRelativeTo("", "dir", "dir")
        testRelativeTo("..", "dir", "dir/subdir")
        testRelativeTo("../../test", "test", "dir/dir")
        testRelativeTo("foo/bar", "../../foo/bar", "../../sub/../.")
        testRelativeTo(null, "../../foo/bar", "../../sub/../..")
    }

    @Test
    fun absolutePaths() {
        val relative = Path("./example")
        assertTrue(relative.absolute().isAbsolute)
        assertEquals(relative.absolute().pathString, relative.absolutePathString())
    }
}
