package SVG;

PolyRender::boot_PolyRender();

use strict;
use warnings;

use XML::Parser;
use XML::Simple;
use Data::Dumper; # debug

sub new {
	my $self = {};
	
	bless $self;
	
	return $self;
}

sub parsergb($) {
	my ($s) = @_;
	my %rgb;
	
	if($s =~ /rgb\((\d+),(\d+),(\d+)\)/) {
		$rgb{"r"} = $1/255;
		$rgb{"g"} = $2/255;
		$rgb{"b"} = $3/255;
	} else {
		$rgb{"r"} = 0;
		$rgb{"g"} = 0;
		$rgb{"b"} = 0;
	}
	
	return \%rgb;	
}

sub getrecursivegfx {
	my ($xml, $listref) = @_;
	my $node;

	if(defined($xml->{"path"})) {
		foreach $node (@{$xml->{"path"}}) {
			push @{$listref}, $node;
		}
	}
	if(defined($xml->{"polygon"})) {
		foreach $node (@{$xml->{"polygon"}}) {
			push @{$listref}, $node;
		}
	}
	if(defined($xml->{"rect"})) {
		foreach $node (@{$xml->{"rect"}}) {
			push @{$listref}, $node;
		}
	}
	if(defined($xml->{"g"})) {
		foreach $node (@{$xml->{"g"}}) {
			getrecursivegfx($node, $listref);
		}
	}
}

# returns RGB hash for RGBHex values ("#FFDDEE")
sub parsergbhex($) {
	my ($s) = @_;
	my %rgb;
	
	if($s eq "none") {
		return { "r" => 0, "g" => 0, "b" => 0};
	}
	
	# clip '#'
	if(substr($s,0,1) eq "#") {
		$s = substr($s,1);
	}
	
	$rgb{"r"} = unpack("C",pack("H2",substr($s,0,2)))/255;
	$rgb{"g"} = unpack("C",pack("H2",substr($s,2,2)))/255;
	$rgb{"b"} = unpack("C",pack("H2",substr($s,4,2)))/255;
	
	# print "$s r g b " . $rgb{"r"} . " " . $rgb{"g"} . " " . $rgb{"b"} . "\n";
	
	return \%rgb;
}	

# returns length in pixels if line (x1,y1)-(x2,y2)
sub linelen($$$$) {
	my ($x1, $y1, $x2, $y2) = @_;

	return sqrt(($x2-$x1)**2 + ($y2-$y1)**2);
}

# returns a point at factor $n in line (x1,y1)-(x2,y2)
sub linepoint($$$$$) {
	my ($n, $x1, $y1, $x2, $y2) = @_;

	return (($x2 - $x1) * $n + $x1, ($y2 - $y1) * $n + $y1);
}

# returns the (x,y) coordinate of quadratic (x1,y2)(x2,y2)(x3,y3) at point n
sub quadraticpoint($$$$$$$) {
	my ($n, $x1, $y1, $x2, $y2, $x3, $y3) = @_;

	# Get point at $n on line (x1,y1)-(x2,y2)
	my($p1x, $p1y) = linepoint($n, $x1, $y1, $x2, $y2);

	# Get point at $n on line (x2,y2)-(x3,y3);
	my($p2x, $p2y) = linepoint($n, $x2, $y2, $x3, $y3);

	# Get point at $n on line (p1x, p1y)-(p2x, p2y);
	return linepoint($n, $p1x, $p1y, $p2x, $p2y);
}

# returns the (x,y) coordinate of cubic point (x1,y1)(x2,y2)(x3,y3)(x4,x4) (P2, P3 are curve coordinates) at point n
sub cubicpoint($$$$$$$$$) {
	my ($n, $x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4) = @_;

	my ($x, $y);

	$x = $x1 * ((1-$n) ** 3) +     3* $x2 * $n * ((1-$n) ** 2) +     3*$x3 * ($n ** 2) * (1-$n) +     $x4 * ($n ** 3);
	$y = $y1 * ((1-$n) ** 3) +     3* $y2 * $n * ((1-$n) ** 2) +     3*$y3 * ($n ** 2) * (1-$n) +     $y4 * ($n ** 3);

	return ($x, $y);
}

