/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

/**
 * @file The file declares several util functions.
 */
package std.fs

public func copy(sourcePath: Path, to!: Path, overwrite!: Bool = false): Unit {
    PathValidator.throwIfEmptyOrContainsNullByte("sourcePath", sourcePath, true)
    PathValidator.throwIfEmptyOrContainsNullByte("to", to, true)
    if (sourcePath == to) {
        throw FSException("The input path 'sourcePath' `${sourcePath}` and 'to' `${to}` are the same path.")
    }

    let fileInfo = FileInfo(sourcePath)
    let destInfo = if (exists(to)) {
        Some(FileInfo(to))
    } else {
        Option<FileInfo>.None
    }

    if (fileInfo.isRegular()) {
        if (let Some(info) <- destInfo) {
            if (!overwrite) {
                throw FSException("Destination path `${to}` is already exists.")
            } else if (!info.isRegular()) {
                throw FSException("Source path `${sourcePath}` is file but destination path `${to}` is already exists and is not file.")
            }
        }
        return File.copy(sourcePath._rawPath, to._rawPath, overwrite)
    } else if (fileInfo.isDirectory()) {
        if (let Some(info) <- destInfo) {
            if (!overwrite) {
                throw FSException("Destination path `${to}` is already exists.")
            } else if (!info.isDirectory()) {
                throw FSException("Source path `${sourcePath}` is directory but destination path `${to}` is already exists and is not directory.")
            }
        }
        return Directory.copy(sourcePath._rawPath, to._rawPath, overwrite)
    } else if (fileInfo.isSymbolicLink()) {
        if (let Some(info) <- destInfo) {
            if (!overwrite) {
                throw FSException("Destination path `${to}` is already exists.")
            } else if (!info.isSymbolicLink()) {
                throw FSException("Source path `${sourcePath}` is symbolic link but destination path `${to}` is already exists and is not symbolic link.")
            } else {
                remove(to)
            }
        }
        let target = SymbolicLink.readFrom(fileInfo._path)
        SymbolicLink.create(to, to: target)
        return
    }
    throw FSException("File type of `${sourcePath}` is not supported.")
}

public func copy(sourcePath: String, to!: String, overwrite!: Bool = false): Unit {
    copy(Path(sourcePath), to: Path(to), overwrite: overwrite)
}

public func remove(path: Path, recursive!: Bool = false): Unit {
    remove(path._rawPath, recursive: recursive)
}

public func remove(path: String, recursive!: Bool = false): Unit {
    PathValidator.throwIfEmptyOrContainsNullByte("path", path, true)

    unsafe {
        try (cpath = LibC.mallocCString(path).asResource()) {
            let pret = CJ_FS_Remove(cpath.value, recursive)
            if (pret.isNull()) {
                throw FSException("Failed malloc in C code when remove `${path}`.")
            }
            let ret = pret.read()
            LibC.free(pret)
            if (ret.rtnCode == 0) {
                return
            }
            try {
                let errMessage = ret.msg.toString()
                throw FSException("Failed to remove `${path}` return ${ret.rtnCode}: \"${errMessage.trimAscii()}\".")
            } finally {
                LibC.free(ret.msg)
            }
        }
    }
}

public func exists(path: Path): Bool {
    exists(path._rawPath)
}

public func exists(path: String): Bool {
    PathValidator.throwIfEmptyOrContainsNullByte("path", path, true)

    unsafe {
        let cPath = LibC.mallocCString(path)
        var ret: Int8 = CJ_FS_Exists(cPath)
        LibC.free(cPath)
        return ret == 0
    }
}

public func rename(sourcePath: String, to!: String, overwrite!: Bool = false): Unit {
    PathValidator.throwIfEmptyOrContainsNullByte("sourcePath", sourcePath, true)
    PathValidator.throwIfEmptyOrContainsNullByte("to", to, true)
    if (Path(sourcePath) == Path(to)) {
        throw FSException("The input path 'sourcePath' `${sourcePath}` and 'to' `${to}` are the same path.")
    }
    if (exists(to) && !overwrite) {
        throw FSException("Destination path 'to' `${to}` is already exists and overwrite is false.")
    }

    try (sourceCPath = unsafe { LibC.mallocCString(sourcePath).asResource() },
        destinationCPath = unsafe { LibC.mallocCString(to).asResource() }) {
        let resultptr = unsafe { CJ_FS_Rename(sourceCPath.value, destinationCPath.value) }
        if (resultptr.isNull()) {
            throw FSException("Failed malloc in C code when rename `${sourcePath}` to `${to}`.")
        }
        let result = unsafe { resultptr.read() }
        unsafe { LibC.free(resultptr) }
        if (result.rtnCode == 0) {
            return
        }
        try {
            let errMessage = result.msg.toString()
            throw FSException("Failed to rename `${sourcePath}` to `${to}` return ${result.rtnCode}:\"${errMessage.trimAscii()}\".")
        } finally {
            unsafe { LibC.free(result.msg) }
        }
    }
}

public func rename(sourcePath: Path, to!: Path, overwrite!: Bool = false): Unit {
    rename(sourcePath._rawPath, to: to._rawPath, overwrite: overwrite)
}

public func removeIfExists(path: Path, recursive!: Bool = false): Bool {
    if (exists(path)) {
        remove(path, recursive: recursive)
        return true
    }
    return false
}

public func removeIfExists(path: String, recursive!: Bool = false): Bool {
    removeIfExists(Path(path), recursive: recursive)
}