/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  Author: g0tsu
 *  Email:  g0tsu at dnmx.0rg
 */

#include <global.h>
#include <libcaptcha.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <errno.h>
#include <assert.h>

int lc_random_bytes(void *bytes, size_t size) {
  size_t read_bytes = 0;
  ssize_t n;

  /* Linux getrandom(2) syscall or FreeBSD getrandom(2) function*/
  /* Keep reading until we get enough entropy */
  while (read_bytes < size) {
  /* Below, (bytes + read_bytes)  is pointer arithmetic.

       bytes   read_bytes  size
         |      |           |
        [#######=============] (we're going to write over the = region)
                 \\\\\\\\\\\\\
                  amount_to_read
  */

  size_t amount_to_read = size - read_bytes;
  n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);

  if (n == -1) {
    if (errno == ENOSYS) {
      /* This can happen if PHP was compiled against a newer kernel where getrandom()
       * is available, but then runs on an older kernel without getrandom(). If this
       * happens we simply fall back to reading from /dev/urandom. */
      assert(read_bytes == 0);
      break;
    } else if (errno == EINTR || errno == EAGAIN) {
      /* Try again */
      continue;
    } else {
        /* If the syscall fails, fall back to reading from /dev/urandom */
      break;
    }
  }

    read_bytes += (size_t) n;
  }

  if (read_bytes < size) {
    int    fd;
    struct stat st;

  if (fd < 0) {

#if HAVE_DEV_URANDOM
  fd = open("/dev/urandom", O_RDONLY);
#else
  fd = open("/dev/random", O_RDONLY);
#endif
    if (fd < 0) {
      return 1;
    }
    /* Does the file exist and is it a character device? */
    if (fstat(fd, &st) != 0 ||
# ifdef S_ISNAM
        !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
# else
        !S_ISCHR(st.st_mode)
# endif
      ) {
        close(fd);
        return 1;
      }
    }

    for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) {
      n = read(fd, bytes + read_bytes, size - read_bytes);
      if (n <= 0) {
        break;
      }
    }

    if (read_bytes < size) {
      return 1;
    }
  }

  return 0;
}

const char lc_cval[16] = "0123456789abcdef";

static int lc_val_pos(const char c) {
  for (int i = 0; i < 16; i++) {
    if (lc_cval[i] == c)
      return i;
  }

  return 0;
}

lc_rgb *lc_rgb_from_text(const char *str) {
  char *cp = (char *)str;
  char c;
  lc_rgb *rgb;

  if (strlen(str) < 7)
    return NULL;

  cp++; // skip #
  rgb = (lc_rgb *)malloc(sizeof(*rgb));

  for (int i = 0; i < 6; i++, cp++) {
    c = tolower(*cp);
    switch (i) {
      case 0:
        rgb->r = lc_val_pos(c) * 16;
        break;
      case 1:
        rgb->r += lc_val_pos(c);
      case 2:
        rgb->g = lc_val_pos(c) * 16;
        break;
      case 3:
        rgb->g += lc_val_pos(c);
      case 4:
        rgb->b = lc_val_pos(c) * 16;
        break;
      case 5:
        rgb->b += lc_val_pos(c);
      default:
        break;
    }
  }

  return rgb;
}

