// GUI app sonic-snap
// by Bram Stolk

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Slider.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Output.H>

#include <FL/fl_draw.H>
#include <FL/fl_message.H>
#include <FL/fl_file_chooser.H>
#include <FL/fl_ask.H>

#include "sonixcam.h"

#ifdef FAME
#include "videoencoder.h"
#endif

static int width=352;
static int height=288;

static SonixCam *cam=0;

#ifdef FAME
static VideoEncoder *encoder=0;
#endif

static Fl_Window *window=0;

static Fl_Output *o_fps;
static Fl_Check_Button *b_stream;
static Fl_Check_Button *b_hist;
static Fl_Check_Button *b_norm;
static Fl_Check_Button *b_autog;
static Fl_Button *b_ppm;
static Fl_Check_Button *b_mpeg;
static Fl_Button *b_about;
static Fl_Button *b_quit;
static Fl_Slider *s_gain;
static Fl_Choice *c_comp;


class CamView : public Fl_Widget
{
  public: 
   CamView():
     Fl_Widget(0,0,width,height,"camview")
   {}
   ~CamView() {}
   void draw(void)
   {
     assert(cam);
     unsigned char *pixels = cam->GetImage();
     assert(pixels);
     fl_draw_image(pixels, 0, 0, width, height);
   }
};


static double elapsed(void)
{
  static struct timespec prev;
  static bool first=true;
  static struct timespec curr;
  int ok;
  ok = clock_gettime(CLOCK_REALTIME, &curr);
  if (ok)
    perror("clock_gettime");
  double delta;
  if (first)
  {
    first = false;
    delta = 0.0;
  }
  else
  {
    delta = curr.tv_sec - prev.tv_sec;
    delta += (curr.tv_nsec - prev.tv_nsec) / 1000000000.0;
  }
  prev = curr;
  return delta;
}


static void adjust_gain(void)
{
  if (b_autog->value())
  {
    float gc = cam->AnalyzeGainChange(0.00025);
    float gain = cam->GetGain();
    gain += gc;
    if (gain>1.0) gain=1.0;
    if (gain<0.0) gain=0.0;
    cam->SetGain(gain);
    s_gain->value(gain);
  }
}


static void idle_cb(void *arg)
{
  CamView *cv = reinterpret_cast<CamView*>(arg);
  if (cam->Sustain())
  { 
    cv->redraw();
#ifdef FAME
    if (encoder)
      encoder->AddFrame(cam->GetImage());
#endif
    adjust_gain();
    // Do FPS calculations
    static int framecount=0;
    if (!(framecount++ & 31))
    {
      double delta = elapsed();
      if (delta)
      {
        static char line[80];
        sprintf(line,"Frame Rate: %4.1f FPS", 32 / delta);
        o_fps->value(line);
      }
    }
  }
  else
  {
    usleep(5000);
  }
  return;
}


static void about_cb(Fl_Widget *o)
{
  fl_message
  (
    "This program is (c)2004 by Bram Stolk.\n"
    "sn9c102 decompressor by Bertrik Sikken.\n"
  );
}


static void hist_cb(Fl_Widget *o)
{
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  cam->ShowHistograms(b->value());
}


static void comp_cb(Fl_Widget *o)
{
#if 0
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  cam->SetCompression(b->value());
#else
  Fl_Choice *c = (Fl_Choice*)o;
  if (c->value() == 0)
  {
    cam->SetCompression(false);
    return;
  }
  if (c->value() == 1)
  {
    cam->SetCompression(true);
    cam->SetJPEG(0);
    return;
  }
  if (c->value() == 2)
  {
    cam->SetCompression(true);
    cam->SetJPEG(1);
    return;
  }
#endif
}


static void norm_cb(Fl_Widget *o)
{
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  cam->DoNormalize(b->value());
}


static void autog_cb(Fl_Widget *o)
{
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  cam->DoAutoGain(b->value());
}


static void quit_cb(Fl_Widget *o)
{
  delete window;
}


static void gain_cb(Fl_Widget *o)
{
  Fl_Slider *s = (Fl_Slider*)o;
  cam->SetGain(s->value());
}


static void stream_cb(Fl_Widget *o)
{
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  if (b->value())
  {
    cam->StartCapture();
    b_hist->activate();
    c_comp->activate();
    b_norm->activate();
    s_gain->activate();
    b_autog->activate();
#ifdef FAME
    b_mpeg->activate();
#endif
  }
  else
  {
    cam->StopCapture();
    b_hist->deactivate();
    c_comp->deactivate();
    b_norm->deactivate();
    s_gain->deactivate();
    b_autog->deactivate();
    b_mpeg->deactivate();
  }
}


