//-----------------------------------------------------------------------------
// Project            	RenderKit
// Class		G3DGlutCamera
// Creator            	Philippe C.D. Robert
// Maintainer         	Philippe C.D. Robert
// Creation Date      	Thu Sep  9 11:05:08 CEST 1999
//
// Copyright (c) Philippe C.D. Robert
//
// The RenderKit is free software; you can redistribute it and/or modify it 
// under the terms of the GNU LGPL Version 2 as published by the Free 
// Software Foundation
//
// $Id: G3DGlutCamera.m,v 1.28 2001/07/15 14:29:13 probert Exp $
//-----------------------------------------------------------------------------

#import "G3DGlutCamera.h"

#import <RenderKit/RenderKit.h>

#ifdef XMESA
#include "GL/xmesa.h"
#endif

#ifndef KNOWS_SEL
#define KNOWS_SEL(x) (renderDelegate && [renderDelegate respondsToSelector:@selector(x)]) 
#endif

/*
 * Function pointers
 *
 */

void (*keyboard)(id,SEL,unsigned char,int,int);
void (*rsh)(id,SEL,int,int);
void (*display)(id,SEL);
void (*mouse)(id,SEL,int button, int state,int x, int y);
void (*motion)(id,SEL,int x, int y);
void (*passiveMotion)(id,SEL,int x, int y);
void (*specialKeyboard)(id,SEL,int key, int x, int y);
void (*idle)(id,SEL);

/*
 * The camera
 *
 */

static id _cam_obj = NULL;

/*
 * The callbacks used by glut
 *
 */

static void _glutKeyboardFunc(unsigned char key, int x, int y);
static void _glutDisplayFunc(void);
static void _glutReshapeFunc(int,int);
static void _glutMouseFunc(int button, int state,int x, int y);
static void _glutMotionFunc(int x, int y);
static void _glutPassiveMotionFunc(int x, int y);
static void _glutSpecialFunc(int key, int x, int y);
static void _glutIdleFunc(void);

static void _glutKeyboardFunc( unsigned char key, int x, int y )
{
  (*keyboard)(_cam_obj,@selector(keyboardEvent:x:y:),key,x,y);
}

static void _glutDisplayFunc( void )
{
  (*display)(_cam_obj,@selector(render));
}

static void _glutReshapeFunc(int w,int h)
{
  (*rsh)(_cam_obj,@selector(reshapeWidth:height:),w,h);
}

static void _glutMouseFunc(int button, int state,int x, int y)
{
  (*mouse)(_cam_obj,@selector(mouseButtonPressed:state:x:y:),button,state,x,y);
}

static void _glutMotionFunc(int x, int y)
{
  (*motion)(_cam_obj,@selector(motionAtX:y:),x,y);
}

static void _glutPassiveMotionFunc(int x, int y)
{
  (*passiveMotion)(_cam_obj,@selector(passiveMotionAtX:y:),x,y);
}

static void _glutSpecialFunc(int key, int x, int y)
{
  (*specialKeyboard)(_cam_obj,@selector(specialKeyboardEvent:x:y:),key,x,y);
}

static void _glutIdleFunc(void)
{
  (*idle)(_cam_obj,@selector(idle));
}

NSString *G3DGlutCameraException = @"G3DGlutCameraException";

@implementation G3DGlutCamera

//-----------------------------------------------------------------------------
// Init methods
//-----------------------------------------------------------------------------

- (id)initWithSize:(NSSize)aSize
{
  return [self initWithSize:aSize fullscreen:NO];
}

- (id)initWithSize:(NSSize)aSize fullscreen:(BOOL)yn
{
  if ((self = [super initWithSize:aSize])) {

    // glut stuff
    display = (void *)[self methodForSelector:@selector(render)];
    keyboard = (void *)[self methodForSelector:@selector(keyboardEvent:x:y:)];
    rsh = (void *)[self methodForSelector:@selector(reshapeWidth:height:)];
    mouse = (void *)[self methodForSelector:@selector(mouseButtonPressed:state:x:y:)];
    motion = (void *)[self methodForSelector:@selector(motionAtX:y:)];
    passiveMotion = (void *)[self methodForSelector:@selector(passiveMotionAtX:y:)];
    specialKeyboard = (void *)[self methodForSelector:@selector(specialKeyboardEvent:x:y:)];
    idle = (void *)[self methodForSelector:@selector(idle)];
    
    _cam_obj = self;
    
    _knowsRE = NO;
    _knowsRB = NO;
    _knowsRM = NO;
    _knowsRPM = NO;
    _knowsRSE = NO;
    _knowsII = NO;
  }

  return self;
}

//-----------------------------------------------------------------------------
// Delegate
//-----------------------------------------------------------------------------

- (void)setRenderDelegate:(id)aDelegate
{
  [super setRenderDelegate:aDelegate];

  _knowsRE = (KNOWS_SEL(glutCamera:didReceiveEvent:x:y:)) ? YES : NO;
  _knowsRB = (KNOWS_SEL(glutCamera:didReceiveButton:state:x:y:)) ? YES : NO;
  _knowsRM = (KNOWS_SEL(glutCamera:didReceiveMotionAtX:y:)) ? YES : NO;
  _knowsRPM = (KNOWS_SEL(glutCamera:didReceivePassiveMotionAtX:y:)) ? YES : NO;
  _knowsRSE = (KNOWS_SEL(glutCamera:didReceiveSpecialEvent:x:y:)) ? YES : NO;
  _knowsII = (KNOWS_SEL(glutCameraIsIdle:)) ? YES : NO;
}

