package app.fayaz.otgmaster.exfat

import app.fayaz.otgmaster.block.RawBlockDevice
import java.nio.ByteBuffer

object ExFatNative {

    init {
        System.loadLibrary("veracrypt-native")
    }

    /**
     * Mounts the exFAT filesystem.
     * @param blockDevice The Kotlin BlockDevice to read from.
     * @return A pointer to the native `struct exfat` or 0 if failed.
     */
    external fun mount(blockDevice: RawBlockDevice): Long

    /**
     * Unmounts the exFAT filesystem and frees memory.
     */
    external fun unmount(exfatPtr: Long)

    @JvmStatic
    external fun getFreeSpace(exfatPtr: Long): Long

    @JvmStatic
    external fun getRootNode(exfatPtr: Long): ExFatNode?

    @JvmStatic
    external fun readDir(exfatPtr: Long, nodePtr: Long): Array<ExFatNode>?

    @JvmStatic
    external fun readFile(exfatPtr: Long, nodePtr: Long, offset: Long, size: Int, buffer: ByteArray): Int

    @JvmStatic
    external fun writeFile(exfatPtr: Long, nodePtr: Long, offset: Long, size: Int, buffer: ByteArray): Int

    @JvmStatic
    external fun setLength(exfatPtr: Long, nodePtr: Long, length: Long): Int

    @JvmStatic
    external fun createFile(exfatPtr: Long, path: String): Int

    @JvmStatic
    external fun createDirectory(exfatPtr: Long, path: String): Int

    @JvmStatic
    external fun deleteNode(exfatPtr: Long, nodePtr: Long): Int

    @JvmStatic
    external fun rename(exfatPtr: Long, oldPath: String, newPath: String): Int

    @JvmStatic
    external fun flush(exfatPtr: Long): Int

    @JvmStatic
    external fun putNode(exfatPtr: Long, nodePtr: Long)

    // These functions will be called FROM C via JNI to read/write the block device.
    
    @JvmStatic
    fun pread(blockDevice: RawBlockDevice, offset: Long, size: Int, destBuffer: ByteArray): Int {
        return try {
            val blockSize = blockDevice.blockSize
            val startBlock = offset / blockSize
            val endBlock = (offset + size - 1) / blockSize
            val numBlocks = (endBlock - startBlock + 1).toInt()
            
            // Read the required blocks
            val blocksData = blockDevice.readBlocks(startBlock, numBlocks)
            
            // Copy the requested byte range into destBuffer
            val blockOffset = (offset % blockSize).toInt()
            System.arraycopy(blocksData, blockOffset, destBuffer, 0, size)
            size
        } catch (e: Exception) {
            e.printStackTrace()
            -1
        }
    }

    @JvmStatic
    fun pwrite(blockDevice: RawBlockDevice, offset: Long, size: Int, srcBuffer: ByteArray): Int {
        return try {
            val blockSize = blockDevice.blockSize
            val startBlock = offset / blockSize
            val endBlock = (offset + size - 1) / blockSize
            val numBlocks = (endBlock - startBlock + 1).toInt()
            
            val blockOffset = (offset % blockSize).toInt()
            
            if (blockOffset == 0 && size % blockSize == 0) {
                // Perfectly block aligned write
                blockDevice.writeBlocks(startBlock, srcBuffer)
            } else {
                // Read-modify-write for unaligned writes
                val blocksData = blockDevice.readBlocks(startBlock, numBlocks)
                System.arraycopy(srcBuffer, 0, blocksData, blockOffset, size)
                blockDevice.writeBlocks(startBlock, blocksData)
            }
            size
        } catch (e: Exception) {
            e.printStackTrace()
            -1
        }
    }
    
    @JvmStatic
    fun getDeviceSize(blockDevice: RawBlockDevice): Long {
        return blockDevice.blockCount * blockDevice.blockSize
    }
}
