/*

copyright 2006 by jon snell
free for all uses

*/
#define AAGD_LINE_PASSES_THROUGH_TOP 1
#define AAGD_LINE_PASSES_THROUGH_BOTTOM 2
#define AAGD_LINE_PASSES_THROUGH_LEFT 4
#define AAGD_LINE_PASSES_THROUGH_RIGHT 8

#include <php.h>
#include <gd.h>
#include <ext/gd/php_gd.h>

/* declaration of functions to be exported */
ZEND_FUNCTION(aa_filled_arc);


//ZEND_MSHUTDOWN(end_import);


/* compiled function list so Zend knows what's in this module */
zend_function_entry firstmod_functions[] =
{
    ZEND_FE(aa_filled_arc, NULL)  
    {NULL, NULL, NULL}
};

/* compiled module information */
zend_module_entry firstmod_module_entry =
{
  STANDARD_MODULE_HEADER,
  "First Module",
  firstmod_functions, /* function list */
  NULL, /* process startup */
  NULL, 
  NULL, 
  NULL, 
  NO_VERSION_YET,
  STANDARD_MODULE_PROPERTIES
};

/* implement standard "stub" routine to introduce ourselves to Zend */
#if COMPILE_DL
ZEND_GET_MODULE(firstmod)
#endif

     /* starts the file open */
double arc_area(double x1, double x2, double a, double b, double h) 
{
  double  rval;
  double a_sq, b_sq, x1_sq, x2_sq;
  double dist;

  if (x2 == x1)
    return 0;

  
  a /= (double) 2;
  b /= (double) 2;
  a_sq = pow(a,(double)2);
  b_sq = pow(b,(double)2);
  x2_sq = pow(x2,(double)2);
  x1_sq = pow(x1,(double) 2);


  rval = 0.5*a*b*(
			((x2/b)*(sqrt(b_sq - x2_sq))/b + asin (x2/b)) - 
			((x1/b)*(sqrt(b_sq - x1_sq))/b + asin (x1/b))
			);
  
  dist = x2 - x1;
  if (dist < 0) {
    dist *= -1.0;
  }
  
  rval -= dist * h;

  return rval; 
}


/* ellipse alpha function */

     /* starts the file open */
double ellipse_alpha(double fx, double fy, double h, double w)
{
  double w2, h2, shade;
  double y_val_at_right, y_val_at_left, x_val_at_top, x_val_at_bottom;
  char enter_through_left = 0;
  char enter_through_top = 0;
  char leave_through_right = 0;
  char leave_through_bottom = 0;

  w2 = w / 2;
  h2 = h / 2;

   if ((((fx)*(fx))/(w2*w2) + ((fy)*(fy))/(h2*h2))>1)
    {
      // entirely outside of the ellipse
      return 0;
    }

  y_val_at_left = sqrt(pow(h2,2)*(1-(pow(fx,2) / pow(w2,2))));
  y_val_at_right = sqrt(pow(h2,2)*(1-(pow((fx+1),2) / pow(w2,2))));
  if (zend_isnan(y_val_at_right))
    y_val_at_right = 0;
 
  if ((y_val_at_left >= fy) && (y_val_at_left <= (fy+1)))
    {
      enter_through_left = 1;
    }
  else
    {
      enter_through_top = 1;		 
      x_val_at_top =  sqrt(pow(w2,2)*(1-(pow(fy+1,2) / pow(h2,2))));		
    }
  if ((y_val_at_right > fy) && (y_val_at_right < (fy+1)))
    {
      leave_through_right = 1;
    }
  else
    {
      x_val_at_bottom =  sqrt(pow(w2,2)*(1 -(pow((fy),2) / pow(h2,2))));       
      leave_through_bottom = 1;
    }
  
  
  if (enter_through_left && leave_through_right)
    {
      shade = arc_area(fx,fx+1, h, w, fy);
    }
  else if (enter_through_left && leave_through_bottom)
    {
      shade = arc_area(fx,x_val_at_bottom, h, w, fy);
    }
  else if (enter_through_top && leave_through_bottom)
    {
      shade = arc_area(x_val_at_top,x_val_at_bottom, h, w, fy) + (( x_val_at_top- fx) * 1);
    }
  else if (enter_through_top && leave_through_right)
    {
      shade = arc_area(x_val_at_top,fx+1, h, w, fy) + ((x_val_at_top - fx) * 1);
    }	
  
  return shade;  
}


double pixel_angle(double oy, double ox) 
{
  double angle;

  angle = 180.0*atan2(ox,oy)/M_PI;
  if (angle < 0) {
    angle += 360;
  }
  angle = (angle+360);
  if (angle < 0) {
    angle += 360;
  }

  return fmod(angle, 360);
}

// assumes x/y is relative to the center pixel
char point_in_arc(double ox, double oy, double start_angle, double end_angle, double *how_close)
{
  double angle;
  char bounds_check;

  angle = pixel_angle(ox, oy);     
  
  if (start_angle > end_angle) {
    bounds_check = ((angle <= start_angle) && (angle >= (end_angle)));
    *how_close = MIN(fabs(angle -  start_angle), fabs(angle - end_angle));
    return  bounds_check;
  } else {
    bounds_check = ((angle <= end_angle) && (angle >= (start_angle)));
    *how_close = MIN(fabs(angle -  start_angle), fabs(angle - end_angle));
    return ! bounds_check;
  }
}