//-----------------------------------------------------------------------------
// Rendering and glut methods
//-----------------------------------------------------------------------------

- (void)setupCamera
{
#ifdef DEBUG
  GLenum err;
  const GLubyte *str;
#endif DEBUG  
  NSString *proc = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];

  // Setup glut...
  glutInitWindowSize(_size.width, _size.height);
  glutInitDisplayMode( GLUT_DEPTH | GLUT_RGB | GLUT_DOUBLE | GLUT_ALPHA);
  
  if (!glutCreateWindow([[proc lastPathComponent] cString])) {
    [NSException raise:G3DGlutCameraException format:@"Failed to create a glut window!"];
    return;
  }

  // The glut callbacks
  glutKeyboardFunc(_glutKeyboardFunc);
  glutDisplayFunc(_glutDisplayFunc);
  glutReshapeFunc(_glutReshapeFunc);
  glutMouseFunc(_glutMouseFunc);
  glutMotionFunc(_glutMotionFunc);
  glutPassiveMotionFunc(_glutPassiveMotionFunc);
  glutSpecialFunc(_glutSpecialFunc);
  glutIdleFunc(_glutIdleFunc);

  glClearColor([backgroundColour red],[backgroundColour green],[backgroundColour blue],[backgroundColour alpha]);

  if (cameraFlags.isFullscreen) {
#ifdef XMESA
    XMesaSetFXmode(XMESA_FX_FULLSCREEN);
#else
    glutFullScreen();
#endif XMESA
  }

  [self setViewportWithSize:_size];

  // Notify the renderDelegate
  if (renderDelegate && [renderDelegate respondsToSelector:@selector(cameraDidInitialise:)]) {
    [renderDelegate cameraDidInitialise:self];
  }

  // Z Buffering
  if (cameraFlags.doDepthTest) {
    glEnable(GL_DEPTH_TEST);
  }

  // Everything is OK now
  cameraFlags.canRender = YES;

  [[G3DRenderEngine renderEngine] initEngine];

#ifdef DEBUG
  if ((err = glGetError())!=GL_NO_ERROR) {
    str = gluErrorString(err);
    NSLog([NSString stringWithFormat:@"GL error while initialising: %s\n",str]);
  }
#endif DEBUG
}

- (void)renderScene
{
  if (cameraFlags.degenerate || !cameraFlags.canRender) {
    return;
  }  

  glutMainLoop();
}

- (void)render
{
  [super render];

  glutSwapBuffers();
}

//-----------------------------------------------------------------------------
// glut callbacks
//-----------------------------------------------------------------------------

- (void)reshapeWidth:(int)w height:(int)h
{
  _size.width = w;
  _size.height = h;

  [self setViewportWithSize:_size];

  (*display)(_cam_obj,@selector(render));
}

- (void)keyboardEvent:(unsigned char)c x:(int)x y:(int)y
{
  if (_knowsRE) {
    [renderDelegate glutCamera:self didReceiveEvent:c x:x y:y];
  }
}

- (void)mouseButtonPressed:(int)button state:(int)glutstate x:(int)x y:(int)y
{
  DLOG(@"Button:%d, state %d, x = %d, y = %d",button,state,x,y);
  if (_knowsRB) {
    [renderDelegate glutCamera:self didReceiveButton:button state:glutstate x:x y:y];
  }
}

- (void)motionAtX:(int)x y:(int)y
{
  DLOG(@"Motion x = %d, y = %d",x,y);
  if (_knowsRM) {
    [renderDelegate glutCamera:self didReceiveMotionAtX:x y:y];
  }
}

- (void)passiveMotionAtX:(int)x y:(int)y
{
  DLOG(@"Passive motion x = %d, y = %d",x,y);
  if (_knowsRPM) {
    [renderDelegate glutCamera:self didReceivePassiveMotionAtX:x y:y];
  }
}

- (void)specialKeyboardEvent:(int)key x:(int)x y:(int)y
{
  DLOG(@"Special event %d, x = %d, y = %d",key,x,y);
  if (_knowsRPM) {
    [renderDelegate glutCamera:self didReceiveSpecialEvent:key x:x y:y];
  }
}

- (void)idle
{
  DLOG(@"Idle...");
  if (_knowsII) {
    [renderDelegate glutCameraIsIdle:self];
  }
}

- (void)setViewportWithSize:(NSSize)s
{
  int width = (int)s.width;
  int height = (int)s.height;
  G3DFrustumValues _fr_vals = [frustum frustumValues];

  cameraFlags.degenerate = (width <= 0 || height <= 0) ? YES : NO;

  DLOG(G3DStringFromFrustum(_fr_vals));

  /*
   * Frustum
   *
   */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(_fr_vals.left,
            _fr_vals.right,
            _fr_vals.bottom,
            _fr_vals.top,
            _fr_vals.near,
            _fr_vals.far);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /*
   * Viewport
   *
   */

  glViewport(0,0,width,height);
  glutReshapeWindow(width,height);

  if (renderDelegate && 
      [renderDelegate respondsToSelector:@selector(cameraViewportDidChange:)]){
    [renderDelegate cameraViewportDidChange:self];
  }

  (*display)(_cam_obj,@selector(render));
}

@end



