/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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.
 *
 *  lebiniou 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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include "webcam.h"
#include "pthread_utils.h"


extern uint8_t hflip, vflip, override_webcam;


static void
process_image(webcam_t *webcam, void *p)
{
  int i, j;
  uint8_t *q = p;
  Pixel_t *d;
  Pixel_t temp[CAP_WIDTH*CAP_HEIGHT];

  p++;
#if 1
  d = temp;

  /* TODO: use sws_scale/colorspace */
  /* XXX lame YUYV -> Y conversion */
  for (j = 0; j < CAP_HEIGHT; j++)
    for (i = 0; i < CAP_WIDTH; i++) {
      *d++ = *q;
      q += 2;
    }
#endif

  if (!xpthread_mutex_lock(&webcam->ctx->cam_mtx[webcam->cam_no])) {
    Buffer8_t *dst = webcam->ctx->cam_save[webcam->cam_no][0];

    if ((WIDTH == CAP_WIDTH) && (HEIGHT == CAP_HEIGHT)) {
      memcpy(dst->buffer, temp, BUFFSIZE*sizeof(Pixel_t));
    } else {
      gray_scale(dst->buffer, CAP_WIDTH, CAP_HEIGHT, temp);
      Buffer8_YUV_to_full_gray(dst);
    }

    if (hflip) {
      Buffer8_flip_h(dst);
    }

    if (!vflip) {
      Buffer8_flip_v(dst);
    }

    Buffer8_t *save = Buffer8_clone(dst); /* TODO s/save/new_picture */
    Context_push_webcam(webcam->ctx, save, webcam->cam_no);
    xpthread_mutex_unlock(&webcam->ctx->cam_mtx[webcam->cam_no]);
  }
}


static int
read_frame(webcam_t *cam)
{
  struct v4l2_buffer buf;

  switch (cam->io) {
    case IO_METHOD_READ:
      if (-1 == read(cam->fd,
                     cam->buffers[0].start,
                     cam->buffers[0].length)) {
        switch (errno) {
          case EAGAIN:
            return 0;

          case EIO:
          /* Could ignore EIO, see spec. */

          /* fall through */

          default:
            xperror("read");
        }
      }
      if (!override_webcam) {
        process_image(cam, cam->buffers[0].start);
      }

      break;

    case IO_METHOD_MMAP:
      CLEAR(buf);

      buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
      buf.memory = V4L2_MEMORY_MMAP;

      if (-1 == xioctl(cam->fd, VIDIOC_DQBUF, &buf)) {
        switch (errno) {
          case EAGAIN:
            return 0;

          case EIO:
          /* Could ignore EIO, see spec. */

          /* fall through */

          default:
            perror("VIDIOC_DQBUF");
	    return -2;
        }
      }

      assert((int)buf.index < cam->n_buffers);

      // printf("index %d, process_image(%d) bytes\n", buf.index, (int)cam->buffers[buf.index].length);
      if (!override_webcam) {
        process_image(cam, cam->buffers[buf.index].start);
      }
      // printf("process_image done cam #%d\n", cam->cam_no);
      if (-1 == xioctl(cam->fd, VIDIOC_QBUF, &buf)) {
        perror("VIDIOC_QBUF");
	return -2;
      }

      break;
  }

  return 1;
}


static int
capture_frame(webcam_t *webcam)
{
  fd_set fds;
  struct timeval tv;
  int r;

  FD_ZERO(&fds);
  FD_SET(webcam->fd, &fds);

  /* Timeout. */
  tv.tv_sec = 30;
  tv.tv_usec = 0;

  r = select(webcam->fd + 1, &fds, NULL, NULL, &tv);
  if (-1 == r) {
    if (EINTR == errno) {
      return 1;
    }
    xperror("select");
  }
  if (0 == r) {
    // select timeout
    return -1;
  }

  return read_frame(webcam);
}


void *
loop(void *args)
{
  webcam_t *webcam = (webcam_t *)args;

  capture_frame(webcam);
  Buffer8_copy(webcam->ctx->cam_save[webcam->cam_no][0], webcam->ctx->cam_ref0[webcam->cam_no]);

  while (webcam->ctx->running) {
    if (!webcam->ctx->ref_taken[webcam->cam_no]) {
      Buffer8_copy(webcam->ctx->cam_save[webcam->cam_no][0], webcam->ctx->cam_ref[webcam->cam_no]);
      webcam->ctx->ref_taken[webcam->cam_no] = 1;
    }
    int ret = capture_frame(webcam);
    if (ret == -1) {
      fprintf(stderr, "[!] %s: select timeout on webcam %d\n", __func__, webcam->cam_no);
    } else if (ret == -2) {
      fprintf(stderr, "[!] %s: unrecoverable error on webcam %d\n", __func__, webcam->cam_no);
      goto exit;
    }
    ms_sleep(THREADS_DELAY);
  }
 exit:
  pthread_exit(NULL);
}