# parse arguments of SVG-style command like ("1234-4321" or "123,123")
sub parseargs($) {
	my ($argstring) = @_;
	my @args;
	my @output;
	my $n = 0;

	if(!defined($argstring)) {
		return @output;
	}
		
	@args = split(/([ ,-])/, $argstring);

	while($n < @args) {
		if($args[$n] eq '-') {
			push @output, -$args[$n+1];
			$n+=2;
		} elsif($args[$n] eq " "  || $args[$n] eq ',' || $args[$n] =~ /^$/) {
			$n+=1;
		} else {
			push @output, $args[$n];
			$n+=1;
		}
	}
	return @output;
	
}

# Parses SVG curve (like "M100,100m50,50c100-200z") and turns it into MoveTo and Bezier cubic curve commands
# Returns a list of list of hashes with (type == 1) for move and (type == 2) for curve and (type == 3) for line
# For type == 1 (move), only (x,y) are present in the hash
# For type == 2 (cubic), (x,y) are the dest. point, and (x1,y1) and (x2,y2) are the curve points
# For type == 3 (line), only (x,y) are present in the hash

sub parsesvg($) {
	my ($spline) = @_;
	my ($lastx, $lasty);
	my ($lastcurvex, $lastcurvey);
	my ($startx, $starty);
	
	my $points = [];

	my @pointstrings = split(/([A-Za-z])/, $spline);

	my $n = 0;

	while($n<@pointstrings) {
		if($pointstrings[$n] =~ /^$/) { $n++; next; }

		my %point = ();
		my $type = $pointstrings[$n];
		my @args = parseargs($pointstrings[$n+1]);

		# print "Type: $type\n";
		# print "Args: " . join(",", @args) . "\n";

		if($type eq 'M') { # abs. moveto
			$point{"type"} = 1;
			$point{"x"} = $args[0];
			$point{"y"} = $args[1];

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
			$startx = $point{"x"};
			$starty = $point{"y"};
		} elsif($type eq 'v') { # rel. lineto vert
			$point{"type"} = 3;
			$point{"x"} = $lastx;
			$point{"y"} = $args[0]+$lasty;

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'V') { # abs. lineto vert
			$point{"type"} = 3;
			$point{"x"} = $lastx;
			$point{"y"} = $args[0];

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'l') { # rel. lineto
			$point{"type"} = 3;
			$point{"x"} = $args[0]+$lastx;
			$point{"y"} = $args[1]+$lasty;

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'L') { # abs. lineto
			$point{"type"} = 3;
			$point{"x"} = $args[0];
			$point{"y"} = $args[1];

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'h') { # rel. lineto horiz
			$point{"type"} = 3;
			$point{"x"} = $lastx + $args[0];
			$point{"y"} = $lasty;

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'H') { # abs. lineto horiz
			$point{"type"} = 3;
			$point{"x"} = $args[0];
			$point{"y"} = $lasty;

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x"};
			$lastcurvey = $point{"y"};
		} elsif($type eq 'c') { # rel. curve
			$point{"type"} = 2;
			$point{"x1"} = $args[0] + $lastx;
			$point{"y1"} = $args[1] + $lasty;
			$point{"x2"} = $args[2] + $lastx;
			$point{"y2"} = $args[3] + $lasty;
			$point{"x"} = $args[4] + $lastx;
			$point{"y"} = $args[5] + $lasty;

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x2"};
			$lastcurvey = $point{"y2"};
		} elsif($type eq 'C') { # abs. curve
			$point{"type"} = 2;
			$point{"x1"} = $args[0];
			$point{"y1"} = $args[1];
			$point{"x2"} = $args[2];
			$point{"y2"} = $args[3];
			$point{"x"} = $args[4];
			$point{"y"} = $args[5];

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x2"};
			$lastcurvey = $point{"y2"};
		} elsif($type eq 'S') { # abs smooth curve
			$point{"type"} = 2;
			$point{"x2"} = $args[0];
			$point{"y2"} = $args[1];
			$point{"x"} = $args[2];
			$point{"y"} = $args[3];
			# get x1, y1
			$point{"x1"} = -($lastcurvex - $lastx) + $lastx; # Last x2 reflected in current (x,y)
			$point{"y1"} = -($lastcurvey - $lasty) + $lasty; # Last y2 reflected in current (x,y)

			$lastx = $args[2];
			$lasty = $args[3];
			$lastcurvex = $point{"x2"};
			$lastcurvey = $point{"y2"};
		} elsif($type eq 's') { # rel. smooth curve
			$point{"type"} = 2;
			$point{"x2"} = $args[0] + $lastx;
			$point{"y2"} = $args[1] + $lasty;
			$point{"x"} = $args[2] + $lastx;
			$point{"y"} = $args[3] + $lasty;
			# get x1, y1
			$point{"x1"} = -($lastcurvex - $lastx) + $lastx; # Last x2 reflected in current (x,y)
			$point{"y1"} = -($lastcurvey - $lasty) + $lasty; # Last y2 reflected in current (x,y)

			$lastx = $point{"x"};
			$lasty = $point{"y"};
			$lastcurvex = $point{"x2"};
			$lastcurvey = $point{"y2"};
		} elsif($type eq 'z' || $type eq 'Z') {
			$point{"type"} = 3;
			$point{"x"} = $startx;
			$point{"y"} = $starty;
				
			$lastx = $args[2];
			$lasty = $args[3];
			$lastcurvex = $point{"x2"};
			$lastcurvey = $point{"y2"};
		} else {
			print "UNKNOWN TYPE $type\n";
		}

		$n+=2;

		push @{$points}, \%point;
	}

	return $points;
}

