#include "attr.hpp"
#include <fcntl.h>


bool operator<(const struct timespec& a, const struct timespec& b) {
    if (!a.tv_nsec || !b.tv_nsec) return a.tv_sec < b.tv_sec; // e.g. sshfs does not provide nanoseconds
    return a.tv_sec < b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_nsec < b.tv_nsec);
}
bool operator>(const struct timespec& a, const struct timespec& b) {
    if (!a.tv_nsec || !b.tv_nsec) return a.tv_sec > b.tv_sec;
    return a.tv_sec > b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_nsec > b.tv_nsec);
}
bool operator==(const struct timespec& a, const struct timespec& b) {
    if (!a.tv_nsec || !b.tv_nsec) return a.tv_sec == b.tv_sec;
    return a.tv_sec == b.tv_sec && a.tv_nsec == b.tv_nsec;
}
bool operator!=(const struct timespec& a, const struct timespec& b) {
    if (!a.tv_nsec || !b.tv_nsec) return a.tv_sec != b.tv_sec;
    return a.tv_sec != b.tv_sec || a.tv_nsec != b.tv_nsec;
}


Attr* Attr::getInst(Attr* inst) {
    if (!inst->is_reg() && !inst->is_dir()) {
        LOG("no dir/reg");
        delete inst;
        return NULL;
    }

    #if 0
        // for localizing on remote machine with getpwnam/getgrnam
        struct passwd* pw = getpwuid(inst->ss.st_uid);
        struct group* gr = getgrgid(inst->ss.st_gid);
        if (!pw || !gr) {
            LOG("getpwuid(%d)/getgrgid(%d)", inst->ss.st_uid, inst->ss.st_gid);
            delete inst;
            return NULL;
        }
        inst->uname = strdup(pw->pw_name);
        inst->gname = strdup(gr->gr_name);
    #endif

    return inst;
}


Attr* Attr::getInst(const struct stat& s) {
    Attr* inst = new Attr();
    inst->ss = s;
    return getInst(inst);
}


Attr* Attr::getInst(int fd) {
    Attr* inst = new Attr();
    if (fstat(fd, &inst->ss) != 0) {
        LOG_ERRNO("fstat(%d)", fd);
        delete inst;
        return NULL;
    }
    return getInst(inst);
}


Attr* Attr::getInst(int dfd, const char* fn) {
    Attr* inst = new Attr();
    if (fstatat(dfd, fn, &inst->ss, AT_SYMLINK_NOFOLLOW) != 0) {
        LOG_ERRNO("fstatat(%d,%s)", dfd, fn);
        delete inst;
        return NULL;
    }
    return getInst(inst);
}


bool Attr::apply_to(int fd) const {
    if (fchmod(fd, ss.st_mode) == -1) { // what first? chmod/chown?
        LOG_ERRNO("fchmod(%d)", fd);
        return false;
    }
    // TODO: translate string user/group to local ids if possible
    //if (fchown(fd, ss.st_uid, ss.st_gid) == -1) {
    //	LOG_ERRNO("fchown(%d)", fd);
    //	return false;
    //}
    if (futimens(fd, &ss.st_atim) == -1) { // actually timespec[2] for atime and mtime - cannot change ctime (will be now as we set the mtime)
        LOG_ERRNO("futimens(%d)", fd);
        return false;
    }
    return true;
}


Attr::merge_t Attr::cmp(const Attr* o) const {
    if (is_reg() != o->is_reg()) {
        return FAIL;
    }
    if (is_reg()) { // ignore mtime for dirs
        if (mtime() > o->mtime()) {
            if (size() != o->size()) {
                return FULL_THIS; // assume fully newer in any case
            } else {
                return FULL_OR_ATTR_THIS; // might be a copy that only requires updating attrs
            }
        } else if (mtime() < o->mtime()) {
            if (size() != o->size()) {
                return FULL_THAT; // assume fully older in any case
            } else {
                return FULL_OR_ATTR_THAT;
            }
        } else if (size() != o->size()) {
            return FULL_CONFLICT; // same name, type, mtime but different size?
        } else {
            // file has same mod time and size - can now only be different attrs
        }
    }
    if (mode() == o->mode() && uid() == o->uid() && gid() == o->gid()) {
        return SAME; // ignore ctime here? might verify this using hashes?
    } else {
        if (ctime() > o->ctime() && (mtime() == o->mtime() || mtime() > o->mtime())) { // ctime will be updated as when when mtime changes, but we check mtime here as well for safety
            return ATTR_THIS;
        } else if (ctime() < o->ctime() && (mtime() == o->mtime() || mtime() < o->mtime())) {
            return ATTR_THAT;
        } else {
            return ATTR_CONFLICT;
        }
    }
}