/*
 * 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.ByteArrayOutputStream
import java.nio.CharBuffer
import java.nio.charset.Charset
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import kotlin.io.path.*
import kotlin.random.Random
import kotlin.test.*

class PathReadWriteTest : AbstractPathTest() {

    private fun String.encodeToByteArray(charset: Charset): ByteArray {
        val out = ByteArrayOutputStream()
        out.writer(charset).use { it.append(this) }
        return out.toByteArray()
    }

    private val hexFormat = HexFormat {
        bytes.bytesPerLine = 32
        bytes.bytesPerGroup = 8
    }

    private fun Path.testContentEquals(expectedContent: ByteArray, charset: Charset) {
        val expected = expectedContent.toHexString(hexFormat)
        val actualContent = readBytes()
        val actual = actualContent.toHexString(hexFormat)
        assertEquals(expected, actual, "$charset. Expected size is ${expectedContent.size}, actual size is ${actualContent.size}")
    }

    private fun Path.testWriteText(text: String, charset: Charset) {
        // Path.writeText
        val encodedText = text.encodeToByteArray(charset)

        writeText(text, charset)
        testContentEquals(encodedText, charset)

        writeText(StringBuilder(text), charset)
        testContentEquals(encodedText, charset)

        val position = 1.coerceAtMost(text.length)
        val limit = (text.length - 1).coerceAtLeast(position)
        val charBuffer = CharBuffer.wrap(text, position, limit)
        val encodedCharBuffer = text.substring(position, limit).encodeToByteArray(charset)

        writeText(charBuffer, charset)
        testContentEquals(encodedCharBuffer, charset)
        assertEquals(position, charBuffer.position())
        assertEquals(limit, charBuffer.limit())
        assertEquals(text.length, charBuffer.capacity())

        // Path.appendText
        val prefix = "_"
        val encodedPrefix = prefix.encodeToByteArray(charset)

        writeText(prefix, charset)
        appendText(text, charset)
        testContentEquals(encodedPrefix + encodedText, charset)

        writeText(prefix, charset)
        appendText(StringBuilder(text), charset)
        testContentEquals(encodedPrefix + encodedText, charset)

        writeText(prefix, charset)
        appendText(charBuffer, charset)
        testContentEquals(encodedPrefix + encodedCharBuffer, charset)
        assertEquals(position, charBuffer.position())
        assertEquals(limit, charBuffer.limit())
        assertEquals(text.length, charBuffer.capacity())

        // File.writeText
        toFile().writeText(text, charset)
        testContentEquals(encodedText, charset)

        // File.appendText
        toFile().writeText(prefix, charset)
        toFile().appendText(text, charset)
        testContentEquals(encodedPrefix + encodedText, charset)
    }

    @Test fun writeText() {
        val charsets = listOf(
            Charsets.UTF_8,
            Charsets.UTF_16,
            Charsets.UTF_32,
            Charsets.ISO_8859_1,
            Charsets.US_ASCII,
        )

        val highSurrogate = Char.MIN_HIGH_SURROGATE
        val lowSurrogate = Char.MIN_LOW_SURROGATE

        val smallString = "Hello"

        val chunkSize = DEFAULT_BUFFER_SIZE
        val string = "k".repeat(chunkSize - 1)

        val path = createTempFile().cleanup()

        for (charset in charsets) {
            path.testWriteText("$highSurrogate", charset)

            path.testWriteText("$lowSurrogate", charset)

            path.testWriteText("$smallString$highSurrogate", charset)

            path.testWriteText("$smallString$lowSurrogate", charset)

            path.testWriteText("$string$highSurrogate", charset)

            path.testWriteText("$string$lowSurrogate", charset)

            path.testWriteText("$string$highSurrogate$lowSurrogate$string", charset)

            path.testWriteText("$string$lowSurrogate$highSurrogate$string", charset)

            path.testWriteText(
                "$string$highSurrogate$lowSurrogate${string.substring(2)}$highSurrogate$lowSurrogate",
                charset
            )

            path.testWriteText("$string$lowSurrogate$highSurrogate$lowSurrogate$string", charset)
        }
    }

    @Test
    fun appendText() {
        val file = createTempFile().cleanup()
        file.writeText("Hello\n")
        file.appendText("World\n" as CharSequence)
        file.writeText(StringBuilder("Again"), Charsets.US_ASCII, StandardOpenOption.APPEND)

        assertEquals("Hello\nWorld\nAgain", file.readText())
        assertEquals(listOf("Hello", "World", "Again"), file.readLines(Charsets.UTF_8))
    }

    @Test
    fun file() {
        val file = createTempFile().cleanup()
        val writer = file.outputStream().writer().buffered()

        writer.write("Hello")
        writer.newLine()
        writer.write("World")
        writer.close()

        val list = ArrayList<String>()
        file.forEachLine(charset = Charsets.UTF_8) {
            list.add(it)
        }
        assertEquals(listOf("Hello", "World"), list)

        assertEquals(listOf("Hello", "World"), file.readLines())

        val lines: List<String>
        val linesResult = file.useLines {
            lines = it.toList()
            lines
        }
        assertEquals(listOf("Hello", "World"), lines)
        assertEquals(lines, linesResult)

        val text = file.inputStream().reader().readText()
        assertTrue("Hello" in text)
        assertTrue("World" in text)

        file.writeText("")
        var c = 0
        file.forEachLine { c++ }
        assertEquals(0, c)

        file.writeText(" ")
        file.forEachLine { c++ }
        assertEquals(1, c)

        file.writeText(" \n")
        c = 0
        file.forEachLine { c++ }
        assertEquals(1, c)

        file.writeText(" \n ")
        c = 0
        file.forEachLine { c++ }
        assertEquals(2, c)
    }

    @Test
    fun bufferedReader() {
        val file = createTempFile().cleanup()
        val lines = listOf("line1", "line2")
        file.writeLines(lines)

        assertEquals(lines, file.bufferedReader().use { it.readLines() })
        assertEquals(lines, file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() })
    }

    @Test
    fun bufferedWriter() {
        val file = createTempFile().cleanup()

        file.bufferedWriter().use { it.write("line1\n") }
        file.bufferedWriter(Charsets.UTF_8, 1024, StandardOpenOption.APPEND).use { it.write("line2\n") }

        assertEquals(listOf("line1", "line2"), file.readLines())
    }

    @Test
    fun writeBytes() {
        val file = createTempFile().cleanup()
        file.writeBytes("Hello".encodeToByteArray())
        file.appendBytes(" world!".encodeToByteArray())
        assertEquals(file.readText(), "Hello world!")

        val bytes = Random.nextBytes(100)
        file.writeBytes(bytes)
        file.appendBytes(bytes)
        assertTrue((bytes + bytes) contentEquals file.readBytes())
    }

    @Test
    fun writeLines() {
        val file = createTempFile().cleanup()
        val lines = listOf("first line", "second line")
        file.writeLines(lines)
        assertEquals(lines, file.readLines())

        file.writeLines(lines.asSequence())
        assertEquals(lines, file.readLines())

        val moreLines = listOf("third line", "the bottom line")
        file.appendLines(moreLines)
        assertEquals(lines + moreLines, file.readLines())

        file.appendLines(moreLines.asSequence())
        assertEquals(lines + moreLines + moreLines, file.readLines())
    }
}

