#ifndef POINTSURFACE_H
#define POINTSURFACE_H

#include <cassert>

#include <point.h>
#include <gobj.h>

#include <random.h>


/*!
\brief Surface visualization with points.

This function randomly generates points and passes them 
  to a client supplied function which either rejects the 
  point or accepts it.  If the point is accepted the 
  function interprets the point in 3D by writing
  another point and passing it back to this class.  

  This is all done through the functional object interface.  
  Both 2D and 3D versions exist because there are also 
  surfaces that are better described with 3D coordinates. 

  In this sense it is like a MonteCarlo method. For example 
  a circle could be drawn with a rejection test of all 
  points in the unit square outside the circle.
  So the rejection serves as a mask.

  Unit intervals are used to make this object generic.  
  It is the client who interprets the interval.  The random 
  sampling of points on this interval is uniform.
*/
template< typename T=double, typename G=randomgenerator >
class pointsurface : public gobjContainerPrePost
{
  pointsurface()
    { assert(false); }
public:

  /** The number of points to draw. */
  uint N;

  /** The random number generator. 
      Generates a random number on the interval [0.0,1.0]. */
  random11<T,G> rnd;

  /** Pass the number of points, preserves OpenGL attributes 
      and turns off lighting. */
  pointsurface(uintc N_)
    : N(N_)
  {
    pre.push( new gobjglPushAttrib(GL_LIGHTING) );
    pre.push( new gobjglPushAttrib(GL_CURRENT_BIT) );
    pre.push( new gobjglDisable(GL_LIGHTING) );

    post.push( new gobjglPopAttrib() );
    post.push( new gobjglPopAttrib() );
  }

  /** Construct a surface from 2D input. */
  template< typename FN >
  void addsurface2D( FN & fn )
  {
    point3<double> p;
    bool valid(false);

    nuke();

    push( new gobjglBegin(GL_POINTS) );

    for (uint i=0; i<N; )
    {
      point3<double> p;
      fn(valid,p.x,p.y,p.z,rnd(),rnd());
      if (valid)
      {
        push( new gobjglVertex3d(p) );
        ++i;
      }
    }

    push( new gobjglEnd() );
  }

  /** Construct a surface from 3D input. */
  template< typename FN >
  void addsurface3D( FN & fn )
  {
    point3<double> p;
    bool valid(false);

    nuke();

    push( new gobjglBegin(GL_POINTS) );

    for (uint i=0; i<N; )
    {
      point3<double> p;
      fn(valid,p.x,p.y,p.z,rnd(),rnd(),rnd());
      if (valid)
      {
        push( new gobjglVertex3d(p) );
        ++i;
      }
    }

    push( new gobjglEnd() );
  }

};

// <TODO> Template the glVertex3d so that the type T determines which
//  OpenGL call is made. ie double -> glVertex3d, float -> glVertex3f.

/*!
\brief Generate points of the surface of a sphere.
This uses Monte-Carlo style point rejection. 
*/
class pointsurfaceSphere
{
public:

  /** Given a random point in a unit cube generate a random point on
      the sphere's surface. */
  void operator()
  (
    bool & accept,
    double & x,
    double & y,
    double & z,
    doublec u,
    doublec v,
    doublec w 
  );

};

/*!
\brief Parallelogram in 3D space with unit coordinates. 
*/
class pointsurfaceParallelogram
{
public:

  /** The origin. */
  point3<double> p0;

  /** The arm of the u-axis. */
  point3<double> pu;
  /** The arm of the v-axis. */
  point3<double> pv;

  /** From the three points construct the parallelograms
      arms. */
  pointsurfaceParallelogram
  (
    point3<double> const & p0_,
    point3<double> const & p1,
    point3<double> const & p2
  )
    : p0(p0_), pu(p1-p0), pv(p2-p0) {}

  /** Given a point in a unit square map to a point
      on the parallelogram in 3D space. */
  void operator()
  (
    bool & accept,
    double & x,
    double & y,
    double & z,
    doublec u,
    doublec v
  ) const
  {
    accept = true;
    x = p0.x + pu.x*u + pv.x*v;
    y = p0.y + pu.y*u + pv.y*v;
    z = p0.z + pu.z*u + pv.z*v;
  }
};


#endif



