#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if defined(_MSC_VER) || defined(__MINGW32__)
  #include <io.h>
  #include <sys/utime.h>
#else
  #include <unistd.h>
  #include <utime.h>
#endif

#include "utils.h"

// global verbosity setting
int g_verbosity = 0;

int read_s16_be(unsigned char *buf)
{
   unsigned tmp = read_u16_be(buf);
   int ret;
   if (tmp > 0x7FFF) {
      ret = -((int)0x10000 - (int)tmp);
   } else {
      ret = (int)tmp;
   }
   return ret;
}

float read_f32_be(unsigned char *buf)
{
   union {uint32_t i; float f;} ret;
   ret.i = read_u32_be(buf);
   return ret.f;
}

int is_power2(unsigned int val)
{
   while (((val & 1) == 0) && (val > 1)) {
      val >>= 1;
   }
   return (val == 1);
}

int fprint_write_output(FILE *fp, write_encoding encoding, const uint8_t *raw, int length)
{
   typedef struct {
      int bytes_per_val;
      const char *suffix;
   } encoding_format;
   const encoding_format enc_fmt[] = {
      [ENCODING_RAW] = {0,                ""},
      [ENCODING_U8]  = {sizeof(uint8_t),  ""},
      [ENCODING_U16] = {sizeof(uint16_t), ""},
      [ENCODING_U32] = {sizeof(uint32_t), ""},
      [ENCODING_U64] = {sizeof(uint64_t), "ULL"},
   };
   int flength = 0;
   const encoding_format *fmt = &enc_fmt[encoding];
   switch (encoding) {
      case ENCODING_RAW:
         flength = fwrite(raw, 1, length, fp);
         break;
      case ENCODING_U8:
      case ENCODING_U16:
      case ENCODING_U32:
      case ENCODING_U64:
         for (int w = 0; w < length; w += fmt->bytes_per_val) {
            flength += fprintf(fp, "0x");
            for (int b = 0; b < fmt->bytes_per_val; b++) {
               int off = w + b;
               flength += fprintf(fp, "%02x", off < length ? raw[off] : 0x00);
            }
            flength += fprintf(fp, "%s%c", fmt->suffix, (w < length - fmt->bytes_per_val) ? ',' : '\n');
         }
         break;
   }
   return flength;
}

void swap_bytes(unsigned char *data, long length)
{
   long i;
   unsigned char tmp;
   for (i = 0; i < length; i += 2) {
      tmp = data[i];
      data[i] = data[i+1];
      data[i+1] = tmp;
   }
}

void reverse_endian(unsigned char *data, long length)
{
   long i;
   unsigned char tmp;
   for (i = 0; i < length; i += 4) {
      tmp = data[i];
      data[i] = data[i+3];
      data[i+3] = tmp;
      tmp = data[i+1];
      data[i+1] = data[i+2];
      data[i+2] = tmp;
   }
}

long filesize(const char *filename)
{
   struct stat st;

   if (stat(filename, &st) == 0) {
      return st.st_size;
   }

   return -1;
}

void touch_file(const char *filename)
{
   int fd;
   //fd = open(filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666);
   fd = open(filename, O_WRONLY|O_CREAT, 0666);
   if (fd >= 0) {
      utime(filename, NULL);
      close(fd);
   }
}

long read_file(const char *file_name, unsigned char **data)
{
   FILE *in;
   unsigned char *in_buf = NULL;
   long file_size;
   long bytes_read;
   in = fopen(file_name, "rb");
   if (in == NULL) {
      return -1;
   }

   // allocate buffer to read from offset to end of file
   fseek(in, 0, SEEK_END);
   file_size = ftell(in);

   // sanity check
   if (file_size > 256*MB) {
      return -2;
   }

   in_buf = malloc(file_size);
   fseek(in, 0, SEEK_SET);

   // read bytes
   bytes_read = fread(in_buf, 1, file_size, in);
   if (bytes_read != file_size) {
      return -3;
   }

   fclose(in);
   *data = in_buf;
   return bytes_read;
}

long write_file(const char *file_name, unsigned char *data, long length)
{
   FILE *out;
   long bytes_written;
   // open output file
   out = fopen(file_name, "wb");
   if (out == NULL) {
      perror(file_name);
      return -1;
   }
   bytes_written = fwrite(data, 1, length, out);
   fclose(out);
   return bytes_written;
}

void generate_filename(const char *in_name, char *out_name, char *extension)
{
   char tmp_name[FILENAME_MAX];
   int len;
   int i;
   strcpy(tmp_name, in_name);
   len = strlen(tmp_name);
   for (i = len - 1; i > 0; i--) {
      if (tmp_name[i] == '.') {
         break;
      }
   }
   if (i <= 0) {
      i = len;
   }
   tmp_name[i] = '\0';
   sprintf(out_name, "%s.%s", tmp_name, extension);
}

char *basename(const char *name)
{
   const char *base = name;
   while (*name) {
      if (*name++ == '/') {
         base = name;
      }
   }
   return (char *)base;
}

void make_dir(const char *dir_name)
{
   struct stat st = {0};
   if (stat(dir_name, &st) == -1) {
      mkdir(dir_name, 0755);
   }
}

long copy_file(const char *src_name, const char *dst_name)
{
   unsigned char *buf;
   long bytes_written;
   long bytes_read;

   bytes_read = read_file(src_name, &buf);

   if (bytes_read > 0) {
      bytes_written = write_file(dst_name, buf, bytes_read);
      if (bytes_written != bytes_read) {
         bytes_read = -1;
      }
      free(buf);
   }

   return bytes_read;
}

void dir_list_ext(const char *dir, const char *extension, dir_list *list)
{
   char *pool;
   char *pool_ptr;
   struct dirent *entry;
   DIR *dfd;
   int idx;

   dfd = opendir(dir);
   if (dfd == NULL) {
      ERROR("Can't open '%s'\n", dir);
      exit(1);
   }

   pool = malloc(FILENAME_MAX * MAX_DIR_FILES);
   pool_ptr = pool;

   idx = 0;
   while ((entry = readdir(dfd)) != NULL && idx < MAX_DIR_FILES) {
      if (!extension || str_ends_with(entry->d_name, extension)) {
         sprintf(pool_ptr, "%s/%s", dir, entry->d_name);
         list->files[idx] = pool_ptr;
         pool_ptr += strlen(pool_ptr) + 1;
         idx++;
      }
   }
   list->count = idx;

   closedir(dfd);
}

void dir_list_free(dir_list *list)
{
   // assume first entry in array is allocated
   if (list->files[0]) {
      free(list->files[0]);
      list->files[0] = NULL;
   }
}

int str_ends_with(const char *str, const char *suffix)
{
   if (!str || !suffix) {
      return 0;
   }
   size_t len_str = strlen(str);
   size_t len_suffix = strlen(suffix);
   if (len_suffix > len_str) {
      return 0;
   }
   return (0 == strncmp(str + len_str - len_suffix, suffix, len_suffix));
}
