# Webpage
# =======
#
# A single webpage, able to send itself over CGI.

package Web::Page;

use strict;
use warnings;

use CGI;
use Data::Dumper;
use HTML::Element;

use constant {
	# Default content type, if not able to determine
	DEFAULT_TYPE => 'application/octet-stream',
	
	# Types and conversion of objects
	TYPES => {
		'HTML::Element' => ['text/html',
							sub { $_[0]->as_HTML(undef, '  ', {}) }],
		'Web::Canvas' => ['text/html',
						  sub { $_[0]->elementify->as_HTML(undef, '  ', {}) }],					
		'ARRAY' => ['text/html',
					sub { HTML::Element->new_from_lol($_[0])
									   ->as_HTML(undef, '  ', {}) }],
	},
};

# Create a new page and define the process that makes its content.
sub new {
	my ($class, $process) = @_;
	my $self = bless {
		process => $process,
		cgi => CGI->new,
	}, $class;
	
	return $self;
}

# Our process.
sub process { $_[0]->{process} }

# Our CGI environment.
sub cgi { $_[0]->{cgi} }

# Call the process and show the result.
sub show {
	my ($self, $content_type) = @_;
	my $result;
	my $type;
	
	eval {
		($result, $type) = $self->process->($self);
	};
	
	if ($@) {
		return $self
			unless length ($@);
			
		$self->error($@);
		return $self;
	}
	
	return $self
		unless defined $result;

	return $self
		if (ref $result) eq 'Web::App::Controller';
		
	if (ref $result) {
		if (defined $content_type) {
			die 'Mismatching content-types'
				unless $content_type eq $self->find_type($result);
		}
		else {
			$content_type = $self->find_type($result)
		}
	}
	else {
		$content_type = $content_type || $type || DEFAULT_TYPE;
	}
	
	binmode STDOUT if $self->is_binary($content_type);
	
	print STDOUT $self->cgi->header($content_type);
	print STDOUT $self->convert($result);
	
	return $self;
}

# Do we send binary?
sub is_binary {
	my ($self, $content_type) = @_;
	
	return 1 if $content_type =~ m!^image/!;
	return 1 if $content_type =~ m!^audio/!;
	return 1 if $content_type =~ m!^video/!;
	return 0 if $content_type =~ m!^application/xml!;
	return 1 if $content_type =~ m!^application/!;
	return 0;
}

# Determine the type of this object
sub find_type {
	my ($self, $object) = @_;
	
	die 'Unknown result type' unless defined TYPES->{ref $object};
	
	return TYPES->{ref $object}->[0];
}

# Convert an object to a content type
sub convert {
	my ($self, $object) = @_;
	
	return $object unless ref $object;
	return unless defined TYPES->{ref $object};
	return TYPES->{ref $object}->[1]->($object);
}

# An error happened: Send a error page.
sub error {
	my ($self, $error) = @_;
	my $html =
		[html => {lang => 'en'},
			[head => 
				[title => 'Server Error'],
			],
			[body =>
				[h1 => 'Error'],
				[pre => ref $error ? Dumper $error : $error],
			],
		];
		
	print $self->cgi->header('text/html', '500 Internal Server Error');
	print "<DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN>\n\n";
	print HTML::Element->new_from_lol($html)->as_HTML(undef, '  ', {});
}

1;
