package PITA::Guest::Server;

use 5.008;
use strict;
use Params::Util               1.00 ();
use POE::Wheel::Run           1.299 ();
use POE::Declare::HTTP::Server 0.03 ();
use PITA::Guest::Server::HTTP       ();

our $VERSION = '0.50';

use POE::Declare 0.53 {
	Hostname      => 'Param',
	Port          => 'Param',
	Program       => 'Param',
	Uploads       => 'Param',
	Mirrors       => 'Param',
	StartupEvent  => 'Message',
	ShutdownEvent => 'Message',
	pinged        => 'Attribute',
	uploaded      => 'Attribute',
	mirrored      => 'Attribute',
	status        => 'Internal',
	http          => 'Internal',
	child         => 'Internal',
};

use constant {
	STOPPED  => 1,
	STARTING => 1,
	RUNNING  => 1,
	STOPPING => 1,
};





######################################################################
# Constructor and Accessors

sub new {
	my $self = shift->SUPER::new(@_);

	# Set up tracking variables
	$self->{status}   = STOPPED;
	$self->{pinged}   = 0;
	$self->{mirrored} = [ ];
	$self->{uploaded} = [ ];

	# Check params
	unless ( Params::Util::_ARRAY($self->Program) ) {
		die "Missing or invalid 'Program' param";
	}

	# Create the web server
	$self->{http} = PITA::Guest::Server::HTTP->new(
		Hostname      => $self->Hostname,
		Port          => $self->Port,
		Mirrors       => $self->Mirrors,
		StartupEvent  => $self->lookback('http_startup_event'),
		StartupError  => $self->lookback('http_startup_error'),
		ShutdownEvent => $self->lookback('http_shutdown_event'),
		PingEvent     => $self->lookback('http_ping'),
		MirrorEvent   => $self->lookback('http_mirror'),
		UploadEvent   => $self->lookback('http_upload'),
	);

	return $self;
}










######################################################################
# Main Methods

# Sort of half-assed Process compatibility for testing purposes
sub run {
	$_[0]->start;
	POE::Kernel->run;
	return 1;
}

sub start {
	my $self = shift;
	unless ( $self->spawned ) {
		$self->spawn;
		$self->post('startup');
	}
	return 1;
}

sub stop {
	my $self = shift;
	if ( $self->spawned ) {
		$self->post('shutdown');
	}
	return 1;
}





######################################################################
# Event Methods

sub startup : Event {
	# Kick off the blanket startup timeout
	$_[SELF]->{status} = STARTING;
	$_[SELF]->startup_timeout_start;
	$_[SELF]->post('http_startup');
}

sub http_startup : Event {
	$_[SELF]->{http}->start;
}

sub http_startup_event : Event {
	$_[SELF]->post('child_startup');
}

sub http_startup_error : Event {
	die "Failed to start the web server";
}

sub http_shutdown_event : Event {
	# Nothing to do?
}

sub http_ping : Event {
	$_[SELF]->startup_timeout_stop;
	$_[SELF]->activity_timeout_start;
	$_[SELF]->{status} = RUNNING;
	$_[SELF]->{pinged} = 1;
	$_[SELF]->StartupEvent;
}

sub http_mirror : Event {
	$_[SELF]->activity_timeout_start;
	push @{$_[SELF]->{mirrored}}, [ $_[ARG1], $_[ARG2], $_[ARG3] ];
}

sub http_upload : Event {
	$_[SELF]->activity_timeout_start;
	push @{$_[SELF]->{uploaded}}, [ $_[ARG1], $_[ARG2] ];

	# Do we have everything?
	unless ( grep { not defined $_ } values %{$_[SELF]} ) {
		$_[SELF]->{status} = STOPPING;
		$_[SELF]->activity_timeout_stop;
		$_[SELF]->shutdown_timeout_start;
	}
}

sub child_startup : Event {
	# Spawn the program
	$_[SELF]->{child} = POE::Wheel::Run->new(
		Program     => $_[SELF]->Program,
		StdoutEvent => 'child_stdout',
		StderrEvent => 'child_stderr',
		CloseEvent  => 'child_close',
	);

	# Trap signals from the child as well.
	# NOTE: This needs to be brought under the management of POE::Declare.
	$_[KERNEL]->sig_child( $_[SELF]->{child}->PID => 'child_signal' );
}

sub child_stdout : Event {
	# Do nothing for now
	# print STDERR "# CHILD STDOUT $_[ARG0]\n";
}

sub child_stderr : Event {
	# Do nothing for now
	# print STDERR "# CHILD STDERR $_[ARG0]\n";
}

sub child_close : Event {
	# print STDERR "# CHILD CLOSE\n";
	if ( $_[SELF]->{child} ) {
		# Wait for a little to give the child time to SIGCHILD us
		$_[SELF]->child_signal_timeout_start;
	}
}

sub child_signal : Event {
	# print STDERR "# CHILD SIGCHILD $_[ARG2]\n";
	$_[SELF]->child_signal_timeout_stop;
	if ( $_[SELF]->{child} ) {
		$_[SELF]->post('shutdown');
	}
}

sub child_signal_timeout : Timeout(5) {
	if ( $_[SELF]->{child} ) {
		$_[SELF]->post('shutdown');
	}
}

sub startup_timeout : Timeout(30) {
	$_[SELF]->post('shutdown');
}

sub activity_timeout : Timeout(3600) {
	$_[SELF]->post('shutdown');
}

sub shutdown_timeout : Timeout(60) {
	$_[SELF]->post('shutdown');
}

sub shutdown : Event {
	$_[SELF]->finish;
	$_[SELF]->ShutdownEvent(
		$_[SELF]->pinged,
		$_[SELF]->mirrored,
		$_[SELF]->uploaded,
	);
	$_[SELF]->{status} = STOPPED;
}





######################################################################
# Support Methods

sub finish {
	my $self = shift;

	# Clean up our children
	if ( $self->{child} ) {
		$self->{child}->kill(9);
		$self->{child} = undef;
	}
	if ( $self->{http}->spawned ) {
		$self->{http}->call('shutdown');
	}

	# Call parent method to clean out other things
	$self->SUPER::finish(@_);
}

compile;
