use strict;
use warnings FATAL => 'all';

package Apache::SWIT::Session;
use Storable qw(thaw freeze);
use HTML::Tested::Seal;

sub access_handler($$) {
	my ($class, $r) = @_;
	my $session = $class->begin($r);
	$r->pnotes("SWITSession", $session);
	return Apache2::Const::OK();
}

sub begin {
	my ($class, $r) = @_;
	my %args = (_request => $r);
	my %cookies = Apache2::Cookie->fetch($r);
	$args{session_value} = $cookies{$class->cookie_name}->value
		if $cookies{$class->cookie_name};
	my $self = $class->new(%args);
	$self->read_stash;
	return $self;
}

sub request { return shift()->{_request}; }

sub end {
	my $self = shift;
	$self->write_stash;
	my $cookie = Apache2::Cookie->new($self->{_request}, 
			'-name' => $self->cookie_name,
			'-value' => $self->session_value);
	$cookie->path($self->{_request}->dir_config("SWITRootLocation"));
	$cookie->bake($self->{_request});
}

sub new {
	my ($class, %args) = @_;
	return bless(\%args, $class);
}

sub _get {
	my ($self, $name, $val) = @_;
	return $self->{_stash}->{$name};
}

sub _delete_children {
	my ($self, $name) = @_;
	for my $d (@{ $self->_get_args($name)->{children} || [] }) {
		my $f = "delete_$d";
		$self->$f;
	}
}

sub _set {
	my ($self, $name, $val) = @_;
	$self->{_stash}->{$name} = $val;
	$self->_delete_children($name);
}

sub _delete {
	my ($self, $name) = @_;
	my $res = delete $self->{_stash}->{$name};
	$self->_delete_children($name);
	return $res;
}

sub add_class_dbi_var {
	my ($class, $var, $dbi_class) = @_;
	$class->add_var($var, inflate => sub {
		return $dbi_class->retrieve(shift());
	}, deflate => sub { return shift()->id });
}

sub _get_args {
	my ($self, $name) = @_;
	my $p_args = "$name\_args";
	return $self->$p_args;
}

sub add_var {
	my ($class, $name, %args) = @_;
	no strict 'refs';
	*{ "$class\::get_$name" } = sub {
		return shift()->_get($name, @_);
	};
	*{ "$class\::set_$name" } = sub {
		return shift()->_set($name, @_);
	};
	*{ "$class\::delete_$name" } = sub {
		return shift()->_delete($name, @_);
	};
	*{ "$class\::$name\_args" } = sub { return \%args; };

	$args{children} = [] unless $args{children};

	for my $d (@{ $args{depends_on} || [] }) {
		push @{ $class->_get_args($d)->{children} }, $name;
	}
}

sub session_value { return shift()->{session_value}; }

sub write_stash {
	my $self = shift;
	my %s;
	while (my ($n, $v) = each %{ $self->{_stash} }) {
		my $in = $self->_get_args($n)->{deflate};
		$s{$n} = $in ? $in->($v) : $v;
	}
	$self->{session_value} = HTML::Tested::Seal->instance->encrypt(
					freeze(\%s));
}

sub _thaw {
	my $v = shift->session_value or return {};
	my $res = HTML::Tested::Seal->instance->decrypt($v) or return {};
	return thaw($res);
}

sub read_stash {
	my $self = shift;
	my $s = $self->_thaw;
	my %stash;
	while (my ($n, $v) = each %$s) {
		my $d = $self->_get_args($n)->{inflate};
		$stash{$n} = $d ? $d->($v) : $v;
	}
	$self->{_stash} = \%stash;
}

sub swit_startup {}

1;
