extern "C"
{
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
}
#include "common.h"

eCopyOverride gCopyOverride = kNone;

bool file_copy(char* spath, char* dpath, bool move)
{
M(("file_copy()"));
M(("spath %s dpath %s", spath, dpath));

    /// key input? ///
    fd_set mask;

    FD_ZERO(&mask);
    FD_SET(0, &mask);

    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    select(1, &mask, NULL, NULL, &tv);
    
    if(FD_ISSET(0, &mask)) {
        int meta;
        int key = mgetch(&meta);

        if(key == 3 || key == 7 || key == 27) {    // CTRL-G and CTRL-C
            gCopyOverride = kCancel;
        }
    }

    /// cancel ///
    if(gCopyOverride == kCancel) {
        return false;
    }
    
    /// new file name check ///
    char new_fname[PATH_MAX];
    strcpy(new_fname, "");
    
    struct stat dstat;
    const int dlen = strlen(dpath);

    if(stat(dpath, &dstat) < 0) {
        if(dpath[dlen -1] == '/') {
            err_msg("file_copy: destination_err(%s)", dpath);
            return false;
        }
        else {
            char* p = dpath + dlen;
            while(p != dpath && *p != '/') {
                p--;
            }
            strcpy(new_fname, p+1);

            dpath[p-dpath+1] = 0;
        }
    }
    else if(!S_ISDIR(dstat.st_mode)) {
        char* p = dpath + dlen;
        while(p != dpath && *p != '/') {
            p--;
        }
        strcpy(new_fname, p+1);

        dpath[p-dpath+1] = 0;
    }
    
    /// add / ///
    char new_dpath[PATH_MAX];
    if(dpath[strlen(dpath) -1] != '/') {
        strcpy(new_dpath, dpath);
        strcat(new_dpath, "/");

        dpath = new_dpath;
    }

    /// dpath check ///
    if(stat(dpath, &dstat) < 0) {
        err_msg("file_copy: destination_err(%s)", dpath);
        return false;
    }
    if(!S_ISDIR(dstat.st_mode)) {
        err_msg("file_copy: destination err(%s)", dpath);
        return false;
    }

    /// illegal argument check ///
    char spath2[PATH_MAX];

    strcpy(spath2, spath);
    strcat(spath2, "/");
        
    if(strstr(dpath, spath2) == dpath) {
        err_msg("file_copy: destination err(%s)", dpath);
        return false;
    }
    
    /// source stat ///
    struct stat stat_;

    if(lstat(spath, &stat_) < 0) {
        err_msg("file_copy: stat err1(%s)", spath);
        return false;
    }

    /// get destination file name ///
    char dfile[PATH_MAX];
    
    if(strcmp(new_fname, "") == 0) {
        char sfname[PATH_MAX];
        int i;
        const int len = strlen(spath);
        for(i = len-1;
            i > 0;
            i--)
        {
            if(spath[i] == '/') break;
        }
        memcpy(sfname, spath + i + 1, len - i);

        /// get destination file name ///
        strcpy(dfile, dpath);
        strcat(dfile, sfname);
    }
    else {
        strcpy(dfile, dpath);
        strcat(dfile, new_fname);
    }

    /// illegal argument check ///
    if(strcmp(spath, dfile) == 0) {
        return false;
    }
        
    /// dir ///
    if(S_ISDIR(stat_.st_mode)) {
        /// msg ///
        char buf[PATH_MAX+10];
        sprintf(buf, "entering %s", spath);
        msg(buf);

        /// override ///
        bool no_mkdir = false;
        bool select_newer = false;
        struct stat dfstat;
        if(access(dfile, F_OK) == 0) {
            if(lstat(dfile, &dfstat) < 0) {
                err_msg("file_copy: stat err2(%s)", dfile);
                return false;
            }
    
            /// ok ? ///
            if(S_ISDIR(dfstat.st_mode)) {
                no_mkdir = true;
            }
            else {
                if(gCopyOverride == kNone) {
                    const char* str[] = {
                        "No", "Yes", "SelectNewer", "No(all)", "Yes(all)", "SelectNewer(all)", "Cancel"
                    };
           
                    char buf[PATH_MAX+20];
                    sprintf(buf, "%s override?", dfile);
                    int ret = select_str(buf, (char**)str, 7, 0);
                    switch(ret) {
                    case 0:
                        return true;
                    case 1:
                        file_remove(dfile, true);
                        break;
                    case 2:
                        select_newer = true;
                        break;
                    case 3:
                        gCopyOverride = kNoAll;
                        return true;
                    case 4:
                        file_remove(dfile, true);
                        gCopyOverride = kYesAll;
                        break;
                    case 5:
                        gCopyOverride = kSelectNewer;
                        select_newer = true;
                        break;
                    case 6:
                        gCopyOverride = kCancel;
                        return false;
                    }
                }
                else if(gCopyOverride == kYesAll) {
                    file_remove(dfile, true);
                }
                else if(gCopyOverride == kNoAll) {
                    return true;
                }
                else if(gCopyOverride == kSelectNewer) {
                    select_newer = true;
                }
            }
        }

        if(select_newer) {
            if(stat_.st_mtime > dfstat.st_mtime) {
                file_remove(dfile, true);
            }
            else {
                return true;
            }
        }

        if(move) {
            if(rename(spath, dfile) < 0) {
/*                
                if(errno != EXDEV) {
                    err_msg("unable to rename %s to %s", spath, dfile);
                    return false;
                }
                else {
*/                
                    goto dir_copy;
/*                    
                }
*/                
            }
        }
        else {
dir_copy:            
            DIR* dir = opendir(spath);
            if(dir == NULL) {
                err_msg("file_copy: opendir err(%s)", spath);
                return false;
            }

            if(!no_mkdir) {
                if(mkdir(dfile, stat_.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR
                        |S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) <0)
                {
                    closedir(dir);
                    err_msg("file_copy: mkdir err(%s)", dfile);
                    return false;
                }
            }

            struct dirent* entry;
            while(entry = readdir(dir)) {
                if(strcmp(entry->d_name, ".") != 0
                    && strcmp(entry->d_name, "..") != 0)
                {
                    char spath2[PATH_MAX];

                    strcpy(spath2, spath);
                    strcat(spath2, "/");
                    strcat(spath2, entry->d_name);

                    char dfile2[PATH_MAX];
    
                    strcpy(dfile2, dfile);
                    strcat(dfile2, "/");
    
                    if(!file_copy(spath2, dfile2, move)) {
                        gCopyOverride = kCancel;
                        closedir(dir);
                        return false;
                    }
                }
            }
    
            closedir(dir);
        
            if(move) rmdir(spath);
        }
    }
    /// file ///
    else {
        /// override ? ///
        if(access(dfile, F_OK) == 0) {
            bool select_newer = false;
            if(gCopyOverride == kNone) {
                const char* str[] = {
                    "No", "Yes", "SelectNewer", "No(all)", "Yes(all)", "SelectNewer(all)", "Cancel"
                };
       
                char buf[PATH_MAX+20];
                sprintf(buf, "%s override?", dfile);
                int ret = select_str(buf, (char**)str, 7, 0);
                switch(ret) {
                case 0:
                    return true;
                case 1:
                    file_remove(dfile, true);
                    break;
                case 2:
                    select_newer = true;
                    break;
                case 3:
                    gCopyOverride = kNoAll;
                    return true;
                case 4:
                    file_remove(dfile, true);
                    gCopyOverride = kYesAll;
                    break;
                case 5:
                    gCopyOverride = kSelectNewer;
                    select_newer = true;
                    break;
                case 6:
                    gCopyOverride = kCancel;
                    return false;
                }
            }
            else if(gCopyOverride == kYesAll) {
                file_remove(dfile, true);
            }
            else if(gCopyOverride == kNoAll) {
                return true;
            }
            else if(gCopyOverride == kSelectNewer) {
                select_newer = true;
            }

            if(select_newer) {
                struct stat dfstat;

                if(lstat(dfile, &dfstat) < 0) {
                    err_msg("file_copy: stat err3(%s)", dfile);
                    return false;
                }

                if(stat_.st_mtime > dfstat.st_mtime) {
                    file_remove(dfile, true);
                }
                else {
                    return true;
                }
            }
        }

        /// msg ///
        char buf[PATH_MAX+10];
        sprintf(buf, "copying %s", spath);
        msg(buf);

        if(move) {
            if(rename(spath, dfile) < 0) {
/*                
                if(errno != EXDEV) {
                    err_msg("unable to rename %s to %s", spath, dfile);
                    return false;
                }
                else {
*/                
                    goto file_copy;
/*                    
                }
*/                
            }
        }
        else {
file_copy:            
            /// sym link ///
            if(S_ISLNK(stat_.st_mode)) {
                char link[PATH_MAX];
                int n = readlink(spath, link, PATH_MAX);
                if(n < 0) {
                    err_msg("file_copy: readlink err(%s)", spath);
                    return false;
                }
                link[n] = 0;
    
                if(symlink(link, dfile) < 0) {
                    err_msg("file_copy: symlink err(%s)", dfile);
                    return false;
                }
            }
            /// file ///
            else if(S_ISREG(stat_.st_mode)) {
M(("file copying"));
                int fd = open(spath, O_RDONLY);
                if(fd < 0) {
                    err_msg("file_copy: open(read) err(%s)", spath);
                    return false;
                }
                int fd2 = open(dfile, O_WRONLY|O_TRUNC|O_CREAT
                      , stat_.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP
                             |S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)
                    );
                if(fd2 < 0) {
                    err_msg("file_copy: open(write) err(%s)", dfile);
                    close(fd);
                    return false;
                }
    
                char buf[BUFSIZ];
                while(1) {
                    int n = read(fd, buf, BUFSIZ);
                    if(n < 0) {
                        err_msg("file_copy: read err(%s)", spath);
                        close(fd);
                        close(fd2);
                        return false;
                    }
                    
                    if(n == 0) {
                        break;
                    }
    
                    if(write(fd2, buf, n) < 0) {
                        err_msg("file_copy: write err(%s)", dfile);
                        close(fd);
                        close(fd2);
                        return false;
                    }
                }
            
                if(close(fd) < 0) {
                    err_msg("file_copy: close err(%s)", spath);
                    return false;
                }
                if(close(fd2) < 0) {
                    err_msg("file_copy: close err fd2(%s)", dfile);
                    return false;
                }
            }
            /// non suport file ///
            else {
                err_msg("file_copy: sorry, non support file type(%s)", spath);
                return false;
            }

            if(move) file_remove(spath, true);
        }
    }

    return true;
}

