/**
 * Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl
 *
 */
#include "malloc.h"
#include "putils.h"
#include "version.h"
#include "sched.h"
#include "vector-of-strings.h"
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static const char *cmd;
enum { default_n_threads = 32 };
static size_t n_threads = default_n_threads;

void help(int exitcode) {
    FILE *out = exitcode ? stderr : stdout;
    fprintf(out, "Usage: %s [OPTION]... [DIR]...\n", cmd);
    fprintf(out, "Recursive remove of a directory hierarchy, done in parallel for performance.\n");
    fprintf(out, " OPTION can be\n");
    fprintf(out, "  -h, --help  print help.\n");
    fprintf(out, "  -P P        (capital P) use P-fold parallelism (default %d threads).\n", default_n_threads);
    fprintf(out, "  --          remaining arguments are files even if they start with '-'.\n");
    fprintf(out, "  --version   print program version\n");
    fprintf(out, "  --history   print a brief history of the versions\n");
    fprintf(out, "This is version %s gitrev %s  compiled %s %s.\n",
            VERSION, gitrev, __DATE__, __TIME__);
    fprintf(out, "Report bugs to parallel-tools-support_ww_grp@oracle.com\n");
    exit(exitcode);
}

static magic_t rm_magic = "rm magic";

struct rmframe {
    magic_t *magic;
    char  *filename;
    struct vector_of_strings *files_in_dir;
    size_t inlet_sum;
    size_t sum;
};

static struct rmframe *
mk_rmframe(char *filename) {
    struct rmframe *MALLOC(rf);
    *rf = (struct rmframe){.magic     = &rm_magic,
                           .filename  = filename,
                           .files_in_dir = NULL,
                           .inlet_sum = 0,
                           .sum       = 0};
    return rf;
}

static struct rmframe *rmframe_free(struct rmframe *rf) {
    FREE(rf->filename);
    if (rf->files_in_dir) {
        rf->files_in_dir = vector_of_strings_destroy(rf->files_in_dir);
    }
    FREE(rf);
    return NULL;
}

static struct rmframe *rmframe_cast(void *app_frame) {
    struct rmframe *rf = app_frame;
    assert(rf->magic == &rm_magic);
    return rf;
}

static void rmfun_return_inlet(void *parent_app_frame, size_t result) {
    struct rmframe *rf = rmframe_cast(parent_app_frame);
    rf->inlet_sum += result;
}


static struct frame *rmfun_have_dirlist(struct frame *frame, void *app_frame);
static struct frame *rmfun_sync(struct frame *frame, void *app_frame);
static struct frame *rmfun(struct frame *frame, void *app_frame) {
    struct rmframe *rf = rmframe_cast(app_frame);
    struct stat statbuf;
    {
        int r = lstat(rf->filename, &statbuf);
        if (r != 0) {
            fprintf(stderr, "%s: cannot stat '%s': %s\n", cmd, rf->filename, strerror(errno));
            rf->sum++;
            return sched_sync(frame, rmfun_sync);
        }
    }
    if (S_ISDIR(statbuf.st_mode)) {
        assert(rf->files_in_dir == NULL);
        rf->files_in_dir = mk_vector_of_strings();
        DIR *dir = opendir(rf->filename);
        if (dir == NULL) {
            fprintf(stderr, "%s: A cannot remove '%s': %s\n", cmd, rf->filename, strerror(errno));
            rf->sum++;
            return sched_sync(frame, rmfun_sync);
        } else {
            struct dirent *entry;
            while ((entry = readdir(dir))) {
                if (strcmp(entry->d_name, ".") == 0) continue;
                if (strcmp(entry->d_name, "..") == 0) continue;
                char *path = pathcat(rf->filename, entry->d_name);
                vector_of_strings_push(rf->files_in_dir, path);
                FREE(path);
            }
            closedir(dir);
            return rmfun_have_dirlist(frame, app_frame);
        }
    } else {
        if (0) {
        printf("sleep start\n");
        sleep(2);
        printf("sleep end\n");
        }
        int r = unlink(rf->filename);
        if (r != 0) {
            fprintf(stderr, "%s: B cannot remove '%s': %s\n", cmd, rf->filename, strerror(errno));
            rf->sum++;
        }
        return sched_sync(frame, rmfun_sync);
    }
}
static struct frame *rmfun_have_dirlist(struct frame *frame, void *app_frame) {
    struct rmframe *rf = rmframe_cast(app_frame);
    assert(rf->files_in_dir != NULL);
    char *fname;
    while ((fname = vector_of_strings_pop(rf->files_in_dir))) {
        struct rmframe *subframe = mk_rmframe(fname);
        return rmfun(sched_spawn(frame, rmfun_have_dirlist, subframe, rmfun_return_inlet),
                     subframe);
    }
    return sched_sync(frame, rmfun_sync);
}

static struct frame *rmfun_sync(struct frame *frame, void *app_frame) {
    struct rmframe *rf = rmframe_cast(app_frame);
    size_t sum = rf->inlet_sum + rf->sum;
    if (rf->filename && rf->files_in_dir && sum == 0) {
        // If NULL then there is no dir.  We don't delete the parents
        // of the files mentioned on the command line argument.
        int r = rmdir(rf->filename);
        if (r != 0) {
            fprintf(stderr, "%s: C cannot remove '%s': %s\n", cmd, rf->filename, strerror(errno));
            sum++;
        }
    }
    rf = rmframe_free(rf);
    return sched_return(frame, sum);
}

static int do_rm(struct vector_of_strings *fargs) {
    struct rmframe *rf = mk_rmframe(NULL);
    assert(rf->files_in_dir == NULL);
    rf->files_in_dir = fargs;
    size_t n = prun(n_threads, rmfun_have_dirlist, rf);
    if (0) fprintf(stderr, "Couldn't delete %zu objects\n", n);
    return n==0;
}


static void
parse_args(int argc, const char *argv[], struct vector_of_strings *fargs) {
    assert(argc > 0);
    cmd = argv[0];
    argc--; argv++;
    while (argc) {
        if (strcmp(argv[0], "-P") == 0) {
            argc--; argv++;
            if (argc == 0) help(1);
            n_threads = parse_number_or_help(argv[0]);
        } else if (strcmp(argv[0], "-h") == 0 ||
                   strcmp(argv[0], "--help") == 0) {
            help(0);
        } else if (strcmp(argv[0], "--version") == 0) {
            printf("%s %s\n"
                    "Copyright (C) 2019 Oracle.\n"
                    "Written by Bradley C. Kuszmaul\n",
                   cmd, VERSION);
            exit(0);
        } else if (strcmp(argv[0], "--history") == 0) {
            fprintf(stderr, "%s", VERSION_HISTORY);
            exit(0);
        } else if (strcmp(argv[0], "--") == 0) {
            argc--; argv++;
            while (argc) {
                vector_of_strings_push(fargs, argv[0]);
                argc--; argv++;
            }
        } else {
            vector_of_strings_push(fargs, argv[0]);
        }
        argc--; argv++;
    }
}

int main (int argc, const char *argv[]) {
    struct vector_of_strings *fargs = mk_vector_of_strings();
    parse_args(argc, argv, fargs);
    if (!do_rm(fargs)) {
        exit(1);
    }
    return 0;
}
