package Program;

BEGIN {
	# export these
	*{main::NEW} = \&Program::NEW;
	*{main::DEL} = \&Program::DEL;
	*{main::FLT} = \&Program::FLT;
	*{main::SET} = \&Program::SET;
	
	*{main::FLTIN} = \&Program::FLTIN;
	*{main::FLTOUT} = \&Program::FLTOUT;
	*{main::FLTINOUT} = \&Program::FLTINOUT;
}

sub NEW() { return 1; }
sub DEL() { return 2; }
sub FLT() { return 3; }
sub SET() { return 4; }

sub FLTIN() { return 1; }
sub FLTOUT() { return 2; }
sub FLTINOUT() { return 3; }

sub fltinout($) {
	my ($a) = @_;
	return -cos($a*3.14159265)/2+0.5;
}

sub fltin($) {
	my ($a) = @_;
	return -cos($a*3.14159265/2)+1;
}

sub fltout($) {
	my ($a) = @_;
	return sin($a*3.14159265/2);
}

sub logstr($) {
	my ($str) = @_;

	print "LOG: ".$str;
}

sub is_numeric {
	return defined eval { $_[ 0] == 0 };
}

sub new($$) {
	my ($class) = @_;


	my $self = {};

	$self->{"curevent"} = 0;
	$self->{"layers"} = [];

	bless $self;

	return $self;
}

sub SetParams($$) {
	my ($self,$params) = @_;
	my $key;

	foreach $key (keys %{$params}) {
		if($key eq "program") {
			# Load program
			$self->LoadProgram($params->{$key});
		} else {
			$self->{"params"}->{$key} = $params->{$key};
		}
	}
}

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

	my $curevent = $self->{"curevent"};

	# logstr( "Render frame @ $msec msec\n" );

	# Scan through the event list to see if anything should be started or ended

	if($curevent >= @{$self->{"program"}}) {
		return 1;
	}

	while(defined($self->{"program"}->[$curevent]) && $self->{"program"}->[$curevent]->[0] <= $msec) {
		my $event = $self->{"program"}->[$curevent];

		if($event->[1] == NEW) {

			logstr( "Event @ " . $event->[0] . " msec: NEW " . $event->[3] . " object\n" );

			# Call the package's constructor
			my $engine = &{$event->[2] . "::new"} ($event->[2]);

			$engine->SetParams($event->[5]);

			$self->RemoveLayer($event->[4]);
			$self->AddLayer($engine, $event->[4], $event->[3], $event->[5], $event->[0]);

			# Find next float point and add it if necessary

			my $nextevent = $self->FindNextFlt($curevent, $event->[4]);

			if(defined($nextevent)) {
				logstr( "Found FLT for object at event $nextevent\n" );
				$self->FloatLayer($event->[4], $nextevent);
			} else {
				logstr( "No FLT after event $event->[0] msec, freezing $event->[4] object\n" );
				$self->FloatLayer($event->[4], 0);
			}
		}
		elsif($self->{"program"}->[$curevent]->[1] == DEL) {
			logstr( "Event @ $event->[0] msec, DEL $event->[2] object\n" );

			$self->RemoveLayer($event->[2]);

		}
		elsif($self->{"program"}->[$curevent]->[1] == FLT) {
			# Passed a float point, find the next and set it
			
			# Set layer to end values
			$self->SetLayer($event->[2], $event->[3], $event->[0]);

			logstr( "Event @ $event->[0] msec, FLT $event->[2] object\n" );

			my $nextevent = $self->FindNextFlt($curevent, $event->[2]);

			if(defined($nextevent)) {
				logstr( "Found FLT for object at event $nextevent\n" );
				$self->FloatLayer($event->[2], $nextevent);
			} else {
				logstr( "No FLT after event $event->[0] msec, freezing $event->[2] object\n" );
				$self->FloatLayer($event->[2], 0);
			}

		}
		elsif($self->{"program"}->[$curevent]->[1] == SET) {
			# Found a SET point (har har)

			logstr( "Event @ $event->[0] msec, SET $event->[2] object\n" );

			$self->SetLayer($event->[2], $event->[3], $event->[0]);

			# Find next float point and add it if necessary

			my $nextevent = $self->FindNextFlt($curevent, $event->[2]);

			if(defined($nextevent)) {
				logstr( "Found FLT for object at event $nextevent\n" );
				$self->FloatLayer($event->[2], $nextevent);
			} else {
				logstr( "No FLT after event $event->[0] msec, freezing $event->[2] object\n" );
				$self->FloatLayer($event->[2], 0);
			}
		}

		$curevent++;
	}

	# Scan through the current effects to see if they should be floated to a next value
	foreach $layer (@{$self->{"layers"}}) {
		if(defined($layer->{"fltevent"})) {
			# This layer has a 'float' param, so it should be smoothly moved to the following pos
			my $msecstart = $layer->{"start"};
			my $msecend = $self->{"program"}->[$layer->{"fltevent"}]->[0];
			my $paramstart = $layer->{"params"};
			my $paramend = $self->{"program"}->[$layer->{"fltevent"}]->[3];
			my $type = $self->{"program"}->[$layer->{"fltevent"}]->[4];

			my $params = FloatParams(($msec-$msecstart) / ($msecend-$msecstart), $paramstart, $paramend, $type);

			$layer->{"engine"}->SetParams($params);
		}
	}

	# logstr( "Renderloop: (" . @{$self->{"layers"}} . " layers):\n" );
	# Render all the layers in ascending order
	foreach $layer (sort { $a->{"depth"} <=> $b->{"depth"} } @{$self->{"layers"}}) {

		my $reltime = $msec - $layer->{"start"};

		$layer->{"engine"}->Render($reltime);
	}
	# logstr( "\n" );

	$self->{"curevent"} = $curevent;

	return 0;
}

