/*
 * shape.cpp: This match the classes inside System.Windows.Shapes
 *
 * Contact:
 *   Moonlight List (moonlight-list@lists.ximian.com)
 *
 * Copyright 2007-2008 Novell, Inc. (http://www.novell.com)
 *
 * See the LICENSE file included with the distribution for details.
 * 
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cairo.h>

#include <math.h>

#include "runtime.h"
#include "shape.h"
#include "brush.h"
#include "utils.h"

//
// SL-Cairo convertion and helper routines
//

#define EXACT_BOUNDS 1

static cairo_line_join_t
convert_line_join (PenLineJoin pen_line_join)
{
	switch (pen_line_join) {
	default:
		/* note: invalid values should be trapped in SetValue (see bug #340799) */
		g_warning ("Invalid value (%d) specified for PenLineJoin, using default.", pen_line_join);
		/* at this stage we use the default value (Miter) for Shape */
	case PenLineJoinMiter:
		return CAIRO_LINE_JOIN_MITER;
	case PenLineJoinBevel:
		return CAIRO_LINE_JOIN_BEVEL;
	case PenLineJoinRound:
		return CAIRO_LINE_JOIN_ROUND;
	}
}

/* NOTE: Triangle doesn't exist in Cairo - unless you patched it using https://bugzilla.novell.com/show_bug.cgi?id=345892 */
#ifndef HAVE_CAIRO_LINE_CAP_TRIANGLE
	#define CAIRO_LINE_CAP_TRIANGLE		 CAIRO_LINE_CAP_ROUND
#endif

static cairo_line_cap_t
convert_line_cap (PenLineCap pen_line_cap)
{
	switch (pen_line_cap) {
	default:
		/* note: invalid values should be trapped in SetValue (see bug #340799) */
		g_warning ("Invalid value (%d) specified for PenLineCap, using default.", pen_line_cap);
		/* at this stage we use the default value (Flat) for Shape */
	case PenLineCapFlat:
		return CAIRO_LINE_CAP_BUTT;
	case PenLineCapSquare:
		return CAIRO_LINE_CAP_SQUARE;
	case PenLineCapRound:
		return CAIRO_LINE_CAP_ROUND;
	case PenLineCapTriangle: 		
		return CAIRO_LINE_CAP_TRIANGLE;
	}
}

cairo_fill_rule_t
convert_fill_rule (FillRule fill_rule)
{
	switch (fill_rule) {
	default:
		/* note: invalid values should be trapped in SetValue (see bug #340799) */
		g_warning ("Invalid value (%d) specified for FillRule, using default.", fill_rule);
		/* at this stage we use the default value (EvenOdd) for Geometry */
	case FillRuleEvenOdd:
		return CAIRO_FILL_RULE_EVEN_ODD;
	case FillRuleNonzero:
		return CAIRO_FILL_RULE_WINDING;
	}
}

//
// Shape
//

Shape::Shape ()
{
	stroke = NULL;
	fill = NULL;
	path = NULL;
	cached_surface = NULL;
	SetShapeFlags (UIElement::SHAPE_NORMAL);
	cairo_matrix_init_identity (&stretch_transform);
}

Shape::~Shape ()
{
	// That also destroys the cached surface
	InvalidatePathCache (true);
}

bool
Shape::MixedHeightWidth (Value **height, Value **width)
{
	Value *vw = GetValueNoDefault (FrameworkElement::WidthProperty);
	Value *vh = GetValueNoDefault (FrameworkElement::HeightProperty);

	// nothing is drawn if only the width or only the height is specified
	if ((!vw && vh) || (vw && !vh)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return true;
	}

	if (width) *width = vw;
	if (height) *height = vh;
	return false;
}

void
Shape::Draw (cairo_t *cr)
{
	if (!path || (path->cairo.num_data == 0))
		BuildPath ();

	cairo_save (cr);
	cairo_transform (cr, &stretch_transform);

	cairo_new_path (cr);
	cairo_append_path (cr, &path->cairo);

	cairo_restore (cr);
}

// break up operations so we can exclude optional stuff, like:
// * StrokeStartLineCap & StrokeEndLineCap
// * StrokeLineJoin & StrokeMiterLimit
// * Fill

bool
Shape::SetupLine (cairo_t *cr)
{
	double thickness = GetStrokeThickness ();
	
	// check if something will be drawn or return 
	// note: override this method if cairo is used to compute bounds
	if (thickness == 0)
		return false;

	cairo_set_line_width (cr, thickness);

	return SetupDashes (cr, thickness);
}

bool
Shape::SetupDashes (cairo_t *cr, double thickness)
{
	return Shape::SetupDashes (cr, thickness, GetStrokeDashOffset () * thickness);
}

bool
Shape::SetupDashes (cairo_t *cr, double thickness, double offset)
{
	DoubleCollection *dashes = GetStrokeDashArray ();
	if (dashes && (dashes->GetCount() > 0)) {
		int count = dashes->GetCount();

		// NOTE: special case - if we continue cairo will stops drawing!
		if ((count == 1) && (dashes->GetValueAt(0)->AsDouble() == 0.0))
			return false;

		// multiply dashes length with thickness
		double *dmul = new double [count];
		for (int i=0; i < count; i++) {
			dmul [i] = dashes->GetValueAt(i)->AsDouble() * thickness;
		}

		cairo_set_dash (cr, dmul, count, offset);
		delete [] dmul;
	} else {
		cairo_set_dash (cr, NULL, 0, 0.0);
	}
	return true;
}

void
Shape::SetupLineCaps (cairo_t *cr)
{
	// Setting the cap to dash_cap. the endcaps (if different) are handled elsewhere
	PenLineCap cap = GetStrokeDashCap ();
	
	cairo_set_line_cap (cr, convert_line_cap (cap));
}

void
Shape::SetupLineJoinMiter (cairo_t *cr)
{
	PenLineJoin join = GetStrokeLineJoin ();
	double limit = GetStrokeMiterLimit ();
	
	cairo_set_line_join (cr, convert_line_join (join));
	cairo_set_miter_limit (cr, limit);
}

// returns true if the path is set on the cairo, false if not
bool
Shape::Fill (cairo_t *cr, bool do_op)
{
	if (!fill)
		return false;

	Draw (cr);
	if (do_op) {
		fill->SetupBrush (cr, extents);
		cairo_set_fill_rule (cr, convert_fill_rule (GetFillRule ()));
		cairo_fill_preserve (cr);
	}
	return true;
}

