#include <luna/PathParser.h>
#include <luna/String.h>
#include <os/ArgumentParser.h>
#include <os/Directory.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/Prompt.h>

using os::File;

Result<void> copy_file(StringView source, StringView destination, bool verbose, bool interactive)
{
    SharedPtr<File> source_file = TRY(os::File::open(source, File::ReadOnly));
    SharedPtr<File> destination_file;

    struct stat st;
    TRY(os::FileSystem::stat(source_file->fd(), st, true));
    umask(0);

    if (st.st_mode & S_IFDIR)
    {
        os::eprintln("cp: warning: -R not specified, skipping directory %s", source.chars());
        return {};
    }

    String path;

    if (os::FileSystem::is_directory(destination, true))
    {
        auto basename = TRY(PathParser::basename(source));
        path = TRY(PathParser::join_paths(destination, basename.view()));

        if (interactive && os::FileSystem::exists(path.view(), false))
        {
            auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, path.chars(), source.chars()));
            if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {};
        }

        destination_file = TRY(File::open_or_create(path.view(), File::ReadWrite, st.st_mode & ~S_IFMT));
    }
    else
    {
        path = TRY(String::from_string_view(destination));

        if (interactive && os::FileSystem::exists(path.view(), false))
        {
            auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, path.chars(), source.chars()));
            if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {};
        }

        destination_file = TRY(File::open_or_create(path.view(), File::ReadWrite, st.st_mode & ~S_IFMT));
    }

    if (verbose) os::eprintln("copying %s to %s", source.chars(), path.chars());

    auto buf = TRY(Buffer::create_sized(4096));
    while (1)
    {
        TRY(source_file->read(buf, 4096));
        if (buf.is_empty()) break;
        destination_file->write(buf);
    }

    return {};
}

Result<void> copy_tree(StringView source, StringView destination, bool verbose, bool interactive)
{
    String path;

    if (!os::FileSystem::is_directory(source, true)) return copy_file(source, destination, verbose, interactive);

    if (os::FileSystem::is_directory(destination, true))
    {
        auto basename = TRY(PathParser::basename(source));
        path = TRY(PathParser::join_paths(destination, basename.view()));

        if (!os::FileSystem::exists(path.view(), false)) TRY(os::FileSystem::create_directory(path.view(), 0755));
    }
    else
    {
        path = TRY(String::from_string_view(destination));
        if (!os::FileSystem::exists(path.view(), false)) TRY(os::FileSystem::create_directory(path.view(), 0755));
    }

    auto dir = TRY(os::Directory::open(source));

    Vector<String> entries = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));

    for (const auto& entry : entries)
    {
        auto subpath = TRY(PathParser::join_paths(source, entry.view()));
        TRY(copy_tree(subpath.view(), path.view(), verbose, interactive));
    }

    return {};
}

Result<int> luna_main(int argc, char** argv)
{
    Vector<StringView> files;
    StringView destination;
    bool verbose { false };
    bool interactive { false };
    bool recursive { false };

    os::ArgumentParser parser;
    parser.add_description("Copy files or directories."_sv);
    parser.add_system_program_info("cp"_sv);
    parser.set_vector_argument(files, "files"_sv, true);
    parser.add_positional_argument(destination, "destination"_sv, true);
    parser.add_switch_argument(verbose, 'v', "verbose"_sv, "show more information"_sv);
    parser.add_switch_argument(interactive, 'i', "interactive"_sv, "prompt before overwriting existing files"_sv);
    parser.add_switch_argument(recursive, 'R', "recursive"_sv, "copy directories recursively"_sv);
    TRY(parser.parse(argc, argv));

    for (const auto& file : files)
    {
        if (recursive) TRY(copy_tree(file, destination, verbose, interactive));
        else
            TRY(copy_file(file, destination, verbose, interactive));
    }

    return 0;
}