package AA;

use overload '""' => 'ID';

our $PI = 3.1415926535;
our $TWOPI = 2*$PI;

our %known_objs = ( );
our $debug = 1;
our $record_dep_obj;
our $record_dep_attr;
our $record_deps;
our @pending_recalcs;

sub new($$) {
	my ($class, $id) = @_;
	my $o = {
		ID => $id,
	};
	!defined $known_objs{$id} or die "Object $id already defined";
	$known_objs{$id} = $o;
	print "Created object $id\n" if $debug;
	return bless $o, $class;
}

sub find($$) {
	my ($class, $id) = @_;
	defined $known_objs{$id} or die "Object $id not known";
	return $known_objs{$id};
}

sub calculate($) {
	my ($class) = @_;
	my $o;
	while ($o = shift @pending_recalcs) {
		print "Going to recalculate object $o\n" if $debug;
		my $pend = $o->{PENDING};
		foreach my $a (keys %$pend) {
			$o->Calc("$a");
		}
		delete $o->{PENDING};
	}
}

sub draw($$) {
	my ($class, $cairo) = @_;
	my @q = ();
	print "Redrawing the scene...\n" if $debug;
	foreach my $o (values %known_objs) {
		push @q, $o if defined $o->{'a:d'};
	}
	foreach my $o (sort { $a->{'a:d'} <=> $b->{'a:d'} } @q) {
		print "Drawing $o at depth ", $o->{'a:d'}, "\n" if $debug;
		$o->Draw($cairo);
	}
}

sub ID($) {
	return (shift @_)->{ID};
}

sub Define($$) {
	my ($o, $a) = @_;
	!exists $o->{"a:$a"} or die "Redefining attribute $a of object $o";
	$o->{"a:$a"} = undef;		# attribute value
	# $o->{"f:$a"} = undef;		# binding function
	$o->{"d:$a"} = { };		# depends on (obj:attr => obj)
	$o->{"n:$a"} = { };		# notify (obj:attr => obj)
}

sub DefSet($$$) {
	my ($o, $a, $v) = @_;
	$o->Define($a);
	$o->Set($a, $v);
}

sub DefBind($$$) {
	my ($o, $a, $v) = @_;
	$o->Define($a);
	$o->Bind($a, $v);
}

sub DefDep($$$$) {
	my ($o, $a, $v, $b) = @_;
	$o->Define($a);
	$o->Bind($a, sub { $v->Get($b); });
}

sub Exists($$) {
	my ($o, $a) = @_;
	return defined $o->{"a:$a"};
}

sub Get($$) {
	my ($o, $a) = @_;
	defined $o->{"a:$a"} or die "Getting undefined attribute $a of object $o";
	if ($record_deps) {
		# record the dependency
		my $did = "$o:$a";	
		if (!defined $record_deps->{$did}) {
			$record_deps->{$did} = $o;
			my $rid = "$record_dep_obj:$record_dep_attr";
			$o->{"n:$a"}->{$rid} = $record_dep_obj;
			print "Added automatic dependency $rid -> $did\n" if $debug;
		}
	}
	return $o->{"a:$a"};
}

sub Set($$$) {
	my ($o, $a, $v) = @_;
	exists $o->{"a:$a"} or die "Setting undefined attribute $a of object $o";
	print "Setting $o:$a = $v\n" if $debug;
	$o->{"a:$a"} = $v;
	!defined $o->{"f:$a"} or $o->UnBind($a);
	$o->Notify($a);
}

sub Bind($$$) {
	my ($o, $a, $f) = @_;
	exists $o->{"a:$a"} or die "Binding undefined attribute $a of object $o";
	print "Binding $o:$a\n" if $debug;
	$o->UnBind($a);
	$o->{"f:$a"} = $f;
	$o->Calc($a);
}

sub Calc($$) {
	my ($o, $a) = @_;
	die if $record_deps;
	$record_deps = $o->{"d:$a"};
	$record_dep_obj = $o;
	$record_dep_attr = $a;

	$o->{"a:$a"} = undef;
	$o->{"a:$a"} = &{$o->{"f:$a"}} ($o);

	$record_deps = undef;
	$record_dep_obj = undef;
	$record_dep_attr = undef;

	print "Recalculated $o:$a = ", $o->{"a:$a"}, "\n" if $debug;
	$o->Notify($a);
}

sub UnBind($$) {
	my ($o, $a) = @_;
	my $aid = "$o:$a";
	foreach my $dep (keys %{$o->{"d:$a"}}) {
		my ($deponame, $depa) = split(/:/, $dep);
		my $depo = $o->{"d:$a"}->{$dep};
		delete $depo->{"n:$depa"}->{$aid};
		print "Removed notify $aid -> $depo:$depa\n" if $debug;
	}
	$o->{"d:$a"} = { };
	delete $o->{"f:$a"};
}

sub Notify($$) {
	my ($o, $a) = @_;
	foreach my $dep (keys %{$o->{"n:$a"}}) {
		my ($deponame, $depa) = split(/:/, $dep);
		my $depo = $o->{"n:$a"}->{$dep};
		print "Sending notify $o:$a -> $deponame:$depa\n" if $debug;
		if (!defined $depo->{PENDING}) {
			$depo->{PENDING} = { };
			push @pending_recalcs, $depo;
			print "\tPending object $deponame\n" if $debug;
		}
		$depo->{PENDING}->{$depa} = 1;
	}
}

1;