Rect
Shape::ComputeStretchBounds ()
{
	Value *vh, *vw;
	needs_clip = true;

	/*
	 * NOTE: this code is extremely fragile don't make a change here without
	 * checking the results of the test harness on with MOON_DRT_CATEGORIES=stretch
	 */

	if (Shape::MixedHeightWidth (&vh, &vw)) {
		return Rect ();
	}

	double w = vw ? vw->AsDouble () : 0.0;
	double h = vh ? vh->AsDouble () : 0.0;

	if ((h < 0.0) || (w < 0.0)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	if ((vh && (h <= 0.0)) || (vw && (w <= 0.0))) { 
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	Rect shape_bounds = ComputeShapeBounds (false, NULL);

	h = (h == 0.0) ? shape_bounds.height : h;
	w = (w == 0.0) ? shape_bounds.width : w;

	if (h <= 0.0 || w <= 0.0 || shape_bounds.width <= 0.0 || shape_bounds.height <= 0.0) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect();
	}

	Stretch stretch = GetStretch ();
	if (stretch != StretchNone) {
		Rect logical_bounds = ComputeShapeBounds (true, NULL);

		bool adj_x = logical_bounds.width != 0.0;
		bool adj_y = logical_bounds.height != 0.0;
             
		double diff_x = shape_bounds.width - logical_bounds.width;
		double diff_y = shape_bounds.height - logical_bounds.height;
		double sw = adj_x ? (w - diff_x) / logical_bounds.width : 1.0;
		double sh = adj_y ? (h - diff_y) / logical_bounds.height : 1.0;

		bool center = false;

		switch (stretch) {
		case StretchFill:
			needs_clip = false;
			center = true;
			break;
		case StretchUniform:
			needs_clip = false;
			sw = sh = (sw < sh) ? sw : sh;
			center = true;
			break;
		case StretchUniformToFill:
			sw = sh = (sw > sh) ? sw : sh;
			break;
		case StretchNone:
			/* not reached */
		break;
		}

		// trying to avoid the *VERY*SLOW* adjustments
		// e.g. apps like Silverlight World have a ratio up to 50 unneeded for 1 needed adjustment
		// so it all boilds down are we gonna change bounds anyway ?
		#define IS_SIGNIFICANT(dx,x)	(IS_ZERO(dx) && (fabs(dx) * x - x > 1.0))
		if ((adj_x && IS_SIGNIFICANT((sw - 1), shape_bounds.width)) || (adj_y && IS_SIGNIFICANT((sh - 1), shape_bounds.height))) {
			// FIXME: this IS still UBER slow
			// hereafter we're doing a second pass to refine the sw and sh we guessed
			// the first time. This usually gives pixel-recise stretches for Paths
			cairo_matrix_t temp;
			cairo_matrix_init_scale (&temp, adj_x ? sw : 1.0, adj_y ? sh : 1.0);
			Rect stretch_bounds = ComputeShapeBounds (false, &temp);
			if (stretch_bounds.width != shape_bounds.width && stretch_bounds.height != shape_bounds.height) {
				sw *= adj_x ? (w - stretch_bounds.width + logical_bounds.width * sw) / (logical_bounds.width * sw): 1.0;
				sh *= adj_y ? (h - stretch_bounds.height + logical_bounds.height * sh) / (logical_bounds.height * sh): 1.0;

				switch (stretch) {
				case StretchUniform:
					sw = sh = (sw < sh) ? sw : sh;
					break;
				case StretchUniformToFill:
					sw = sh = (sw > sh) ? sw : sh;
					break;
				default:
					break;
				}
			}
			// end of the 2nd pass code
		}

		double x = vh || adj_x ? shape_bounds.x : 0;
		double y = vw || adj_y ? shape_bounds.y : 0;
		if (center)
			cairo_matrix_translate (&stretch_transform, 
						adj_x ? w * 0.5 : 0, 
						adj_y ? h * 0.5 : 0);
		else //UniformToFill
			cairo_matrix_translate (&stretch_transform, 
						adj_x ? (logical_bounds.width * sw + diff_x) * .5 : 0,
						adj_y ? (logical_bounds.height * sh + diff_y) * .5: 0);
		
		cairo_matrix_scale (&stretch_transform, 
				    adj_x ? sw : 1.0, 
				    adj_y ? sh : 1.0);
		
		cairo_matrix_translate (&stretch_transform, 
					adj_x ? -shape_bounds.width * 0.5 : 0, 
					adj_y ? -shape_bounds.height * 0.5 : 0);

		if (!Is (Type::LINE) || (vh && vw))
			cairo_matrix_translate (&stretch_transform, -x, -y);
		
		// Double check our math
		cairo_matrix_t test = stretch_transform;
		if (cairo_matrix_invert (&test)) {
			g_warning ("Unable to compute stretch transform %f %f %f %f \n", sw, sh, shape_bounds.x, shape_bounds.y);
		}		
	}
	
	shape_bounds = shape_bounds.Transform (&stretch_transform);
	
	if (vh && vw) {
		Rect reduced_bounds = shape_bounds.Intersection (Rect (0, 0, vw->AsDouble (), vh->AsDouble ()));
		needs_clip = reduced_bounds != shape_bounds;
		needs_clip = needs_clip && stretch != StretchFill;
		needs_clip = needs_clip && stretch != StretchUniform;
	}
	
	return shape_bounds;
}

void
Shape::Stroke (cairo_t *cr, bool do_op)
{
	if (do_op) {
		stroke->SetupBrush (cr, extents);
		cairo_stroke (cr);
	}
}

void
Shape::Clip (cairo_t *cr)
{
	// some shapes, like Line, Polyline, Polygon and Path, are clipped if both Height and Width properties are present
	if (needs_clip) {
		Value *vh = GetValueNoDefault (FrameworkElement::HeightProperty);
		if (!vh)
			return;
		
		Value *vw = GetValueNoDefault (FrameworkElement::WidthProperty);
		if (!vw)
			return;

#if EXACT_CLIP
		cairo_rectangle (cr, 0, 0, vw->AsDouble (), vh->AsDouble ());
#else
		cairo_rectangle (cr, 0, 0, vw->AsDouble () > 1 ? vw->AsDouble () : 1, vh->AsDouble () > 1 ? vh->AsDouble() : 1);
#endif
		cairo_clip (cr);
		cairo_new_path (cr);
	}
}

//
// Returns TRUE if surface is a good candidate for caching.
// We accept a little bit of scaling.
//
bool
Shape::IsCandidateForCaching (void)
{
	if (IsEmpty ()) 
		return FALSE;

	if (! GetSurface ())
		return FALSE;

	// This is not 100% correct check -- the actual surface size might be
	// a tiny little bit larger. It's not a problem though if we go few
	// bytes above the cache limit.
	if (!GetSurface ()->VerifyWithCacheSizeCounter ((int) bounds.width, (int) bounds.height))
		return FALSE;

	// one last line of defense, lets not cache things 
	// much larger than the screen.
	if (bounds.width * bounds.height > 4000000)
		return FALSE;

	return TRUE;
}

//
// This routine is useful for Shape derivatives: it can be used
// to either get the bounding box from cairo, or to paint it
//
void
Shape::DoDraw (cairo_t *cr, bool do_op)
{
	bool ret = FALSE;

	// quick out if, when building the path, we detected an empty shape
	if (IsEmpty ())
		goto cleanpath;

	if (do_op && cached_surface == NULL && IsCandidateForCaching ()) {
		Rect cache_extents = bounds.RoundOut ();
		cairo_t *cached_cr = NULL;
		
		// g_warning ("bounds (%f, %f), extents (%f, %f), cache_extents (%f, %f)", 
		// bounds.width, bounds.height,
		// extents.width, extents.height,
		// cache_extents.width, cache_extents.height);
		
		cached_surface = image_brush_create_similar (cr, (int) cache_extents.width, (int) cache_extents.height);
		cairo_surface_set_device_offset (cached_surface, -cache_extents.x, -cache_extents.y);
		cached_cr = cairo_create (cached_surface);
		
		cairo_set_matrix (cached_cr, &absolute_xform);
		Clip (cached_cr);
		
		ret = DrawShape (cached_cr, do_op);
		
		cairo_destroy (cached_cr);
		
		// Increase our cache size
		cached_size = GetSurface ()->AddToCacheSizeCounter ((int) cache_extents.width, (int) cache_extents.height);
	}
	
	if (do_op && cached_surface) {
		cairo_pattern_t *cached_pattern = NULL;

		cached_pattern = cairo_pattern_create_for_surface (cached_surface);
		cairo_identity_matrix (cr);
		cairo_set_source (cr, cached_pattern);
		cairo_pattern_destroy (cached_pattern);
		cairo_paint (cr);
	} else {
		cairo_set_matrix (cr, &absolute_xform);
		if (do_op)
			Clip (cr);
		
		if (DrawShape (cr, do_op))
			return;
	}

cleanpath:
	if (do_op)
		cairo_new_path (cr);
}

void
Shape::Render (cairo_t *cr, int x, int y, int width, int height)
{
	cairo_save (cr);
	DoDraw (cr, true);
	cairo_restore (cr);
}

void
Shape::ShiftPosition (Point p)
{
	double dx = bounds.x - p.x;
	double dy = bounds.y - p.y;

	// FIXME this is much less than ideal but we must invalidate the surface cache
	// if the shift is not an integer otherwise we can potentially drow outside our
	// rounded out bounds.
       	if (cached_surface && (dx == trunc(dx)) && (dy == trunc(dy))) {
		cairo_surface_set_device_offset (cached_surface, trunc (-p.x), trunc (-p.y));
	} else {
		InvalidateSurfaceCache ();
	}

	FrameworkElement::ShiftPosition (p);
}

Size
Shape::MeasureOverride (Size availableSize)
{
	Size size = FrameworkElement::MeasureOverride (availableSize);

	if (GetStretch () != StretchNone)
		size = size.Min (0,0);

	return size;
}

Size
Shape::ArrangeOverride (Size availableSize)
{
	Size size =  FrameworkElement::ArrangeOverride (availableSize);
	
	// XXX hack to handle rectangle and ellipse specially until more is understood
	// at which point they can be their own overrides
	if (GetStretch () != StretchNone && !(Is (Type::RECTANGLE) || Is (Type::ELLIPSE)))
		size = size.Min (0,0);

	return size;
}

void
Shape::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
{
	InvalidateSurfaceCache ();
	bounds = IntersectBoundsWithClipPath (extents, false).Transform (current);
}

void
Shape::ComputeBounds ()
{
	cairo_matrix_init_identity (&stretch_transform);
	InvalidateSurfaceCache ();
	
	extents = ComputeStretchBounds ();
	bounds = IntersectBoundsWithClipPath (extents, false).Transform (&absolute_xform);
	//printf ("%f,%f,%f,%f\n", bounds.x, bounds.y, bounds.width, bounds.height);
}

Rect
Shape::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
{
	if (!path || (path->cairo.num_data == 0))
		BuildPath ();

	if (IsEmpty () || Shape::MixedHeightWidth (NULL, NULL))
		return Rect ();

	double thickness = (logical || !IsStroked ()) ? 0.0 : GetStrokeThickness ();
	
	cairo_t *cr = measuring_context_create ();
	if (matrix)
		cairo_set_matrix (cr, matrix);

	cairo_set_line_width (cr, thickness);

	if (thickness > 0.0) {
		//FIXME: still not 100% precise since it could be different from the end cap
		PenLineCap cap = GetStrokeStartLineCap ();
		if (cap == PenLineCapFlat)
			cap = GetStrokeEndLineCap ();
		cairo_set_line_cap (cr, convert_line_cap (cap));
	}

	cairo_append_path (cr, &path->cairo);
	
	cairo_identity_matrix (cr);

	double x1, y1, x2, y2;

	if (logical) {
		cairo_path_extents (cr, &x1, &y1, &x2, &y2);
	} else if (thickness > 0) {
		cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
	} else {
		cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
	}

	Rect bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));

	measuring_context_destroy (cr);

	return bounds;
}

