/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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.
  
    You should have received a copy of the GNU General Public License
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-camera.c :: Midnight Code UVC camera library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <arpa/inet.h>

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-camera.h"



int
midnightcode_camera_open(struct midnightcode_camera_element_t * camera,
                     int width, int height, int fps, int format, int method) {


  if(camera == NULL)
    return 1;
  if(camera->enabled)
    return 1;
  if(width == 0 || height == 0)
    return 1;
  if(format != V4L2_PIX_FMT_MJPEG && format != V4L2_PIX_FMT_YUYV)
    return 1;
  if(method != V4L2_MEMORY_MMAP)
    return 1;


  if((camera->fd = open(camera->device, O_RDWR)) < 0)
    return 1;

  memset(camera->status, 0, 128);
  memset(camera->label, 0, 128);

  camera->width = width;
  camera->height = height;
  camera->fps = fps;
  camera->format = format;
  camera->method = method;
  camera->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  camera->frame_buffer = NULL;
  camera->tmp_buffer = NULL;

  camera->image_serial = 0;

  camera->recordstart = 0;
  camera->recordtime = 0;
  camera->framecount = 0;
  camera->getpic = 0;
  camera->signalquit = 1;
  camera->fileCounter = 0;

  camera->rawFrameCapture = 0;
  camera->rfsBytesWritten = 0;
  camera->rfsFramesWritten = 0;
  camera->bytesWritten = 0;
  camera->framesWritten = 0;

  if(midnightcode_camera_format(camera))
    return 1;
  if(midnightcode_camera_fps(camera))
    return 1;
  if(midnightcode_camera_buffers(camera))
    return 1;

  camera->framesize = (camera->width * camera->height << 1);

  switch(camera->format) {

    case V4L2_PIX_FMT_MJPEG:
      camera->tmp_buffer = (unsigned char *)malloc((size_t)camera->framesize);
      if(!camera->tmp_buffer)
        return 1;
      camera->frame_buffer = (unsigned char *)malloc(
        (size_t)(camera->width * (camera->height + 8) * 2));
      break;

    case V4L2_PIX_FMT_YUYV:
      camera->frame_buffer =
        (unsigned char *)malloc((size_t)camera->framesize);
      break;

    default:
      return 1;
      break;

  }

  if(camera->frame_buffer == NULL) {
    if(camera->tmp_buffer != NULL)
      free(camera->tmp_buffer);
    camera->tmp_buffer = NULL;
    return 1;
  }

  camera->enabled = 1;


  return 0;
}



int
midnightcode_camera_close(struct midnightcode_camera_element_t * camera) {


  if(camera == NULL)
    return 1;
  if(!camera->enabled)
    return 1;

  if(camera->active)
    midnightcode_camera_deactivate(camera);

  if(camera->frame_buffer != NULL)
    free(camera->frame_buffer);
  camera->frame_buffer = NULL;

  if(camera->tmp_buffer != NULL)
    free(camera->tmp_buffer);
  camera->tmp_buffer = NULL;

  memset(camera->label, 0, 128);
  memset(camera->status, 0, 128);

  camera->enabled = 0;
  close(camera->fd);


  return 0;
}



/* NOTE: mencoder camera.mjpeg
           -ovc lavc -lavcopts vcodec=xvid -oac copy -o output.divx */