/// don't put '/' at last
bool file_remove(char* path, bool no_ctrl_c)
{
M(("file_remove"));
M(("path %s", path));

    /// key input ? ///
    if(!no_ctrl_c) {
        fd_set mask;

        FD_ZERO(&mask);
        FD_SET(0, &mask);

        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 0;

        select(1, &mask, NULL, NULL, &tv);
    
        if(FD_ISSET(0, &mask)) {
            int meta;
            int key = mgetch(&meta);

            if(key == 3 || key == 7 || key == 27) {    // CTRL-C and CTRL-G
                gCopyOverride = kCancel;
            }
        }
    }

    /// cancel ///
    if(gCopyOverride == kCancel) {
        return false;
    }
    
    struct stat stat_;

    if(lstat(path, &stat_) < 0) {
        err_msg("file_remove: stat err(%s)", path);
        return false;
    }

    /// file ///
    if(!S_ISDIR(stat_.st_mode)) {
        /// msg ///
        char buf[PATH_MAX+20];
        sprintf(buf, "deleting %s", path);
        msg(buf);

        /// go ///
        if(unlink(path) < 0) {
            err_msg("file_remove: unlink err(%s)", path);
            return false;
        }
    }
    /// directory ///
    else {
        char buf[PATH_MAX+20];
        sprintf(buf, "entering %s", path);
        msg(buf);
        
        DIR* dir = opendir(path);
        if(dir == NULL) {
            err_msg("file_remove: opendir err(%s)", path);
            return false;
        }
        struct dirent* entry;
        while(entry = readdir(dir)) {
            if(strcmp(entry->d_name, ".") != 0
                && strcmp(entry->d_name, "..") != 0)
            {
                char path2[PATH_MAX];
                strcpy(path2, path);
                strcat(path2, "/");
                strcat(path2, entry->d_name);

                if(!file_remove(path2, no_ctrl_c)) {
                    gCopyOverride = kCancel;
                    closedir(dir);
                    return false;
                }
            }
        }
        closedir(dir);

        if(rmdir(path) < 0) {
            err_msg("file_remove: rmdir err(%s)", path);
            return false;
        }
    }

TEND();    
    return true;
}