static void mpeg_cb(Fl_Widget *o)
{
#ifdef FAME
  Fl_Check_Button *b = (Fl_Check_Button*)o;
  if (b->value())
  {
    char *fname = fl_file_chooser("Capture to MPEG", 0,0);
    if (!fname)
    {
      b->value(0);
      return;
    }
    encoder = new VideoEncoder(fname, width, height);
  }
  else
  {
    delete encoder;
    encoder = 0;
  }
#endif
}
 

static void captureppm_cb(Fl_Widget *o)
{
  int sz=width*height*3;
  unsigned char *img = new unsigned char[sz];
  memcpy(img, cam->GetImage(), sz);
  char *fname = fl_file_chooser("Capture to PPM", 0,0);
  if (!fname)
  {
    delete [] img;
    return;
  }
  FILE *f=fopen(fname,"wb");
  if (!f)
  {
    fl_alert("Cannot open file %s for writing", fname);
    delete [] img;
    return;
  }
  fprintf(f,"P6 %d %d %d\n", width, height, 255);
  int rv=fwrite(img, sz, 1, f);
  assert(rv==1);
  fclose(f);
  delete [] img;
}


int main(int argc, char *argv[])
{
  char *devname = "/dev/video0";

  if (argc==3 || argc > 4 || (argc > 1 && argv[1][0]=='-'))
  {
    fprintf(stderr,"Usage: %s [/dev/video0] [352 288]\n", argv[0]);
    exit(1);
  }
  if (argc > 1)
    devname = argv[1];
  if (argc==4)
  {
    width=atoi(argv[2]);
    height=atoi(argv[3]);
  }

  struct timespec res;
  int ok = clock_getres(CLOCK_REALTIME, &res);
  if (ok) perror("clock_getres");
  fprintf(stderr,"Clock resolution is %d nanoseconds\n", res.tv_nsec);


  cam = new SonixCam(devname, width, height);

  window = new Fl_Window(width,height+240,cam->GetName());
  CamView *cv = new CamView();

  int vpos = height;

  o_fps = new Fl_Output(0,vpos,width,20); vpos+=20;
  o_fps->value("FPS: 0");
  b_stream = new Fl_Check_Button(0,vpos,width,20,"Stream");
  b_stream->callback(stream_cb); vpos+=20;
  b_hist = new Fl_Check_Button(0,vpos,width,20,"Histograms");
  b_hist->callback(hist_cb); vpos+=20;
  b_norm = new Fl_Check_Button(0,vpos,width,20,"Normalize");
  b_norm->callback(norm_cb); vpos+=20;
  b_autog = new Fl_Check_Button(0,vpos,width,20,"Auto Gain");
  b_autog->callback(autog_cb); vpos+=20;

  c_comp = new Fl_Choice(width/2,vpos,width/2,20,"Compression");
  c_comp->callback(comp_cb); vpos+=20;

  c_comp->add("Off",          0,0);
  c_comp->add("Low Quality",  0,0);
  c_comp->add("High Quality", 0,0);

  b_ppm = new Fl_Button(0,vpos,width,20,"Capture image as PPM");
  b_ppm->callback(captureppm_cb); vpos+=20;
  b_mpeg = new Fl_Check_Button(0,vpos,width,20,"Capture as MPEG");
  b_mpeg->callback(mpeg_cb); vpos+=20;
  b_about = new Fl_Button(0,vpos,width,20,"About");
  b_about->callback(about_cb); vpos+=20;
  b_quit = new Fl_Button(0,vpos,width,20,"Quit");
  b_quit->callback(quit_cb); vpos+=20;
  s_gain = new Fl_Slider(0,vpos,width,20,"gain");
  s_gain->type(FL_HOR_NICE_SLIDER);
  s_gain->callback(gain_cb); vpos+=20;
  window->end();
  Fl::visual(FL_RGB);

  b_stream->value(true); stream_cb(b_stream);
  b_hist->value(true); hist_cb(b_hist);
  c_comp->value(2); comp_cb(c_comp);
  b_norm->value(true); norm_cb(b_norm);
  s_gain->value(0.5);  gain_cb(s_gain);
#ifndef FAME
  b_mpeg->deactivate();
#endif

  window->show();
  cv->show();

#if 1
  Fl::add_idle(idle_cb, cv);
#else
  int rv = pthread_create(&threadid, 0, readthread, (void*) cv);
  if (rv)
    perror("pthread_create");
  assert(!rv);
#endif

  Fl::run();

  cam->StopCapture();
  delete cam;
}

