#include <assert.h>

#include "ray.h"
#include "jpalist.h"
#include "raycastable.h"
#include "cyclestate.h"
#include "globalstate.h"
#include "kdtree.h"
#include "intersect.h"
#include "common.h"
#ifdef DISABLE_LOG
#include "reconmap.h"
#endif
#include "ilog.h"

extern GlobalState *truly_GS;

#define DRAWOBJ if (drawing_flags & DRAW_WIREFRAME_OBJS) \
		  obj->Draw (RayCastable::WIREFRAME); \
		else \
		  obj->Draw (RayCastable::FILLED); 

//drawing_flags used: DRAW_OBJECTS, DRAW_BBOXES_MISSED, DRAW_OBJS_MISSED

static int DoAxisCheck (int signi, int i, const Vec4 &hitpt, const Bounds3d &bbox, const Stats &stats, RayCastable *obj);


//a huge wrapper around a simple loop that just calls obj->Intersect() for
//all the given objects, and keeps lots of stats.
//only pass in PlaneCache if you want viewvec T to be used.
void IntersectObjList (CycleState &CS, int x, int y, const Ray &ray, MPObjList *objlist, Hit &hit, PlaneCache *pc, int options, int drawing_flags, Stats &stats)
{
  //vars for culling objs that are past the hit point's screen-space Z-plane
  int did_check_t = 0;
  float bestt = MAXFLOAT;
  if (pc && hit.obj) bestt = pc->ComputeViewVecT (x,y,hit.t);
  
  //vars for stats
  RayCastable *oldobj = hit.obj;
  int oldnumint;
  ILOG (Int_ObjList_Start, new Int_ObjList_Start_Entry (ray, x, y, objlist));
  
  //vars for list traversal
  const void *place;
  ObjInfo *info;
  RayCastable *obj;
  
  //vars for axis check
  int was_axis_checked;
  int sign[3];
  Vec4 hitpt;
  if ((options & CHECK_1_AXIS) || (options & CHECK_3_AXIS))
    {
      if (hit.obj) Vec4FastAddScale (hitpt, ray.R(), ray.D(), hit.t);
      sign[X] = ray.Direction(X);
      sign[Y] = ray.Direction(Y);
      sign[Z] = ray.Direction(Z);
    }
  
  objlist->Reset (place);
  while (place)
    {
      info = objlist->Peek (place);
      objlist->Next (place);
      obj = info->_obj;
      assert (obj);
      
      if ( (!(truly_GS->_disabled_flags & DISABLE_DOUBLE_CHECK)) && obj->_ray_touched_id == ray.ID())
	{
	  ILOG (Int_Avoided, new Int_Avoided_Entry (obj, AlreadyChecked));
	  KDTree::_num_double_checks_avoided++;
	  continue;
	}

      if (obj->_sent_to_gl_frame_id == CS._frame_id)
	{
	  ILOG (Int_Avoided, new Int_Avoided_Entry (obj, SentToGL));
	  continue;
	}
      
      did_check_t = 0;
      //are we looking at farther away objects now?
      if (pc && hit.obj)
	{
	  did_check_t = 1;
	  if (info->_tnear > bestt)
	    {
	      ILOG (Int_Avoided, new Int_Avoided_Entry (obj, BboxTFartherThanHit));
	      break;
	    }
	}
      
#ifdef DISABLE_LOG
      if (truly_GS->_disabled_flags2 & ENABLE_THROW_BAD_AREA_RATIO_TO_GL)
	{
	  if (obj->ObjAreaToBboxArea() < .25)
	    {
	      obj->_sent_to_gl_frame_id = CS._frame_id;
	      CS._samplebuffer->AddObjForGL (obj, CS._frame_id);
	      ILOG (Int_Avoided, new Int_Avoided_Entry (obj, SentToGL));
	      continue;
	    }
	}
      
      if (truly_GS->_disabled_flags2 & ENABLE_THROW_BAD_HIT_RATIO_TO_GL)
	{
	  if ( (obj->_num_intersect_attempts > MIN_HITS_BEFORE_GLED) && (obj->_num_successful_intersects * BAD_HIT_RATIO < obj->_num_intersect_attempts) )
	    {
	      obj->_sent_to_gl_frame_id = CS._frame_id;
	      CS._samplebuffer->AddObjForGL (obj, CS._frame_id);
	      ILOG (Int_Avoided, new Int_Avoided_Entry (obj, SentToGL));
	      continue;
	    }
	  
	}
#endif      
      
      was_axis_checked = 0;
      if (hit.obj && (options & CHECK_1_AXIS))
	{
	  was_axis_checked = 1;
	  const Bounds3d &bbox = obj->bbox();
	  Vec4 dim = bbox[1]- bbox[0];
	  if (dim.y() < dim.x())
	    {
	      if (dim.y() < dim.z())
		{
		  
#define DOAXISCHECK(i) \
		  if (DoAxisCheck (sign[i], i, hitpt, bbox, stats, obj)) \
								   continue;
		  DOAXISCHECK (1);
		}
	      else
		{
		  DOAXISCHECK (2);
		}
	    }
	  else
	    {
	      if (dim.x() < dim.z())
		{
		  DOAXISCHECK (0);
		}
	      else
		{
		  DOAXISCHECK(2);
		}
	    }
	}
      else if (hit.obj && (options & CHECK_3_AXIS))
	{
	  was_axis_checked = 1;
	  int axiswon = 0;
	  const Bounds3d &bbox = obj->bbox();
	  for (int i = 0; i < 3; i ++)
	    {
	      if (sign[i] > 0)
		{
		  (*stats.num_axis_checks)++;
		  if (bbox[0][i] >= hitpt[i])
		    {
		      ILOG (Int_Avoided, new Int_Avoided_Entry (obj, BboxAxisFartherThanHit));
		      (*stats.num_axis_check_successes)++;
		      axiswon = 1;
		      break;
		    }
		}
	      else if (sign[i] < 0)
		{
		  (*stats.num_axis_checks)++;
		  if (bbox[1][i] <= hitpt[i])
		    {
		      ILOG (Int_Avoided, new Int_Avoided_Entry (obj, BboxAxisFartherThanHit));
		      (*stats.num_axis_check_successes)++;
		      axiswon = 1;
		      break;
		    }
		}
	    }
	  if (axiswon)
	    continue;
	}
      
      (*stats.num_intersect_calls)++;
      obj->_num_intersect_attempts++;
      
      if ( (obj->_num_intersect_attempts == 1) && (truly_GS->_disabled_flags2 & ENABLE_COLLECT_INTERSECT) )
	{
	  CS._objs_intersected.PlaceInList (obj);
	}
      
      oldnumint = hit.num_intersect_successes;
      int oldnumsuccess = hit.num_bbox_successes;
      
      //the actual intersect!
      if (options & BBOX_CHECK)
	obj->Intersect(ray, hit);
      else
	obj->IntersectProbable(ray, hit);
      
      obj->_ray_touched_id = ray.ID();
      //drawing, stats...
      if (drawing_flags & DRAW_OBJECTS)
	{
	  obj->bbox().drawWireFrame ();
	  glColor3fv (obj->GetRepresentativeColor().ArrayForGL());
	  DRAWOBJ;
	}

      if (oldnumsuccess != hit.num_bbox_successes)
      {
	if (drawing_flags & DRAW_BBOXES_MISSED)
	  {
	    glColor3fv (obj_color);
	    obj->bbox().drawWireFrame();
	  }
	(*stats.num_bbox_successes)++;
      }
      
      if (hit.obj != oldobj)
	{
	  if ((options & CHECK_3_AXIS) || (options & CHECK_1_AXIS))
	    {
	      Vec4FastAddScale (hitpt, ray.R(), ray.D(), hit.t);
	      if (oldobj) (*stats.num_axis_check_had_to_fail)++;
	    }
	}
      
      if (oldnumint != hit.num_intersect_successes)
	{
	  obj->_num_successful_intersects++;
	  (*stats.num_intersect_successes)++;
	  if (oldobj && (hit.obj != oldobj))
	    {
	      (*stats.num_intersections_overruled)++;
	      if (pc && hit.obj) bestt = pc->ComputeViewVecT (x,y,hit.t);
	    }
	}
      else
	{
	  if (drawing_flags & DRAW_OBJS_MISSED)
	    {
	      glColor3fv (obj_color);
	      obj->bbox().drawWireFrame ();
	      glColor3fv (obj->GetRepresentativeColor().ArrayForGL());
	      DRAWOBJ;
	    }
	}
      
      ILOG (Int_Obj, new Int_Obj_Entry (obj, options & BBOX_CHECK,  (oldnumsuccess == hit.num_bbox_successes),  (oldnumint != hit.num_intersect_successes), (oldobj != hit.obj), was_axis_checked, did_check_t));
      
      oldobj = hit.obj;
    }
  
  ILOG (Int_ObjList_End, new Int_ObjList_End_Entry (x, y, objlist));
}


int DoAxisCheck (int signi, int i, const Vec4 &hitpt, const Bounds3d &bbox, const Stats &stats, RayCastable *obj)
{
  if (signi > 0) 
    { 
      (*stats.num_axis_checks)++;  
      if (bbox[0][i] >= hitpt[i])
	{
	  ILOG (Int_Avoided, new Int_Avoided_Entry (obj, BboxAxisFartherThanHit));
	  (*stats.num_axis_check_successes)++;
	  return 1;
	}
    }
  else if (signi < 0)
    {
      (*stats.num_axis_checks)++;
      if (bbox[1][i] <= hitpt[i])
	{
	  ILOG (Int_Avoided, new Int_Avoided_Entry (obj, BboxAxisFartherThanHit)); 
	  (*stats.num_axis_check_successes)++;
	  return 1;
	}
    }
  return 0;
}
