#ifndef GOBJBASE_H
#define GOBJBASE_H

#include <cassert>
#include <deque>
#include <vector>
#include <iostream>
using namespace std;

#include <GL/glut.h>
#include <GL/gl.h>

#include <fnobjTfn.h>
#include <point.h>
#include <print.h>
#include <typedefs.h>

#define gobjpush(x) assert(gobjContainer::global != 0); gobj::global->push(x)
#define DOUBLECOLOR(uR,uG,uB) 1.0*uR/256.0, 1.0*uG/256.0, 1.0*uB/256.0 

#include <gobjdebug.h>



class gobjContainer;

/*
\brief Scene base class.

It is assumed that the scene is a vector of gobj pointers. 
 ie a stack.  See gobjContainer.  I believe a stack is more 
 general and powerful than a scene graph which is assumed 
 to be a tree.  A vector contains trees as a subset. 

An important application is that vectors can be easily 
 indexed with integers. Contrast this with a scene graph 
 which is often implemented with pointers.

All graphics objects can draw (unless they exist in a gobj 
 stack for other purposes).

All graphics sees the universal graphics stream 
 gobj::global. (This is currently a single threaded design, 
 but this may be changed).
*/
class gobj
{
public:

  /** The graphics stream. */
  static gobjContainer * global;

  /** Convert the global graphics into a display list. */
  static void globaldisplaylist(uintc id);

  /** Draw the object. */
  virtual void draw() = 0;

  /** Destructor. */
  virtual ~gobj();

};

/*!
\brief Switch global graphics stream. 
Uses stack unwinding with named variable to release the resource.
\par Example
\verbatim
  gobjContainer x; ...
  gobjGlobal g(&x);
  ...
  //Resource released when g out of scope.
\endverbatim
*/
class gobjGlobal
{
  gobjContainer * const globalnew;
  gobjGlobal() : globalnew(0) {}
public:

  /** Push the new global graphics stream. */
  gobjGlobal(gobjContainer * const globalnew_);
  // TODO
  //gobjGlobal(gobjContainer& globalnew_);

  /** Pop and return gobj::global to its previous state. */
  ~gobjGlobal();
};


/*!
\brief OpenGL glCallList function wrapped as an object.

gobjglCallList is templated to be used to either 
 contain the listName or reference the listName.
 ie  gobjglCallList<GLuint> or gobjglCallList<GLuint&>.

glCallList evaluates the display list pointed to by listName.
*/
template< typename T >
class gobjglCallList : public gobj
{
public:

  /** The list id. */
  T listid;

  /** The id is assumed to be valid. */
  gobjglCallList(T listid_)
    : listid(listid_) {}

  /** Draw the compiled list. */
  void draw()
    { GOBJDEBUGCODE glCallList(listid); }

};


/*!
\brief Callback writes graphics.

A function is supplied which when called back pushes
   gobj's to the global graphics stream.  
*/
template< class T >
class gobjcallback : public gobj
{
  fnobj0Tfn<T,void> fobj;
public:

  typedef void (T::*Fptr)();

  /** Constructor binding function. */
  gobjcallback( T & data_, Fptr fn_)
    : fobj(data_,fn_) {}

  /** Write graphics by executing the callback function. */
  void draw()
    { GOBJDEBUGCODE fobj(); }

};

/** Helper function creating callback with no arguments
    functional object. */
template< class T >
gobjcallback<T>* gobjcallbackcreatenew(T & data_, void (T::*fn_)() )
  { return new gobjcallback<T>(data_,fn_); }


/*!
\brief  Composite container of graphics objects.

The client adds the graphics objects to the container
 through push(gobj*). 

Very simple pointer memory management is supported with 
 nuke() and the destructor calling delete() on the pointers.
 By default it is assumed that objects pushed are contructed
 from new operator and are memory managed by this class.

 If cleanup is set to false (explicityly or at construction time)
 then this class assumes the graphics pointers are managed outside 
 of this class.

There is a global graphics pointer which is like a 
 universal graphics stream.  Clients write to the container
 by pushing back gobj's.  This can be used to pass geometry
 as well as a storage place for the gobj's to be evaluated
 at a later date.  
*/
class gobjContainer : public gobj
{
public:

  /** Make the current container the container where all the 
      global graphics is written to. */
  void set() 
    { gobj::global = this; }

  /** Push and pop the graphic context/stream. */
  static vector< gobjContainer * > globalvec;

  /** Store the previous container and make this container
      the global graphics stream. */
  void globalpush() 
    { globalvec.push_back( gobj::global); set(); }

  /** Restore the previous graphics stream. */
  static void globalpop();

  /** Public for the client to access all the graphics. */
  vector<gobj*> vg;

  /** Default true to delete vg[i]. */
  bool cleanup;

  /** Memory management enabled by default.*/
  gobjContainer();
  /** Graphics objects optionally deleted. **/
  gobjContainer(boolc cleanup_);
  /** Destructor. */
  ~gobjContainer();

  /** Empties container, deleting if cleanup is true. */
  void nuke();

  /** Sets vg[k] to 0. Useful for mixed memory situations. */
  boolc kill(uintc k);
  /** Set vg[k] to 0. O(N) complexity. */
  boolc kill(gobj* const targ);

  /** Draws vg[i]. */
  void draw();

  /** Add graphics objects to vg. */
  void push(gobj * g)
    { assert(g!=0); vg.push_back(g); }
  /** Add a callback function.*/
  template< class T >
  void pushcallback(T & data_, void (T::*fn_)() )
   { push(new gobjcallback<T>(data_,fn_)); }