Rect
Shape::ComputeLargestRectangleBounds ()
{
	Rect largest = ComputeLargestRectangle ();
	if (largest.IsEmpty ())
		return largest;

	return IntersectBoundsWithClipPath (largest, false).Transform (&absolute_xform);
}

Rect
Shape::ComputeLargestRectangle ()
{
	// by default the largest rectangle that fits into a shape is empty
	return Rect ();
}

void
Shape::GetSizeForBrush (cairo_t *cr, double *width, double *height)
{
	*height = extents.height;
	*width = extents.width;
}

bool
Shape::InsideObject (cairo_t *cr, double x, double y)
{
	bool ret = true;

	TransformPoint (&x, &y);
	if (!extents.PointInside (x, y))
		return false;


	cairo_save (cr);
	
	// cairo_in_* functions which we're using to check if point inside
	// the path don't take the clipping into account. Therefore, we need 
	// to do this in two steps: first check if the point is within 
	// the clipping bounds and later check if within the path itself.

	Geometry *clip = GetClip ();
	if (clip) {
		clip->Draw (cr);
		ret = cairo_in_fill (cr, x, y);
		cairo_new_path (cr);

		if (!ret) {
			cairo_restore (cr);
			return false;
		}
	}

	// don't do the operation but do consider filling
	DoDraw (cr, false);

	// don't check in_stroke without a stroke or in_fill without a fill (even if it can be filled)
	ret = ((fill && CanFill () && cairo_in_fill (cr, x, y)) || (stroke && cairo_in_stroke (cr, x, y)));
	cairo_new_path (cr);
	cairo_restore (cr);
		
	return ret;
}

void
Shape::CacheInvalidateHint (void)
{
	// Also kills the surface cache
	InvalidatePathCache ();
}