int
midnightcode_camera_read(struct midnightcode_camera_element_t * camera,
                     char ** buffer, int * buffer_size) {


  if(camera == NULL)
    return 1;
  if(buffer == NULL)
    return 1;
  if(!camera->enabled)
    return 1;

  if(!camera->active) {
    if(midnightcode_camera_activate(camera))
      return 1;
  }

  memset(&camera->v4l_buffer, 0, sizeof(struct v4l2_buffer));
  camera->v4l_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  camera->v4l_buffer.memory = V4L2_MEMORY_MMAP;
  if(ioctl(camera->fd, VIDIOC_DQBUF, &camera->v4l_buffer) < 0)
    return 1;

  if(camera->v4l_buffer.bytesused > 0) {
    *buffer_size = camera->v4l_buffer.bytesused;
    *buffer = malloc(*buffer_size);
    if(*buffer == NULL)
      return 1;
    memcpy(*buffer, camera->buffer[camera->v4l_buffer.index], *buffer_size);
  }

  if(ioctl(camera->fd, VIDIOC_QBUF, &camera->v4l_buffer) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_activate(struct midnightcode_camera_element_t * camera) {


  if(camera == NULL)
    return 1;
  if(!camera->enabled)
    return 1;
  if(camera->active)
    return 1;

  if(ioctl(camera->fd, VIDIOC_STREAMON, &camera->type) < 0)
    return 1;


  camera->active = 1;


  return 0;
}



int
midnightcode_camera_deactivate(struct midnightcode_camera_element_t * camera) {


  if(camera == NULL)
    return 1;
  if(!camera->enabled)
    return 1;
  if(!camera->active)
    return 1;

  if(ioctl(camera->fd, VIDIOC_STREAMOFF, &camera->type) < 0)
    return 1;

  camera->active = 0;


  return 0;
}



int
midnightcode_camera_format(struct midnightcode_camera_element_t * camera) {


  if(camera == NULL)
    return 1;


  memset(&camera->v4l_format, 0, sizeof(struct v4l2_format));
  camera->v4l_format.type = camera->type;
  camera->v4l_format.fmt.pix.width = camera->width;
  camera->v4l_format.fmt.pix.height = camera->height;
  camera->v4l_format.fmt.pix.pixelformat = camera->format;
  camera->v4l_format.fmt.pix.field = V4L2_FIELD_ANY;

  if(ioctl(camera->fd, VIDIOC_S_FMT, &camera->v4l_format) < 0)
    return 1;

  if((camera->v4l_format.fmt.pix.width != (unsigned int)camera->width) ||
    (camera->v4l_format.fmt.pix.height != (unsigned int)camera->height)) {
    camera->width = camera->v4l_format.fmt.pix.width;
    camera->height = camera->v4l_format.fmt.pix.height;
  }


  return 0;
}



int
midnightcode_camera_fps(struct midnightcode_camera_element_t * camera) {


  if(camera == NULL)
    return 1;


  memset(&camera->v4l_parameters, 0, sizeof(struct v4l2_streamparm));
  camera->v4l_parameters.type = camera->type;
  camera->v4l_parameters.parm.capture.timeperframe.numerator = 1;
  camera->v4l_parameters.parm.capture.timeperframe.denominator = camera->fps;
  if(ioctl(camera->fd, VIDIOC_S_PARM, &camera->v4l_parameters) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_buffers(struct midnightcode_camera_element_t * camera) {
  int bufn;


  if(camera == NULL)
    return 1;


  memset(&camera->v4l_requestbuf, 0, sizeof(struct v4l2_requestbuffers));
  camera->v4l_requestbuf.count = MIDNIGHTCODE_CAMERA_BUFFERS;
  camera->v4l_requestbuf.type = camera->type;
  camera->v4l_requestbuf.memory = V4L2_MEMORY_MMAP;
  if(ioctl(camera->fd, VIDIOC_REQBUFS, &camera->v4l_requestbuf) < 0)
    return 1;

  for(bufn = 0; bufn < MIDNIGHTCODE_CAMERA_BUFFERS; bufn++) {
    memset(&camera->v4l_buffer, 0, sizeof(struct v4l2_buffer));
    camera->v4l_buffer.index = bufn;
    camera->v4l_buffer.type = camera->type;
    camera->v4l_buffer.memory = V4L2_MEMORY_MMAP;
    if(ioctl(camera->fd, VIDIOC_QUERYBUF, &camera->v4l_buffer) < 0)
      return 1;
    camera->buffer[bufn] = mmap(0, camera->v4l_buffer.length,
      PROT_READ, MAP_SHARED, camera->fd, camera->v4l_buffer.m.offset);
    if(camera->buffer[bufn] == MAP_FAILED)
      return 1;
  }

  for(bufn = 0; bufn < MIDNIGHTCODE_CAMERA_BUFFERS; bufn++) {
    memset(&camera->v4l_buffer, 0, sizeof(struct v4l2_buffer));
    camera->v4l_buffer.index = bufn;
    camera->v4l_buffer.type = camera->type;
    camera->v4l_buffer.memory = V4L2_MEMORY_MMAP;
    if(ioctl(camera->fd, VIDIOC_QBUF, &camera->v4l_buffer) < 0)
      return 1;
  }


  return 0;
}



int
midnightcode_camera_verifycontrol(
                     struct midnightcode_camera_element_t * camera,
                     int control, struct v4l2_queryctrl * v4l_queryctrl) {


  if(camera == NULL)
    return 1;
  if(!camera->enabled)
    return 1;


  v4l_queryctrl->id = control;
  if(ioctl(camera->fd, VIDIOC_QUERYCTRL, v4l_queryctrl) < 0)
    return 1;

  if(v4l_queryctrl->flags & V4L2_CTRL_FLAG_DISABLED)
    return 1;

  if(v4l_queryctrl->flags & V4L2_CTRL_TYPE_BOOLEAN)
    return 1;

  if(v4l_queryctrl->type & V4L2_CTRL_TYPE_INTEGER)
    return 0;

  return 1;
}



int
midnightcode_camera_action_backlightcompensation(
                     struct midnightcode_camera_element_t * camera,
                     int value) {
  int control;
  struct v4l2_control v4l_control;
  struct v4l2_queryctrl v4l_queryctrl;


  if(camera == NULL)
    return 1;

  control = MIDNIGHTCODE_CAMERA_CID_BACKLIGHT_COMPENSATION;

  if(midnightcode_camera_verifycontrol(camera, control, &v4l_queryctrl))
    return 1;

  v4l_control.id = control;
  v4l_control.value = (unsigned char)value;
  if(ioctl(camera->fd, VIDIOC_S_CTRL, &v4l_control) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_action_powerlinefrequency(
                     struct midnightcode_camera_element_t * camera,
                     int value) {
  int control;
  struct v4l2_control v4l_control;
  struct v4l2_queryctrl v4l_queryctrl;


  if(camera == NULL)
    return 1;

  control = MIDNIGHTCODE_CAMERA_CID_POWER_LINE_FREQUENCY;

  if(midnightcode_camera_verifycontrol(camera, control, &v4l_queryctrl))
    return 1;

  v4l_control.id = control;
  v4l_control.value = (unsigned char)value;
  if(ioctl(camera->fd, VIDIOC_S_CTRL, &v4l_control) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_action_panrelative(
                     struct midnightcode_camera_element_t * camera,
                     int value) {
  int control;
  struct v4l2_control v4l_control;
  struct v4l2_queryctrl v4l_queryctrl;


  if(camera == NULL)
    return 1;

  control = MIDNIGHTCODE_CAMERA_CID_PAN_RELATIVE;

  if(midnightcode_camera_verifycontrol(camera, control, &v4l_queryctrl))
    return 1;

  v4l_control.id = control;
  v4l_control.value = (unsigned char)value;
  if(ioctl(camera->fd, VIDIOC_S_CTRL, &v4l_control) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_action_tiltrelative(
                     struct midnightcode_camera_element_t * camera,
                     int value) {
  int control;
  struct v4l2_control v4l_control;
  struct v4l2_queryctrl v4l_queryctrl;


  if(camera == NULL)
    return 1;

  control = MIDNIGHTCODE_CAMERA_CID_TILT_RELATIVE;

  if(midnightcode_camera_verifycontrol(camera, control, &v4l_queryctrl))
    return 1;

  v4l_control.id = control;
  v4l_control.value = (unsigned char)value;
  if(ioctl(camera->fd, VIDIOC_S_CTRL, &v4l_control) < 0)
    return 1;


  return 0;
}



int
midnightcode_camera_action_resetpantilt(
                     struct midnightcode_camera_element_t * camera,
                     int value) {
  int control;
  struct v4l2_control v4l_control;
  struct v4l2_queryctrl v4l_queryctrl;


  if(camera == NULL)
    return 1;

  control = MIDNIGHTCODE_CAMERA_CID_PANTILT_RESET;

  if(midnightcode_camera_verifycontrol(camera, control, &v4l_queryctrl))
    return 1;

  v4l_control.id = control;
  v4l_control.value = (unsigned char)value;
  if(ioctl(camera->fd, VIDIOC_S_CTRL, &v4l_control) < 0)
    return 1;


  return 0;
}



/* TODO: Consider not implementing this against libavcodec, internally */
int
midnightcode_camera_transcode(struct midnightcode_camera_element_t * camera,
                     int transcode, char * input_buffer, int input_size,
                     char ** output_buffer, int * output_size) {


  if(camera == NULL)
    return 1;
  if(input_buffer == NULL)
    return 1;
  if(input_size <= 0)
    return 1;
  if(output_buffer == NULL)
    return 1;
  if(output_size == NULL)
    return 1;
  if(transcode != MIDNIGHTCODE_CAMERA_TRANSCODE_JPEG && 
    transcode != MIDNIGHTCODE_CAMERA_TRANSCODE_AVI)
    return 1;

  *output_buffer = NULL;
  *output_size = 0;
  if(input_size <= 175)
    return 2;

  switch(transcode) {

    case MIDNIGHTCODE_CAMERA_TRANSCODE_JPEG:
      switch(camera->format) {

        case V4L2_PIX_FMT_MJPEG:
          // jpeg_decode(&vd->framebuffer, vd->tmpbuffer, &vd->width,
          //   &vd->height)
          break;

        default:
          return 1;
          break;

      }
      break;

    case MIDNIGHTCODE_CAMERA_TRANSCODE_AVI:
      switch(camera->format) {

        case V4L2_PIX_FMT_MJPEG:
          // AVI_write_frame(vd->avifile, vd->tmpbuffer,
          //   vd->buf.bytesused, vd->framecount);
          break;

        default:
          return 1;
          break;

      }
      break;

    default:
      return 1;
      break;
  }
   

  return 0;
}



int
midnightcode_camera_probe(struct midnightcode_camera_element_t * camera) {
  int fd;


  if(!camera)
    return 1;
  if(strlen(camera->device) < 6)
    return 1;
  if(camera->enabled)
    return 1;
  if(camera->active)
    return 1;

  if(access(camera->device, F_OK) != 0)
    return 1;

  if((fd = open(camera->device, O_RDWR)) < 0)
    return 1;

  memset(&camera->v4l_capability, 0, sizeof(struct v4l2_capability));
  if(ioctl(fd, VIDIOC_QUERYCAP, &camera->v4l_capability) < 0) {
    close(fd);
    return 1;
  }
  close(fd);

  if((camera->v4l_capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0 &&
    (camera->v4l_capability.capabilities & V4L2_CAP_STREAMING) == 0) {
    return 2;
  }

  return 0;
}



int
midnightcode_camera_list(struct midnightcode_camera_head_t *elements) {
  int devn, count;
  struct midnightcode_camera_device_t * dev;
  struct midnightcode_camera_element_t * now_element;
  struct midnightcode_camera_device_t camera_device[] = {
    { "/dev/video0" }, { "/dev/video1" },
    { "/dev/video2" }, { "/dev/video3" },
    { "/dev/video4" }, { "/dev/video5" },
    { "/dev/video6" }, { "/dev/video7" },
    { "/dev/video8" }, { "/dev/video9" },
    { "/dev/video10" }, { "/dev/video11" },
    { "/dev/video12" }, { "/dev/video13" },
    { "/dev/video14" }, { "/dev/video15" },
    { 0 }
  };


  count = 0;

  for(dev = camera_device, devn = 0;
    camera_device[devn].device != NULL; devn++) {
    now_element = malloc(sizeof(struct midnightcode_camera_element_t));
    memset(now_element, 0, sizeof(struct midnightcode_camera_element_t));

    snprintf(now_element->device, 63, "%s", camera_device[devn].device);

    if(midnightcode_camera_probe(now_element)) {
      free(now_element);
    } else {
      midnightcode_list_append(elements, now_element);
      count++;
    }
  }

  return count;
}