  /** Compile the list of graphics to a display list. The 
      new operator called so the client owns the object. */
  gobjglCallList<GLuint> * displaylistcreatenew(uint id);

  /** Convert this graphics list into a display list.  
      This destroys and realizes the current geometry and 
      is a non reversible operation. cleanup is set to true. 
      The id is the compiled lists id.*/
  void displaylist(uintc id);
};

class gobjContainerdeque : public gobj
{
public:

  void push_front(gobj* g)
    { assert(g!=0); vg.push_front(g); }
  void push_back(gobj* g)
    { assert(g!=0); vg.push_back(g); }

  /** Draws vg[i]. */
  void draw();

  /** Public for the client to access all the graphics. */
  deque<gobj*> vg;

  /** Generally set at construction and leave. If set to 
      true, calls delete on vg[i] when object dies. */
  bool cleanup;

  /** Memory management enabled by default.*/
  gobjContainerdeque()
    : cleanup(true) {};

  /** If cleanup is true the graphics objects need to have 
      been constructed from new as they are deleted when 
      this container dies. */
  gobjContainerdeque(boolc cleanup_)
    : cleanup(cleanup_) {};
  /** Destructor. */
  ~gobjContainerdeque();

  /** Empties the container of all graphics, calling 
      delete if cleanup is true. */
  void nuke();

};

template< typename BOOL=bool >
class gobjSwitch;

/*!
\brief A graphics container where each is a switch.

Useful for a scene where there are multiple objects
 that may or may not be visible.  By default all
 objects are visible.

Use in a similar way to gobjContainer.
*/
class gobjContainerSwitch : public gobj
{
public:

  /** The graphics being displayed. */
  gobjContainer gcontainer;
  /** Turn on and off individual graphics. */
  vector< gobjSwitch<>* > gswitch; 

  /** On death the switches are deleted but what is inside them
      is deleted on the clients cleanup policy. */
  gobjContainerSwitch(boolc cleanup_);

  /** Draw the scene. */
  void draw()
    { GOBJDEBUGCODE gcontainer.draw(); }
  
  /** Delete the switches which contain the memory policy of
      what they are pointing to. */
  void nuke();
  /** Clean up memory, at the least the switches. */
  ~gobjContainerSwitch();

  /** g is wrapped in a gobjSwitch and pushed to gcontainer. */
  gobjSwitch<> * push(gobj* g);
};


/*!
\brief Execute draw functions on the containers in the order 
       of pre is first, this class is second and post is 
       last. 

The aquisition and release of resources usually 
 follows a particular pattern.  First be aquire the resouce. 
 Then use it.  Then clean up.  

From a graphics perspective we can also use this class to 
 change or update what is in the middle. For example 
 initialize color in pre, restore color in post. And 
 whenever the geometry gets rewritten clear and write the 
 new geometry to the middle container.

So the client pushed and pops geometry into either of the 
 three containers, the middle container being this classes 
 derived container.
*/
class gobjContainerPrePost : public gobjContainer
{
public:

  /** The first geometry to be drawn. */
  gobjContainer pre;
  /** The last geometry to be drawn. */
  gobjContainer post;

  /** Draws pre, this and post containers. */
  void draw();

  /** The three containers use the same memory stategy. */
  gobjContainerPrePost(boolc cleanup_=false)
    : gobjContainer(cleanup_), pre(cleanup_), post(cleanup_) {}

};


/*!
\brief  If the switch is on draw the object else don't.

This class wraps a gobj in a switch so that the gobj can be 
 displayed or not displayed depending on isdrawn's state.  

The BOOL is designed to have either bool or bool & .
*/
template< typename BOOL >
class gobjSwitch : public gobj
{ 
public:

  /** The object that the switch is acting on. */
  gobj * x;

  /** Is the object drawn when draw() is called? */
  BOOL isdrawn;
  /** Release x when this object dies. */
  bool cleanup;

  /** Pass the object, set the initial drawing state and 
      memory policy. If x_ is 0 this switch is in a 
      unconstructed state. */
  gobjSwitch
  (
    gobj * x_,
    BOOL isdrawn_, 
    boolc cleanup_=true
  )
    : x(x_), isdrawn(isdrawn_), cleanup(cleanup_) 
    { }

  /** Possible cleanup. */
  ~gobjSwitch()
    { if (cleanup==false) return; delete x; x=0; }

  /** Draws the object x if flag is true. */
  void draw()
    { GOBJDEBUGCODE assert(x!=0); if (isdrawn) x->draw(); }

  /** Toggle the isdrawn variable. */
  void toggle()
    { assert(x!=0); isdrawn = ! isdrawn; }

};


/*!
\brief Callback writes graphics.

A function is supplied that pushes gobj's to 
 a gobjContainer.
*/
template< class T >
class gobjcallbackcontainer : public gobj
{
  fnobj1Tfn<T,void,gobjContainer&> fobj;
public:

  typedef void (T::*Fptr)(gobjContainer&);

  /** Constructor binding function. */
  gobjcallbackcontainer( T & data_, Fptr fn_)
    : fobj(data_,fn_) {}

  /** Write graphics by executing the callback function. */
  void draw()
  { 
    GOBJDEBUGCODE
    gobjContainer* shp = new gobjContainer(true);
    
    fobj(*shp); 
    gobjpush(shp);
  }

};

/** Helper function creating callback with first
 argument gobjContainer functional object. */
template< class T >
gobjcallbackcontainer<T>* gobjcallbackcontainercreatenew
(
  T & data_, 
  void (T::*fn_)(gobjContainer&) 
)
  { return new gobjcallbackcontainer<T>(data_,fn_); }



#endif