void
Shape::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::SHAPE) {
		if ((args->property == FrameworkElement::HeightProperty) || (args->property == FrameworkElement::WidthProperty))
			InvalidatePathCache ();

		if (args->property == UIElement::OpacityProperty) {
			if (IS_INVISIBLE (args->new_value->AsDouble ()))
				InvalidateSurfaceCache ();
		} else {
			if (args->property == UIElement::VisibilityProperty) {
				if (args->new_value->AsInt32() != VisibilityVisible)
					InvalidateSurfaceCache ();
			}
		}

		FrameworkElement::OnPropertyChanged (args);
		return;
	}

	if (args->property == Shape::StretchProperty) {
		InvalidatePathCache ();
		UpdateBounds (true);
	}
	else if (args->property == Shape::StrokeProperty) {
		Brush *new_stroke = args->new_value ? args->new_value->AsBrush () : NULL;

		if (!stroke || !new_stroke) {
			// If the stroke changes from null to
			// <something> or <something> to null, then
			// some shapes need to reclaculate the offset
			// (based on stroke thickness) to start
			// painting.
			InvalidatePathCache ();
			UpdateBounds ();
               } else
			InvalidateSurfaceCache ();
		

		stroke = new_stroke;
	} else if (args->property == Shape::FillProperty) {
		Brush *new_fill = args->new_value ? args->new_value->AsBrush () : NULL;

		if (!fill || !new_fill) {
			InvalidatePathCache ();
			UpdateBounds ();
		} else
			InvalidateSurfaceCache ();
			
		fill = args->new_value ? args->new_value->AsBrush() : NULL;
	} else if (args->property == Shape::StrokeThicknessProperty) {
		InvalidatePathCache ();
		UpdateBounds ();
	} else if (args->property == Shape::StrokeDashCapProperty
		   || args->property == Shape::StrokeEndLineCapProperty
		   || args->property == Shape::StrokeLineJoinProperty
		   || args->property == Shape::StrokeMiterLimitProperty
		   || args->property == Shape::StrokeStartLineCapProperty) {
		UpdateBounds ();
		InvalidatePathCache ();
	}
	
	Invalidate ();

	NotifyListenersOfPropertyChange (args);
}

void
Shape::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
{
	if (prop == Shape::FillProperty || prop == Shape::StrokeProperty) {
		Invalidate ();
		InvalidateSurfaceCache ();
	}
	else
		FrameworkElement::OnSubPropertyChanged (prop, obj, subobj_args);
}

Point
Shape::GetTransformOrigin ()
{
	Point *user_xform_origin = GetRenderTransformOrigin ();
	
	return Point (GetWidth () * user_xform_origin->x, 
		      GetHeight () * user_xform_origin->y);
}

void
Shape::InvalidatePathCache (bool free)
{
	SetShapeFlags (UIElement::SHAPE_NORMAL);
	if (path) {
		if (free) {
			moon_path_destroy (path);
			path = NULL;
		} else {
			moon_path_clear (path);
		}
	}

	InvalidateSurfaceCache ();
}

void
Shape::InvalidateSurfaceCache (void)
{
	if (cached_surface) {
		cairo_surface_destroy (cached_surface);
		if (GetSurface ())
			GetSurface ()->RemoveFromCacheSizeCounter (cached_size);
		cached_surface = NULL;
		cached_size = 0;
	}
}

//
// Ellipse
//

Ellipse::Ellipse ()
{
	SetStretch (StretchFill);
}

/*
 * Ellipses (like Rectangles) are special and they don't need to participate
 * in the other stretch logic
 */
Rect
Ellipse::ComputeStretchBounds ()
{
	Rect shape_bounds = ComputeShapeBounds (false);
	needs_clip = !IsDegenerate () && (GetStretch () == StretchUniformToFill);
	return shape_bounds;
}

Rect
Ellipse::ComputeShapeBounds (bool logical)
{
	Value *vh, *vw;
	if (Shape::MixedHeightWidth (&vh, &vw)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	double w = GetWidth ();
	double h = GetHeight ();
	if ((vh && (h <= 0.0)) || (vw && (w <= 0.0))) { 
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	double t = IsStroked () ? GetStrokeThickness () : 0.0;
	return Rect (0, 0, MAX (w, t), MAX (h, t));
}

// The Ellipse shape can be drawn while ignoring properties:
// * Shape::StrokeStartLineCap
// * Shape::StrokeEndLineCap
// * Shape::StrokeLineJoin
// * Shape::StrokeMiterLimit
bool
Ellipse::DrawShape (cairo_t *cr, bool do_op)
{
	bool drawn = Fill (cr, do_op);

	if (!stroke)
		return drawn;
	if (!SetupLine (cr))
		return drawn;
	SetupLineCaps (cr);

	if (!drawn)
		Draw (cr);
	Stroke (cr, do_op);
	return true; 
}

void
Ellipse::BuildPath ()
{
	Value *height, *width;
	
	if (Shape::MixedHeightWidth (&height, &width))
		return;

	Stretch stretch = GetStretch ();
	double t = IsStroked () ? GetStrokeThickness () : 0.0;
	Rect rect = Rect (0.0, 0.0, GetWidth (), GetHeight ());

	if (rect.width < 0.0 || rect.height < 0.0) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);		
		return;
	}

	switch (stretch) {
	case StretchNone:
		rect.width = rect.height = 0.0;
		break;
	case StretchUniform:
		rect.width = rect.height = (rect.width < rect.height) ? rect.width : rect.height;
		break;
	case StretchUniformToFill:
		rect.width = rect.height = (rect.width > rect.height) ? rect.width : rect.height;
		break;
	case StretchFill:
		/* nothing needed here.  the assignment of w/h above
		   is correct for this case. */
		break;
	}

	if (rect.width <= t || rect.height <= t){
		rect.width = MAX (rect.width, t + t * 0.001);
		rect.height = MAX (rect.height, t + t * 0.001);
		SetShapeFlags (UIElement::SHAPE_DEGENERATE);
	} else
		SetShapeFlags (UIElement::SHAPE_NORMAL);

	rect = rect.GrowBy ( -t/2, -t/2);

	path = moon_path_renew (path, MOON_PATH_ELLIPSE_LENGTH);
	moon_ellipse (path, rect.x, rect.y, rect.width, rect.height);
}

Rect
Ellipse::ComputeLargestRectangle ()
{
	double t = GetStrokeThickness ();
	double x = (GetWidth () - t) * cos (M_PI_2);
	double y = (GetHeight () - t) * sin (M_PI_2);
	return ComputeShapeBounds (false).GrowBy (-x, -y).RoundIn ();
}

void
Ellipse::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	DependencyProperty *prop = args->property;

	if ((prop == Shape::StrokeThicknessProperty) || (prop == Shape::StretchProperty) ||
		(prop == FrameworkElement::WidthProperty) || (prop == FrameworkElement::HeightProperty)) {
		BuildPath ();
		InvalidateSurfaceCache ();
	}

	// Ellipse has no property of it's own
	Shape::OnPropertyChanged (args);
}

