#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include "libhash.h"

#define BLOCKSIZE 4096
#define XATTR_SHA512HASH "trusted.sha512.control.lth.se"


struct __attribute__((__packed__)) hash_attr {
  time_t hash_time;
  time_t mtime;
  uint32_t mtime_nsec;
  uint8_t hash[SHA512_DIGEST_LENGTH];
};

int hash_sha512_buf(char *buf,
                 size_t length,
                 unsigned char *hash)
{
  SHA512_CTX ctx;
  
  SHA512_Init(&ctx);
  SHA512_Update(&ctx, buf, length);
  SHA512_Final(hash, &ctx);

  return 0;
}

int hash_sha512_file(char *filename,
                  time_t mtime,
                  int64_t mtime_nsec,
                  int flags,
                  time_t maxage,
                  unsigned char *hash)
{
  int fd;
  int result = 0, recalc = 1;
  struct hash_attr hash_attr;

  if (flags & FLAGS_CLEAR_XATTR) {
    if (removexattr(filename, XATTR_SHA512HASH) != 0) {
      if (errno != ENODATA) {
        result = -1;
        fprintf(stderr, "Failed to remove xattr '%s'\n", filename);
        goto out;
      } else if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
        fprintf(stderr, "No xattr for '%s'\n", filename);
      }
    } else {
      if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
        fprintf(stderr, "Removed xattr for '%s'\n", filename);
      }
    }
  }
  if (flags & FLAGS_NO_CALC_HASH) {
    // We should not calculate hash
    goto out;
  }

  if (flags & FLAGS_READ_XATTR) {
    int err;

    err = getxattr(filename, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr));
    if (err < 0 && errno != ENODATA) {
      result = -1;
      goto out;
    }
    if (err != sizeof(hash_attr)) {
      recalc = 1;
    } else if (le64toh(hash_attr.mtime) != mtime) {
      recalc = 1;
    } else if (le32toh(hash_attr.mtime_nsec) != mtime_nsec) {
      recalc = 1;
    } else if (flags & FLAGS_MAX_AGE && le64toh(hash_attr.hash_time) < maxage) {
      recalc = 1;
    } else {
      recalc = 0;
      memcpy(hash, hash_attr.hash, SHA512_DIGEST_LENGTH);
    }
  }

  if (recalc == 0) {
    goto out;
  }
  
  fd = open (filename, O_RDONLY);
  if (fd < 0) {
    result = -1;
    goto out;
  } else {
    SHA512_CTX ctx;
    unsigned char buffer[BLOCKSIZE];
    int count;
    time_t now;
      
    time(&now);
    SHA512_Init(&ctx);
    while ((count = read(fd, buffer, BLOCKSIZE)) > 0) {
      SHA512_Update(&ctx, buffer, count);
    }
    SHA512_Final(hash, &ctx);

    if (count < 0) {
      result = -1;
      fremovexattr(fd, XATTR_SHA512HASH);
      goto out_close;
    }

    if (flags & FLAGS_WRITE_XATTR) {
      int err;

      hash_attr.hash_time = htole64(now);
      hash_attr.mtime = htole64(mtime);
      hash_attr.mtime_nsec = htole32(mtime_nsec);
      memcpy(hash_attr.hash, hash, SHA512_DIGEST_LENGTH);
      err = fsetxattr(fd, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr), 0);
      if (err < 0) {
        result = -1;
        fremovexattr(fd, XATTR_SHA512HASH);
        goto out_close;
      }
    } else if (flags & FLAGS_READ_XATTR) {
      fremovexattr(fd, XATTR_SHA512HASH);
    }
  }
out_close:
  close(fd);
out:
  return result;
}