# Override for SetParams
sub SetParams($$) {
	my ($self, $params) = @_;
	my $total = 0;

	# Check if there's a new SVG
	
	if(!defined($self->{"filename"}) || $self->{"filename"} ne $params->{"filename"}) {
		my @objects;
		
		$self->{"filename"} = $params->{"filename"};
		
		# Parse SVG
		my $svg = XMLin($params->{"filename"}, ForceArray => 1, KeyAttr => [ ]);
		
		if(!defined($svg)) {
			return;
		}
		
		my ($originx, $originy) = split(" ", $svg->{"i:viewOrigin"});
		
		# print "ORIGIN $originx, $originy\n";
		
		my $xmlobj;
		my @gfx;
		
		getrecursivegfx($svg, \@gfx); 
		
		foreach $xmlobj (@gfx) {
			my $type;
			
			if(defined($xmlobj->{"d"})) {	
				$type = "path";
			} elsif(defined($xmlobj->{"points"})) {
				$type = "polygon";
			} else {
				$type = "rect";
			}
			
			if(defined($xmlobj)) {
				# Found a path
				my $paths;
				
				if($type eq "path") {
					$paths = parsesvg($xmlobj->{"d"});
				} elsif($type eq "polygon") {
					my @points = split(" ", $xmlobj->{"points"});
					$paths = \@points;
				} else {
					my($x1, $y1, $w, $h) = ($xmlobj->{"x"}, $xmlobj->{"y"}, $xmlobj->{"width"}, $xmlobj->{"height"});
					my($x2, $y2) = ($x1+$w, $y1+$h);
					
					my @points = (   
									("$x1,$y1") ,
									("$x2,$y1") ,
									("$x2,$y2") ,
									("$x1,$y2"));

					$paths = \@points;
				}
								
				my ($fill, $stroke);
				my %black;
				
				$black{"r"} = $black{"g"} = $black{"b"} = $black{"a"} = 0;
				
				$fill = \%black;
				$stroke = \%black;
				
				if(defined($xmlobj->{"fill"})) {
					$fill = parsergbhex($xmlobj->{"fill"});
				}
				
				if(defined($xmlobj->{"stroke"})) {
					$stroke = parsergbhex($xmlobj->{"stroke"});
				}
				
				if(defined($xmlobj->{"style"})) {
					my @styles = split(";", $xmlobj->{"style"});
					my %styles;
					my $style;
					
					foreach $style (@styles) {
						my ($name, $val) = split(":", $style);
						
						$styles{$name} = $val;
					}
					
					if(defined($styles{"fill"})) {
						$fill = parsergb($styles{"fill"});
					}
					
					if(defined($styles{"stroke"})) {
						$stroke = parsergb($styles{"stroke"});
					}	
				}
				
				my %object;
				
				$object{"paths"} = $paths;
				$object{"fill"} = $fill;
				$object{"stroke"} = $stroke;
				$object{"type"} = $type;
				
				push @objects, \%object;
			}
		}
		
		# We now have all our renderable objects in %objects, with the paths parsed out to moves, lines and curves.
		# So, send the data off the OpenGL for tesselation and list generation
		
		my @polygons;
		
		my $object;
		foreach $object (@objects) {
			my %polygon;
			
			$polygon{"fill"} = $object->{"fill"};
			$polygon{"stroke"} = $object->{"stroke"};
			
			my $pathpart;
			
			my @contours; # all contours for this polygon
			my @points; # line points in the current contour
			
			if($object->{"type"} eq "path") {
				foreach $pathpart (@{$object->{"paths"}}) {
					# We have to split each path at any MOVE command, so we create multiple contours
					if($pathpart->{"type"} == 1) {
						# Move, so create new contour if applicable
						if(@points > 1) { # There must be 2 points in the path, otherwise there are no lines
							my @contourpoints = @points;
							push @contours, \@contourpoints;
						}
						@points = ();

						my %point;
						$point{"x"} = $pathpart->{"x"};
						$point{"y"} = $pathpart->{"y"};
					
						$total++;	
						push @points, \%point;
					}
					elsif($pathpart->{"type"} == 2) {
						# cubic curve
						# loop through points on cubic curve
						my $i;
						my %lastpoint = %{$points[@points-1]};
						my $linelen = linelen($lastpoint{"x"}, $lastpoint{"y"},$pathpart->{"x"}, $pathpart->{"y"});
						my $steps = 10;
						
						for($i=1;$i<=$steps;$i++) {
							my %point;
							($point{"x"}, $point{"y"}) = cubicpoint($i/$steps, $lastpoint{"x"}, $lastpoint{"y"}, 
																	$pathpart->{"x1"}, $pathpart->{"y1"},
																	$pathpart->{"x2"}, $pathpart->{"y2"},
																	$pathpart->{"x"}, $pathpart->{"y"});
							$total++;
							push @points, \%point;					
						}
					}
					elsif($pathpart->{"type"} == 3) {
						# line
						my %point;
						
						$point{"x"} = $pathpart->{"x"};
						$point{"y"} = $pathpart->{"y"};
						
						$total++;
						push @points, \%point;
					}
				}
				
				# Add last contour
				if(@points > 1) {
					push @contours, \@points;
				} 
				
				$polygon{"contours"} = \@contours;
			} else {
				my @points;
				foreach $pathpart (@{$object->{"paths"}}) {
					my %point;
					
					($point{"x"}, $point{"y"}) = split(",", $pathpart);
					
					$total++;
					push @points, \%point;
				}
				
				$polygon{"contours"} = [ [ @points ] ];
				
			}
			
			push @polygons, \%polygon;
		}

		my $polygon;
		
		foreach $polygon (@polygons) {
			my %strokergba = %{$polygon->{"stroke"}};
			my %fillrgba = %{$polygon->{"fill"}};
			
			if(!defined($strokergba{"a"})) {
				$strokergba{"a"} = 1;
			}
			if(!defined($fillrgba{"a"})) {
				$fillrgba{"a"} = 1;
			}
			
			my $engine = new PolyRender();
			$engine->SetOrigin($originx, $originy);
			$engine->SetPoly($polygon->{"contours"}, \%strokergba, \%fillrgba, 1);

			push @{$self->{"engines"}}, $engine;
		}
		#print "Total vertices: $total\n";
	}		

	if(defined($params->{"rot"})) {
		my $engine;
		
		foreach $engine (@{$self->{"engines"}}) {
			$engine->SetRotation($params->{"rot"});
		}
	}
	
	if(defined($params->{"scale"})) {
		my $engine;
		
		foreach $engine (@{$self->{"engines"}}) {
			$engine->SetScale($params->{"scale"});
		}
	}
	
	if(defined($params->{"alpha"})) {
		my $engine;
		
		foreach $engine (@{$self->{"engines"}}) {
			$engine->SetAlpha($params->{"alpha"});
		}
	}
	
	if(defined($params->{"x"}) && defined($params->{"y"})) {
		my $engine;
		
		foreach $engine (@{$self->{"engines"}}) {
			$engine->SetPos($params->{"x"}, $params->{"y"});
		}
	}
}

sub Render($$) {
	my ($self, $msec) = @_;

	my $engine;	
		foreach $engine (@{$self->{"engines"}}) {
			$engine->Render();
		}
}


1;