//
// Rectangle
//

Rectangle::Rectangle ()
{
	SetStretch (StretchFill);
}

/*
 * Rectangles (like Ellipses) are special and they don't need to participate
 * in the other stretch logic
 */
Rect
Rectangle::ComputeStretchBounds ()
{
	Rect shape_bounds = ComputeShapeBounds (false);
	needs_clip = !IsDegenerate () && (GetStretch () == StretchUniformToFill);
	return shape_bounds;
}

Rect
Rectangle::ComputeShapeBounds (bool logical)
{
	Value *vh, *vw;
	if (Shape::MixedHeightWidth (&vh, &vw)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	Rect rect = Rect (0, 0, GetWidth (), GetHeight ());

	if ((vw && (rect.width <= 0.0)) || (vh && (rect.height <= 0.0))) { 
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return Rect ();
	}

	double t = IsStroked () ? GetStrokeThickness () : 0.0;
	switch (GetStretch ()) {
	case StretchNone:
		rect.width = rect.height = 0.0;
		break;
	case StretchUniform:
		rect.width = rect.height = MIN (rect.width, rect.height);
		break;
	case StretchUniformToFill:
		// this gets an rectangle larger than it's dimension, relative
		// scaling is ok but we need Shape::Draw to clip to it's original size
		rect.width = rect.height = MAX (rect.width, rect.height);
		break;
	case StretchFill:
		/* nothing needed here.  the assignment of w/h above
		   is correct for this case. */
		break;
	}
	
	if (rect.width == 0)
		rect.x = t *.5;
	if (rect.height == 0)
		rect.y = t *.5;

	if (t >= rect.width || t >= rect.height) {
		SetShapeFlags (UIElement::SHAPE_DEGENERATE);
		rect = rect.GrowBy (t * .5005, t * .5005);
	} else {
		SetShapeFlags (UIElement::SHAPE_NORMAL);
	}

	return rect;
}

Rect 
Rectangle::GetCoverageBounds ()
{
	Brush *fill = GetFill ();
	
	if (fill != NULL && fill->IsOpaque()) {
		/* make it a little easier - only consider the rectangle inside the corner radii.
		   we're also a little more conservative than we need to be, regarding stroke
		   thickness. */
		double xr = (GetRadiusX () + GetStrokeThickness () / 2);
		double yr = (GetRadiusY () + GetStrokeThickness () / 2);
		
		return bounds.GrowBy (-xr, -yr).RoundIn ();
	}
	
	return Rect ();
}


// The Rectangle shape can be drawn while ignoring properties:
// * Shape::StrokeStartLineCap
// * Shape::StrokeEndLineCap
// * Shape::StrokeLineJoin	[for rounded-corner rectangles only]
// * Shape::StrokeMiterLimit	[for rounded-corner rectangles only]
bool
Rectangle::DrawShape (cairo_t *cr, bool do_op)
{
	bool drawn = Fill (cr, do_op);

	if (!stroke)
		return drawn;

	if (!SetupLine (cr))
		return drawn;

	SetupLineCaps (cr);

	if (!HasRadii ())
		SetupLineJoinMiter (cr);

	// Draw if the path wasn't drawn by the Fill call
	if (!drawn)
		Draw (cr);
	Stroke (cr, do_op);
	return true; 
}

/*
 * rendering notes:
 * - a Width="0" or a Height="0" can be rendered differently from not specifying Width or Height
 * - if a rectangle has only a Width or only a Height it is NEVER rendered
 */
void
Rectangle::BuildPath ()
{
	Value *height, *width;
	if (Shape::MixedHeightWidth (&height, &width))
		return;

	Stretch stretch = GetStretch ();
	double t = IsStroked () ? GetStrokeThickness () : 0.0;
	
	// nothing is drawn (nor filled) if no StrokeThickness="0"
	// unless both Width and Height are specified or when no streching is required
	double radius_x = 0.0, radius_y = 0.0;
	Rect rect = Rect (0, 0, GetWidth (), GetHeight ());
	GetRadius (&radius_x, &radius_y);

	switch (stretch) {
	case StretchNone:
		rect.width = rect.height = 0;
		break;
	case StretchUniform:
		rect.width = rect.height = MIN (rect.width, rect.height);
		break;
	case StretchUniformToFill:
		// this gets an rectangle larger than it's dimension, relative
		// scaling is ok but we need Shape::Draw to clip to it's original size
		rect.width = rect.height = MAX (rect.width, rect.height);
		break;
	case StretchFill:
		/* nothing needed here.  the assignment of w/h above
		   is correct for this case. */
		break;
	}
	
	if (rect.width == 0)
		rect.x = t *.5;
	if (rect.height == 0)
		rect.y = t *.5;

	if (t >= rect.width || t >= rect.height) {
		rect = rect.GrowBy (t * 0.001, t * 0.001);
		SetShapeFlags (UIElement::SHAPE_DEGENERATE);
	} else {
		rect = rect.GrowBy (-t * 0.5, -t * 0.5);
		SetShapeFlags (UIElement::SHAPE_NORMAL);
	}

	path = moon_path_renew (path, MOON_PATH_ROUNDED_RECTANGLE_LENGTH);
	moon_rounded_rectangle (path, rect.x, rect.y, rect.width, rect.height, radius_x, radius_y);
}

void
Rectangle::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::RECTANGLE) {
		Shape::OnPropertyChanged (args);
		return;
	}

	if ((args->property == Rectangle::RadiusXProperty) || (args->property == Rectangle::RadiusYProperty)) {
		InvalidatePathCache ();
		// note: changing the X and/or Y radius doesn't affect the bounds
	}

	Invalidate ();
	NotifyListenersOfPropertyChange (args);
}

bool
Rectangle::GetRadius (double *rx, double *ry)
{
	Value *value = GetValueNoDefault (Rectangle::RadiusXProperty);
	if (!value)
		return false;
	*rx = value->AsDouble ();

	value = GetValueNoDefault (Rectangle::RadiusYProperty);
	if (!value)
		return false;
	*ry = value->AsDouble ();

	return ((*rx != 0.0) && (*ry != 0.0));
}

Rect
Rectangle::ComputeLargestRectangle ()
{
	double x = GetStrokeThickness ();
	double y = x;
	if (HasRadii ()) {
		x += GetRadiusX ();
		y += GetRadiusY ();
	}
	return ComputeShapeBounds (false).GrowBy (-x, -y).RoundIn ();
}

//
// Line
//
// rules
// * the internal path must be rebuilt when
//	- Line::X1Property, Line::Y1Property, Line::X2Property or Line::Y2Property is changed
//
// * bounds calculation is based on
//	- Line::X1Property, Line::Y1Property, Line::X2Property and Line::Y2Property
//	- Shape::StrokeThickness
//