sub AddLayer($$$$$) {
	my ($self, $engine, $name, $depth, $params, $start) = @_;

	my $newlayer;
	my %params = %{$params};

	$newlayer->{"engine"} = $engine;
	$newlayer->{"name"} = $name;
	$newlayer->{"depth"} = $depth;
	$newlayer->{"params"} = \%params;
	$newlayer->{"start"} = $start;
	
	push @{$self->{"layers"}}, $newlayer;
}

sub RemoveLayer($$) {
	my ($self, $name) = @_;

	my $layer;
	
	@{$self->{"layers"}} = grep { $_->{"name"} ne $name } @{$self->{"layers"}};
}

sub SetLayer($$$$) {
	my ($self, $name, $params, $start) = @_;

	# Find the correct layer
	foreach $layer (@{$self->{"layers"}}) {
		if($layer->{"name"} eq $name) {
			$layer->{"params"} = FloatParams(1,$layer->{"params"},$params);
			$layer->{"curevent"} = $event;
			$layer->{"start"} = $start;
			$layer->{"engine"}->SetParams($layer->{"params"});
		}
	}
}
sub FloatLayer($$$) {
	my ($self, $name, $fltevent) = @_;

	my $layer;

	# Find the correct layer
	foreach $layer (@{$self->{"layers"}}) {
		if($layer->{"name"} eq $name) {
			if($fltevent == 0) {
				# turn off float
				undef($layer->{"fltevent"});
			} else {
				$layer->{"fltevent"} = $fltevent;
			}
		}
	}
}

sub LoadProgram($$) {
	my ($self, $program) = @_;

	logstr( "Loading program, (" . (0 + @{$program}) . " events)\n" );
	# Copy program
	my @program= @{$program};

	# Sort by start moment
	@program = sort { $a->[0] <=> $b->[0] } @program;


	$self->{"program"} = \@program;
}

sub FindNextFlt($$$$) {
	my ($self, $first, $name) = @_;

	$first++;
	while($first < @{$self->{"program"}}) {
		if(	$self->{"program"}->[$first]->[1] == FLT &&
			$self->{"program"}->[$first]->[2] eq $name ) {
				return $first;
		}
				
		if(	$self->{"program"}->[$first]->[1] == SET &&
			$self->{"program"}->[$first]->[2] eq $name ) {
				return;
		}

		if(	$self->{"program"}->[$first]->[1] == DEL &&
			$self->{"program"}->[$first]->[2] eq $name ) {
				return;
		}

		if(	$self->{"program"}->[$first]->[1] == NEW &&
			$self->{"program"}->[$first]->[4] eq $name ) {
				return;
		}
		$first++;
	}

	return;
}

sub FloatParams($$$$) {
	my ($n, $p1, $p2, $type) = @_;
	my $key;
	my %output = %{$p1};

	my @sharedkeys;
	
	if(!defined($type)) {
		$n = $n;
	} elsif($type == FLTIN) {
		$n = fltin($n);
	} elsif($type == FLTOUT) {
		$n = fltout($n);
	} elsif($type == FLTINOUT) {
		$n = fltinout($n);
	}

	@sharedkeys = grep { $a = $_; grep { $_ eq $a } keys %{$p2} } keys %{$p1};

	foreach $key (@sharedkeys) {
		if(is_numeric($p1->{$key}) && is_numeric($p2->{$key})) {
			$output{$key} = $n * ($p2->{$key} - $p1->{$key}) + $p1->{$key};
		}
	}

	return \%output;
}

1;