long alpha_alpha(gdImagePtr im, long color, int alpha)
{
  int old_alpha, r, g, b;
  long cur_color;
  old_alpha = (color & 0xFF000000) >> 24;	      
  r = (color & 0x00FF0000) >> 16;
  g = (color & 0x0000FF00) >> 8;
  b = (color & 0x000000FF);
  cur_color = gdImageColorResolveAlpha(im, r, g, b, 127.0 - ((127.0 - old_alpha)* (alpha/127.0)));
  return cur_color;
}

void pixel_arc_mask(gdImagePtr im, double cx, double cy, double ox, double oy, double start_angle, double end_angle, long color)
{
  double how_close;
  char pt_1_test;
  int corners;
  int sub_sample_points = 127;
  double step_size;
  double points_matched, steps_completed;
  double x_s, y_s;

  int pixel_shade = 0;
  if (start_angle != end_angle) {
    how_close = 0;
    pt_1_test = point_in_arc(oy, ox, start_angle, end_angle, &how_close);
    
    if (how_close > 5 && MIN(ox,oy) > 5) {
      
      pixel_shade = pt_1_test ? 127 : 0;
    }  else { 
      // now check all corners
      
      corners = ((pt_1_test ? 1 : 0) 
		  +(point_in_arc(oy-1.0, ox, start_angle, end_angle, &how_close) ? 1 : 0)
		  + (point_in_arc(oy, ox-1.0, start_angle, end_angle, &how_close)  ? 1 : 0)
		  +  (point_in_arc(oy-1.0, ox-1.0, start_angle, end_angle, &how_close) ? 1 : 0));
      if (corners == 4) {
	pixel_shade = 127;
      } else if (corners > 0 )	{	  
	pixel_shade = (double) corners / 4.0 * 127.0;
	if (pixel_shade != 127) {
	  step_size = 1 / floor(sqrt((double) sub_sample_points));
	  steps_completed = 0;
	  points_matched = 0;
	  for(x_s = ox; x_s >= ox-1.0; x_s-=step_size) {
	    for(y_s = oy; y_s >= oy-1.0; y_s-=step_size) {
	      if (point_in_arc(y_s, x_s, start_angle, end_angle, &how_close)) {
		points_matched++;
	      }		
	      steps_completed++;		
	    }
	  }
	  
	  pixel_shade = (points_matched / steps_completed) * 127.0;
	  //	    echo("Shade: pixel_shade <br>");
	  
	}
	
	
      }
    }
  }
  if (pixel_shade != 0) {
    color = alpha_alpha(im, color, pixel_shade);
    gdImageSetPixel(im, cx+ox, cy+oy, color);
		    //    ImageSetPixel(im, cx+ox, cy+oy, color);
  }
}

ZEND_FUNCTION(aa_filled_arc)
{
  gdImagePtr im;
  double cx, cy, w, h;
  double given_start_angle, given_end_angle;
  long color;
  //  int corners;

  double x_at_start, y_at_start, start_angle, y_at_end, x_at_end;

  double end_angle, w2, h2, fy, fx;

  double shade;

  long s_color, cur_color;

  zval *param;
  zval *IM;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                          "rddddddl", 
			    &IM, &cx, &cy, &w, &h, &given_start_angle, &given_end_angle, &color,
			    &param) == FAILURE) {
    return;
}

  ZEND_FETCH_RESOURCE(im, gdImagePtr, &IM, -1.0, "Image", phpi_get_le_gd());



  /* first convert the angles given into real angles
so we can easily mask them 
this is done by skewing them by the height and width
of the ellipse
   */
  x_at_start = h*sin(given_start_angle / 180.0 * M_PI);
  y_at_start = w*cos(given_start_angle / 180.0 * M_PI);
  start_angle = atan2(y_at_start, x_at_start) * 180.0 / M_PI;

  x_at_end = h*sin(given_end_angle / 180.0 * M_PI);
  y_at_end = w*cos(given_end_angle / 180.0 * M_PI);
  end_angle = atan2(y_at_end, x_at_end) * 180.0 / M_PI;

  /* normalize angles */
  if (start_angle < 0) {
    start_angle += 360.0;
  }
  if (end_angle < 0) {
    end_angle += 360.0;
  }
  start_angle = fmod(start_angle, 360.0);
  end_angle = fmod(end_angle, 360.0);

  w2 = w / 2;
  h2 = h / 2;  

 
  for(fy = 0; fy <= h2+1; fy++)
    {
      for(fx = 0; fx <= w2 + 1; fx++)
	{
	  shade = 0;
	  s_color = color;	  
	  
	  // is all of this pixel is within boundaries?
	  if ((((fx+1)*(fx+1))/(w2*w2) + ((fy+1)*(fy+1))/(h2*h2))<=1)  
	    {
	      // draw it at 100%
	      shade = 1;
	    }
	  else
	    {
	      // pixel is on the edge, so calculate the alpha
	      // now determine which way it's passing through so we know how to calculate area
	      // yval at left side
	      shade = ellipse_alpha(fx, fy, h, w);
	    }
	  if (shade > 0)
	    {
	      cur_color = alpha_alpha(im, s_color, shade * 127.0);
      
	      
	      if (fx > 0) {
		// when fx is 0, we only want to set the pixel once
		pixel_arc_mask(im, cx, cy, fx, fy, start_angle, end_angle, cur_color);
	      }
	      pixel_arc_mask(im, cx, cy, -1.0 * fx, fy, start_angle, end_angle, cur_color);	      
	      
	      if (fy > 0)
		{
		  if (fx > 0) {
		    pixel_arc_mask(im, cx, cy, fx, -1.0 * fy, start_angle, end_angle, cur_color);
		  }
		  pixel_arc_mask(im, cx, cy, -1.0 * fx, -1.0 * fy, start_angle, end_angle, cur_color);
		}
	      
	    }
	}
    }
}