#define LINECAP_SMALL_OFFSET	0.1

//Draw the start cap. Shared with Polyline
static void
line_draw_cap (cairo_t *cr, Shape* shape, PenLineCap cap, double x1, double y1, double x2, double y2)
{
	double sx1, sy1;
	if (cap == PenLineCapFlat)
		return;

	if (cap == PenLineCapRound) {
		cairo_set_line_cap (cr, convert_line_cap (cap));
		cairo_move_to (cr, x1, y1);
		cairo_line_to (cr, x1, y1);
		shape->Stroke (cr, true);
		return;
	}

	if (x1 == x2) {
		// vertical line
		sx1 = x1;
		if (y1 > y2)
			sy1 = y1 + LINECAP_SMALL_OFFSET;
		else
			sy1 = y1 - LINECAP_SMALL_OFFSET;
	} else if (y1 == y2) {
		// horizontal line
		sy1 = y1;
		if (x1 > x2)
			sx1 = x1 + LINECAP_SMALL_OFFSET;
		else
			sx1 = x1 - LINECAP_SMALL_OFFSET;
	} else {
		double m = (y1 - y2) / (x1 - x2);
		if (x1 > x2) {
			sx1 = x1 + LINECAP_SMALL_OFFSET;
		} else {
			sx1 = x1 - LINECAP_SMALL_OFFSET;
		}
		sy1 = m * sx1 + y1 - (m * x1);
	}
	cairo_set_line_cap (cr, convert_line_cap (cap));
	cairo_move_to (cr, x1, y1);
	cairo_line_to (cr, sx1, sy1);
	shape->Stroke (cr, true);
}

// The Line shape can be drawn while ignoring properties:
// * Shape::StrokeLineJoin
// * Shape::StrokeMiterLimit
// * Shape::Fill
bool
Line::DrawShape (cairo_t *cr, bool do_op)
{
	// no need to clear path since none has been drawn to cairo
	if (!stroke)
		return false; 

	if (!SetupLine (cr))
		return false;

	// here we hack around #345888 where Cairo doesn't support different start and end linecaps
	PenLineCap start = GetStrokeStartLineCap ();
	PenLineCap end = GetStrokeEndLineCap ();
	PenLineCap dash = GetStrokeDashCap ();
	bool dashed = false;
	DoubleCollection *dashes = GetStrokeDashArray ();
	
	if (dashes && (dashes->GetCount() > 0))
		dashed = true;

	//if (do_op && !(start == end && start == dash)) {
	if (do_op && (start != end || (dashed && !(start == end && start == dash)))) {
		double x1 = GetX1 ();
		double y1 = GetY1 ();
		double x2 = GetX2 ();
		double y2 = GetY2 ();
		
		// draw start and end line caps
		if (start != PenLineCapFlat) 
			line_draw_cap (cr, this, start, x1, y1, x2, y2);
		
		if (end != PenLineCapFlat) {
			//don't draw the end cap if it's in an "off" segment
			double thickness = GetStrokeThickness ();
			double offset = GetStrokeDashOffset ();
			
			SetupDashes (cr, thickness, sqrt ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) + offset * thickness);
			line_draw_cap (cr, this, end, x2, y2, x1, y1);
			SetupLine (cr);
		}

		cairo_set_line_cap (cr, convert_line_cap (dash));
	} else 
		cairo_set_line_cap (cr, convert_line_cap (start));

	Draw (cr);
	Stroke (cr, do_op);
	return true;
}

