/* this file contains glut bindings for the gl widgets 
   it implements the gui.h interface */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <unistd.h>  // for usleep
#include <pthread.h>
#include "widget.h"
#include "queue.h"
#include "gui.h"

/* global vars: there's only one window.. */
static class container *mother = 0; // top level container widget, fills entire window
static int window_width = 800;
static int window_height = 200;
static pthread_t thread;
static int need_reshape = 1;
static int need_display = 1;

static class queue *queue_gui = 0;   //  app -> gui comm
static class queue *queue_app = 0;   //  gui -> app comm

static void do_event(event_t *e) {
  if(!mother) return;
  need_display = 1; // crude hey
  class widget *w = (class widget *)e->receiver;
  
  switch(e->type){

  case SUICIDE: // kill widget = remove from widget tree & delete
    if (w) { mother->remove(w); delete w; } return;

  default:
    if (!w) w = mother;
    w->event(e); return;
  }
} 

static void do_appevents(void){
  event_t e;
  while (gui_receive(&e)) { do_event(&e); }
}



/* GLUT CALLBACKS */
static void do_display(void){
  //fprintf(stderr, "do_display pid = %d\n", getpid());
  //usleep(10000); // hack to workaround thread startup problem

  if (!mother) return; // nothing to display

  glClear(GL_COLOR_BUFFER_BIT);
  mother->draw();
  glFlush();
  glutSwapBuffers();

  need_display = 0;
}


static void do_reshape(int width, int height){
  /* get args */
  window_width = width;
  window_height = height;
  if (!mother) return; // nothing to reshape
  mother->resize(0, 0, width, height);
  need_reshape = 0;
  need_display = 1;
}


static void do_idle(void){
  // update gui periodically
  do_appevents();
  //need_reshape = 1;
  //need_display = 1;
  if (need_reshape) do_reshape(window_width, window_height);
  if (need_display) do_display();

  // 50 fps
  // it would be nice to be able to sync to display...
  usleep(20000);
}

static void init_event_coords(event_t *e, int x, int y){
  e->sender = 0;
  e->receiver = 0;
  y = (window_height - y); // reverse y axis
  e->e.mouse.x = ((float)(x)) / ((float)(window_width)); // relative
  e->e.mouse.y = ((float)(y)) / ((float)(window_height)); // relative
}

static void do_mouse(int button, int state, int x, int y)  {
    event_t e;
    init_event_coords(&e, x, y);
    e.type = state ? RELEASE : PRESS;
    e.e.mouse.button = button;
    do_event(&e);
}

static void do_moved_mouse(int x, int y)  {
    event_t e;
    init_event_coords(&e, x, y);
    e.type = MOTION;
    do_event(&e);
}


static void do_keyboard(unsigned char thekey, int mousex, int mousey){
    event_t e;
    init_event_coords(&e, mousex, mousey);
    e.type = KEY_PRESS;
    e.e.keyboard.key = thekey;
    do_event(&e);
}


/* GLUT THREAD */
void *thread_main(void *x){
  const char *argv[] = {"editor-window"}; // dummy program name
    int argc = 1;

    //fprintf(stderr, "thread_main pid = %d\n", getpid());

    glutInit(&argc, (char**)argv);

    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
    glutInitWindowSize(window_width, window_height);
    glutCreateWindow("wvvw");

    glutDisplayFunc(do_display);
    glutReshapeFunc(do_reshape);
    glutMouseFunc(do_mouse);
    glutMotionFunc(do_moved_mouse);
    glutKeyboardFunc(do_keyboard);
    glutIdleFunc(do_idle);
    

    glutMainLoop();
    return 0;
}




/* some remarks

communication between gui thread and pd thread should not
cause the pd thread to block. the only way to guarantee this
is to use lock free communication.

 */


int gui_send(event_t *e) {return queue_gui->write(e);}
int gui_receive(event_t *e) {return queue_gui->read(e);}
int app_send(event_t *e) {return queue_app->write(e);}
int app_receive(event_t *e) {return queue_app->read(e);}
int gui_add(class widget *w){mother->add(w); return 1;}

void gui_start(void){
  pthread_attr_t attr;
  mother = new pack();
  mother->resize(0,0, window_width, window_height);
  queue_app = new queue (1024);
  queue_gui = new queue (1024);


  /* remark on SCHED_OTHER attribute
     if i don't do this, there seems to be a race condition:
     the do_display() function seems to be called in an infinite loop,
     when the main thread has SCHED_FIFO.

     this is not in correspondence with the pthread man pages,
     that clearly state SCHED_OTHER is the default. strange...
  */

  //fprintf(stderr, "gui_start pid = %d\n", getpid());
  pthread_attr_init(&attr);
  pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
  pthread_create(&thread, &attr, thread_main, 0);
}