void
calc_line_bounds (double x1, double x2, double y1, double y2, double thickness, PenLineCap start_cap, PenLineCap end_cap, Rect* bounds)
{
	if (x1 == x2) {
		bounds->x = x1 - thickness / 2.0;
		bounds->y = MIN (y1, y2) - (y1 < y2 && start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) - (y1 >= y2 && end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
		bounds->width = thickness;
		bounds->height = fabs (y2 - y1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
	} else 	if (y1 == y2) {
		bounds->x = MIN (x1, x2) - (x1 < x2 && start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) - (x1 >= x2 && end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
		bounds->y = y1 - thickness / 2.0;
		bounds->width = fabs (x2 - x1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
		bounds->height = thickness;
	} else {
		double m = fabs ((y1 - y2) / (x1 - x2));
#if EXACT_BOUNDS
		double dx = sin (atan (m)) * thickness;
		double dy = cos (atan (m)) * thickness;
#else
		double dx = (m > 1.0) ? thickness : thickness * m;
		double dy = (m < 1.0) ? thickness : thickness / m;
#endif
		if (x1 < x2)
			switch (start_cap) {
			case PenLineCapSquare:
				bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
				break;
			case PenLineCapTriangle: //FIXME, reverting to Round for now
			case PenLineCapRound:
				bounds->x = MIN (x1, x2) - thickness / 2.0;
				break;
			default: //PenLineCapFlat
				bounds->x = MIN (x1, x2) - dx / 2.0;
			}	
		else 
			switch (end_cap) {
			case PenLineCapSquare:
				bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
				break;
			case PenLineCapTriangle: //FIXME, reverting to Round for now
			case PenLineCapRound:
				bounds->x = MIN (x1, x2) - thickness / 2.0;
				break;
			default: //PenLineCapFlat
				bounds->x = MIN (x1, x2) - dx / 2.0;
			}		
		if (y1 < y2)
			switch (start_cap) {
			case PenLineCapSquare:
				bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
				break;
			case PenLineCapTriangle: //FIXME, reverting to Round for now
			case PenLineCapRound:
				bounds->y = MIN (y1, y2) - thickness / 2.0;
				break;
			default: //PenLineCapFlat
				bounds->y = MIN (y1, y2) - dy / 2.0;
			}	
		else
			switch (end_cap) {
			case PenLineCapSquare:
				bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
				break;
			case PenLineCapTriangle: //FIXME, reverting to Round for now
			case PenLineCapRound:
				bounds->y = MIN (y1, y2) - thickness / 2.0;
				break;
			default: //PenLineCapFlat
				bounds->y = MIN (y1, y2) - dy / 2.0;
			}	
		bounds->width = fabs (x2 - x1);
		bounds->height = fabs (y2 - y1);
		switch (start_cap) {
		case PenLineCapSquare:
			bounds->width += (dx + dy) / 2.0;
			bounds->height += (dx + dy) / 2.0;
			break;
		case PenLineCapTriangle: //FIXME, reverting to Round for now
		case PenLineCapRound:
			bounds->width += thickness / 2.0;
			bounds->height += thickness / 2.0;
			break;
		default: //PenLineCapFlat
			bounds->width += dx/2.0;
			bounds->height += dy/2.0;
		}
		switch (end_cap) {
		case PenLineCapSquare:
			bounds->width += (dx + dy) / 2.0;
			bounds->height += (dx + dy) / 2.0;
			break;
		case PenLineCapTriangle: //FIXME, reverting to Round for now
		case PenLineCapRound:
			bounds->width += thickness / 2.0;
			bounds->height += thickness / 2.0;
			break;
		default: //PenLineCapFlat
			bounds->width += dx/2.0;
			bounds->height += dy/2.0;	
		}
	}
}

void
Line::BuildPath ()
{
	if (Shape::MixedHeightWidth (NULL, NULL))
		return;

	SetShapeFlags (UIElement::SHAPE_NORMAL);

	path = moon_path_renew (path, MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH);
	
	double x1 = GetX1 ();
	double y1 = GetY1 ();
	double x2 = GetX2 ();
	double y2 = GetY2 ();
	
	moon_move_to (path, x1, y1);
	moon_line_to (path, x2, y2);
}

Rect
Line::ComputeShapeBounds (bool logical)
{
	Rect shape_bounds = Rect ();

	if (Shape::MixedHeightWidth (NULL, NULL))
		return shape_bounds;

	double thickness;
	if (!logical)
		thickness = GetStrokeThickness ();
	else
		thickness = 0.0;

	PenLineCap start_cap, end_cap;
	if (!logical) {
		start_cap = GetStrokeStartLineCap ();
		end_cap = GetStrokeEndLineCap ();
	} else 
		start_cap = end_cap = PenLineCapFlat;
	
	if (thickness <= 0.0 && !logical)
		return shape_bounds;
	
	double x1 = GetX1 ();
	double y1 = GetY1 ();
	double x2 = GetX2 ();
	double y2 = GetY2 ();
	
	calc_line_bounds (x1, x2, y1, y2, thickness, start_cap, end_cap, &shape_bounds);

	return shape_bounds;
}

void
Line::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::LINE) {
		Shape::OnPropertyChanged (args);
		return;
	}

	if (args->property == Line::X1Property ||
	    args->property == Line::X2Property ||
	    args->property == Line::Y1Property ||
	    args->property == Line::Y2Property) {
		InvalidatePathCache ();
		UpdateBounds (true);
	}

	NotifyListenersOfPropertyChange (args);
}

//
// Polygon
//
// rules
// * the internal path must be rebuilt when
//	- Polygon::PointsProperty is changed
//	- Shape::StretchProperty is changed
//
// * bounds calculation is based on
//	- Polygon::PointsProperty
//	- Shape::StretchProperty
//	- Shape::StrokeThickness
//


// The Polygon shape can be drawn while ignoring properties:
// * Shape::StrokeStartLineCap
// * Shape::StrokeEndLineCap
bool
Polygon::DrawShape (cairo_t *cr, bool do_op)
{
	bool drawn = Fill (cr, do_op);

	if (!stroke)
		return drawn; 

	if (!SetupLine (cr))
		return drawn;
	SetupLineCaps (cr);
	SetupLineJoinMiter (cr);

	Draw (cr);
	Stroke (cr, do_op);
	return true;
}

// special case when a polygon has a single line in it (it's drawn longer than it should)
// e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8" Points="260,80 300,40" />
static void
polygon_extend_line (double *x1, double *x2, double *y1, double *y2, double thickness)
{
	// not sure why it's a 5 ? afaik it's not related to the line length or any other property
	double t5 = thickness * 5.0;
	double dx = *x1 - *x2;
	double dy = *y1 - *y2;

	if (dy == 0.0) {
		t5 -= thickness / 2.0;
		if (dx > 0.0) {
			*x1 += t5;
			*x2 -= t5;
		} else {
			*x1 -= t5;
			*x2 += t5;
		}
	} else if (dx == 0.0) {
		t5 -= thickness / 2.0;
		if (dy > 0.0) {
			*y1 += t5;
			*y2 -= t5;
		} else {
			*y1 -= t5;
			*y2 += t5;
		}
	} else {
		double angle = atan (dy / dx);
		double ax = fabs (sin (angle) * t5);
		if (dx > 0.0) {
			*x1 += ax;
			*x2 -= ax;
		} else {
			*x1 -= ax;
			*x2 += ax;
		}
		double ay = fabs (sin ((M_PI / 2.0) - angle)) * t5;
		if (dy > 0.0) {
			*y1 += ay;
			*y2 -= ay;
		} else {
			*y1 -= ay;
			*y2 += ay;
		}
	}
}

void
Polygon::BuildPath ()
{
	if (Shape::MixedHeightWidth (NULL, NULL))
		return;

	PointCollection *col = GetPoints ();
	
	// the first point is a move to, resulting in an empty shape
	if (!col || (col->GetCount() < 2)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return;
	}

	int i, count = col->GetCount();
	GPtrArray* points = col->Array();

	SetShapeFlags (UIElement::SHAPE_NORMAL);

	// 2 data per [move|line]_to + 1 for close path
	path = moon_path_renew (path, count * 2 + 1);

	// special case, both the starting and ending points are 5 * thickness than the actual points
	if (count == 2) {
		double thickness = GetStrokeThickness ();
		double x1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x;
		double y1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y;
		double x2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->x;
		double y2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->y;
		
		polygon_extend_line (&x1, &x2, &y1, &y2, thickness);

		moon_move_to (path, x1, y1);
		moon_line_to (path, x2, y2);
	} else {
		moon_move_to (path,
			      ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
			      ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);
		for (i = 1; i < count; i++)
			moon_line_to (path,
				      ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
				      ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
	}
	moon_close_path (path);
}

void
Polygon::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::POLYGON) {
		Shape::OnPropertyChanged (args);
		return;
	}

	if (args->property == Polygon::PointsProperty) {
		InvalidatePathCache ();
		UpdateBounds (true /* force one here, even if the bounds don't change */);
	}

	Invalidate ();
	NotifyListenersOfPropertyChange (args);
}

void
Polygon::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
{
	if (col != GetPoints()) {
		Shape::OnCollectionChanged (col, args);
		return;
	}
	
	InvalidatePathCache ();
	UpdateBounds (true);
	Invalidate ();
}

void
Polygon::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
{
	Shape::OnCollectionItemChanged (col, obj, args);
	
	UpdateBounds (true);
	Invalidate ();
}

//
// Polyline
//
// rules
// * the internal path must be rebuilt when
//	- Polyline::PointsProperty is changed
//	- Shape::StretchProperty is changed
//
// * bounds calculation is based on
//	- Polyline::PointsProperty
//	- Shape::StretchProperty
//	- Shape::StrokeThickness

// The Polyline shape can be drawn while ignoring NO properties
bool
Polyline::DrawShape (cairo_t *cr, bool do_op)
{
	bool drawn = Fill (cr, do_op);

	if (!stroke)
		return drawn; 

	if (!SetupLine (cr))
		return drawn;
	SetupLineJoinMiter (cr);

	// here we hack around #345888 where Cairo doesn't support different start and end linecaps
	PenLineCap start = GetStrokeStartLineCap ();
	PenLineCap end = GetStrokeEndLineCap ();
	PenLineCap dash = GetStrokeDashCap ();
	
	if (do_op && ! (start == end && start == dash)){
		// the previous fill, if needed, has preserved the path
		if (drawn)
			cairo_new_path (cr);

		// since Draw may not have been called (e.g. no Fill) we must ensure the path was built
		if (!drawn || !path || (path->cairo.num_data == 0))
			BuildPath ();

		cairo_path_data_t *data = path->cairo.data;
		int length = path->cairo.num_data;
		// single point polylines are not rendered
		if (length >= MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH) {
			// draw line #1 with start cap
			if (start != PenLineCapFlat) {
				line_draw_cap (cr, this, start, data[1].point.x, data[1].point.y, data[3].point.x, data[3].point.y);
			}
			// draw last line with end cap
			if (end != PenLineCapFlat) {
				line_draw_cap (cr, this, end, data[length-1].point.x, data[length-1].point.y, data[length-3].point.x, data[length-3].point.y);
			}
		}
	}
	cairo_set_line_cap (cr, convert_line_cap (dash));

	Draw (cr);
	Stroke (cr, do_op);
	return true;
}

void
Polyline::BuildPath ()
{
	if (Shape::MixedHeightWidth (NULL, NULL))
		return;

	PointCollection *col = GetPoints ();
	
	// the first point is a move to, resulting in an empty shape
	if (!col || (col->GetCount() < 2)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return;
	}

	int i, count = col->GetCount();
	GPtrArray *points = col->Array();

	SetShapeFlags (UIElement::SHAPE_NORMAL);

	// 2 data per [move|line]_to
	path = moon_path_renew (path, count * 2);

	moon_move_to (path,
		      ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
		      ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);

	for (i = 1; i < count; i++)
		moon_line_to (path,
			      ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
			      ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
}

void
Polyline::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::POLYLINE) {
		Shape::OnPropertyChanged (args);
		return;
	}

	if (args->property == Polyline::PointsProperty) {
		InvalidatePathCache ();
		UpdateBounds (true /* force one here, even if the bounds don't change */);
	}

	Invalidate ();
	NotifyListenersOfPropertyChange (args);
}

void
Polyline::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
{
	if (col != GetPoints ()) {
		Shape::OnCollectionChanged (col, args);
		return;
	}
	
	InvalidatePathCache ();
	UpdateBounds (true);
	Invalidate ();
}

void
Polyline::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
{
	Shape::OnCollectionItemChanged (col, obj, args);
	
	UpdateBounds (true);
	Invalidate ();
}

//
// Path
//

bool
Path::SetupLine (cairo_t* cr)
{
	// we cannot use the thickness==0 optimization (like Shape::SetupLine provides)
	// since we'll be using cairo to compute the path's bounds later
	// see bug #352188 for an example of what this breaks
	double thickness = IsDegenerate () ? 1.0 : GetStrokeThickness ();
	cairo_set_line_width (cr, thickness);
	return SetupDashes (cr, thickness);
}

// The Polygon shape can be drawn while ignoring properties:
// * none 
// FIXME: actually it depends on the geometry, another level of optimization awaits ;-)
// e.g. close geometries don't need to setup line caps, 
//	line join/miter	don't applies to curve, like EllipseGeometry
bool
Path::DrawShape (cairo_t *cr, bool do_op)
{
	bool drawn = Shape::Fill (cr, do_op);

	if (stroke) {
		if (!SetupLine (cr))
			return drawn;	// return if we have a path in the cairo_t
		SetupLineCaps (cr);
		SetupLineJoinMiter (cr);

		if (!drawn)
			Draw (cr);
		Stroke (cr, do_op);
	}

	return true;
}

FillRule
Path::GetFillRule ()
{
	Geometry *geometry;
	
	if (!(geometry = GetData ()))
		return Shape::GetFillRule ();
	
	return geometry->GetFillRule ();
}

Rect
Path::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
{
	Rect shape_bounds = Rect ();
	
	Value *vh, *vw;
	if (Shape::MixedHeightWidth (&vh, &vw))
		return shape_bounds;
	
	Geometry *geometry;
	
	if (!(geometry = GetData ())) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return shape_bounds;
	}
	
	double w = vw ? vw->AsDouble () : 0.0;
	double h = vh ? vh->AsDouble () : 0.0;

	if ((h < 0.0) || (w < 0.0)) {
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return shape_bounds;
	}

	if ((vh && (h <= 0.0)) || (vw && (w <= 0.0))) { 
		SetShapeFlags (UIElement::SHAPE_EMPTY);
		return shape_bounds;
	}

	if (logical)
		return geometry->GetBounds ();
		
	double thickness = !IsStroked () ? 0.0 : GetStrokeThickness ();
	
	cairo_t *cr = measuring_context_create ();
	cairo_set_line_width (cr, thickness);

	if (thickness > 0.0) {
		//FIXME: still not 100% precise since it could be different from the end cap
		PenLineCap cap = GetStrokeStartLineCap ();
		if (cap == PenLineCapFlat)
			cap = GetStrokeEndLineCap ();
		cairo_set_line_cap (cr, convert_line_cap (cap));
	}

	if (matrix)
		cairo_set_matrix (cr, matrix);
	geometry->Draw (cr);

	cairo_identity_matrix (cr);

	double x1, y1, x2, y2;

	if (thickness > 0) {
		cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
	} else {
		cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
	}

        shape_bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));
	
	measuring_context_destroy (cr);

	return shape_bounds;
}

void
Path::Draw (cairo_t *cr)
{
	cairo_new_path (cr);
	
	Geometry *geometry;
	
	if (!(geometry = GetData ()))
		return;
	
	cairo_save (cr);
	cairo_transform (cr, &stretch_transform);
	geometry->Draw (cr);
	cairo_restore (cr);
}

void
Path::OnPropertyChanged (PropertyChangedEventArgs *args)
{
	if (args->property->GetOwnerType() != Type::PATH) {
		Shape::OnPropertyChanged (args);
		return;
	}

	InvalidatePathCache ();
	FullInvalidate (false);

	NotifyListenersOfPropertyChange (args);
}

void
Path::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
{
	if (prop == Path::DataProperty) {
		InvalidatePathCache ();
		FullInvalidate (false);
	}
	else
		Shape::OnSubPropertyChanged (prop, obj, subobj_args);
}

/*
 * Right now implementing Path::ComputeLargestRectangle doesn't seems like a good idea. That would require
 * - checking the path for curves (and either flatten it or return an empty Rect)
 * - checking for polygon simplicity (finding intersections)
 * - checking for a convex polygon (if concave we can turn it into several convex or return an empty Rect)
 * - find the largest rectangle inside the (or each) convex polygon(s)
 * 	http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/complete.html
 */
