use 5.010;
use strict;
use MooseX::Declare;

BEGIN
{
	$WWW::DataWiki::Controller::Page::AUTHORITY = 'cpan:TOBYINK';
	$WWW::DataWiki::Controller::Page::VERSION   = '0.000_01';
}

class WWW::DataWiki::Controller::Page
	extends WWW::DataWiki::Controller
{
	use CatalystX::Syntax::Action;
	use File::Slurp qw(slurp);
	use URI::Escape qw(uri_escape);
	
	our $RE_page = '((?:[a-z][a-z0-9-]*[a-z0-9])(?:/[a-z][a-z0-9-]*[a-z0-9])*)';
	our $RE_ext  = '(\.nt|\.n3|\.ttl|\.x?html|\.xml|\.json|\.txt|\.csv|\.tab)';
	our $RE_path = "${RE_page}${RE_ext}?";
	our $RE_vid  = '(\d{4}(?:\d{2}(?:\d{2}(?:T\d{4,6}(?:\.\d+)?Z?)?)?)?)';
	__PACKAGE__->config(
		namespace => '',
		action => {
			wikipage    => { Regex => "^${RE_path}(?:[,]${RE_vid})?\$" },
			wikihistory => { Regex => "^${RE_path},history\$" },
			wikioptions => { Regex => "^${RE_path},options\$" },
			wikieditor  => { Regex => "^${RE_path},edit\$" },
			end         => { Private => 1 },
			},
		);

	action wikipage    { $ctx->add_http_vary('Accept-Datetime'); $self->_dispatch_by_method($ctx, 'wikipage'); }
	action wikihistory { $self->_dispatch_by_method($ctx, 'wikihistory');  }
	action wikioptions { $self->_dispatch_by_method($ctx, 'wikioptions'); }
	action wikieditor  { $self->_dispatch_by_method($ctx, 'wikieditor'); }

	action wikipage_GET
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma
		
		my $ver = $ctx->model('Wiki')->version($wikiname, $v);
		WWW::DataWiki::Exception->throw(404 => 'Page not found') unless defined $ver;
		# should probably throw an exception if $v in future
		# should probably redirect to canonical $v if $v isn't canonical.
		$ctx->stash(resource => $ver);

		if (WWW::DataWiki::Controller::Query->req_is_query($ctx->req))
		{
			$ctx->detach('Controller::Query', 'query');
		}
		
		$ctx->res->headers->push_header(
			Link => sprintf('<%s>; rel="timegate original"', $ver->page_iri->uri),
			);
		$ctx->res->headers->push_header(
			Link => sprintf('<%s>; rel="timemap"', $ver->page->history_iri->uri),
			);
		if (my $accept_dt = $ctx->req->header('Accept-Datetime') and not defined $v)
		{
			$accept_dt = DateTime::Format::HTTP->parse_datetime($accept_dt);
			# should probably throw an exception if in future
			$accept_dt = WWW::DataWiki::Utils->dt_fmt_short->format_datetime($accept_dt);
			my $memento = $ctx->model('Wiki')->version($wikiname, $accept_dt);
			if ($memento)
			{
				# should probably append query string, and extension
				$ctx->res->redirect($memento->version_iri->uri, 302);
				$ctx->detach;
			}
			else
			{
				WWW::DataWiki::Exception->throw(406 => 'Not Acceptable',
					'No versions exist that early.');
			}
		}
		my $dt = DateTime::Format::HTTP->format_datetime(
			WWW::DataWiki::Utils->dt_fmt_short->parse_datetime($ver->version));
		$ctx->res->headers->push_header('Memento-Datetime' => $dt);
		$self->_memento_header($ctx, 'first-memento', $ver->page->earliest_version);
		$self->_memento_header($ctx, 'prev-memento', $ver->page->version_offset_from($ver->version, -1));
		$self->_memento_header($ctx, 'next-memento', $ver->page->version_offset_from($ver->version, +1));
		$self->_memento_header($ctx, 'last-memento', $ver->page->latest_version);
	}
	
	method _memento_header($ctx, $rel, $ver)
	{
		return unless blessed($ver);
		my $dt = DateTime::Format::HTTP->format_datetime(
			WWW::DataWiki::Utils->dt_fmt_short->parse_datetime($ver->version));
		$ctx->res->headers->push_header(
			Link => sprintf('<%s>; rel="%s"; datetime="%s"',
				$ver->version_iri->uri, $rel, $dt),
			);
	}

	action wikipage_POST 
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma
		
		my $ver = $ctx->model('Wiki')->version($wikiname, $v);
		WWW::DataWiki::Exception->throw(404 => 'Page not found') unless defined $ver;
		$ctx->stash(resource => $ver);
		
		if (WWW::DataWiki::Controller::Query->req_is_query($ctx->req))
		{
			$ctx->detach('Controller::Query', 'query');
		}
		elsif (WWW::DataWiki::Controller::Query->req_is_update($ctx->req))
		{
			$ctx->detach('Controller::Query', 'update');
		}
		elsif (defined (my $fmt = WWW::DataWiki::Utils->canonicalise_rdf_format($ctx->req->content_type)))
		{
			my $data = $ctx->req->body;
			$data = slurp($data)
				if $data =~ m#^/# && -f $data;
				
			my $new = $ver->create_version_from_append_string($data, $fmt);
			if ($new)
			{
				$new->store_meta( WWW::DataWiki::Utils->ctx_to_provenance($ctx) );
				
				$ctx->set_http_status_code(201 => 'Created');
				$ctx->stash(resource => $new);
			}
			else
			{
				$ctx->stash(resource => undef);
				WWW::DataWiki::Exception->throw(500 => 'Error creating new version');
			}			
		}
		else
		{
			WWW::DataWiki::Exception->throw(415 => 'Unsupported media type',
				'This resource accepts POST requests using SPARQL Query/Update, and accepts RDF data to append to the wiki page.');
		}
	}

	action wikipage_PATCH
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma
		
		my $ver = $ctx->model('Wiki')->version($wikiname, $v);
		WWW::DataWiki::Exception->throw(404 => 'Page not found') unless defined $ver;
		$ctx->stash(resource => $ver);
		
		if (WWW::DataWiki::Controller::Query->req_is_update($ctx->req))
		{
			$ctx->detach('Controller::Query', 'update');
		}
		else
		{
			WWW::DataWiki::Exception->throw(415 => 'Unsupported media type',
				'This resource accepts PATCH requests using SPARQL Update.');
		}
	}

	action wikipage_PUT 
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma
		
		my $page = $ctx->model('Wiki')->page($wikiname);
		
		if (defined (my $fmt = WWW::DataWiki::Utils->canonicalise_rdf_format($ctx->req->content_type)))
		{
			my $data = $ctx->req->body;
			$data = slurp($data)
				if $data =~ m#^/# && -f $data;
				
			my $new = $page->create_version_from_string($data, $fmt);
			if ($new)
			{
				$new->store_meta( WWW::DataWiki::Utils->ctx_to_provenance($ctx) );
				
				$ctx->set_http_status_code(201 => 'Created');
				$ctx->stash(resource => $new);
			}
			else
			{
				WWW::DataWiki::Exception->throw(500 => 'Error creating new version');
			}			
		}
		else
		{
			WWW::DataWiki::Exception->throw(415 => 'Unsupported media type',
				'Accepts Notation 3 (preferred), and most common RDF serializations.');
		}
	}

	action wikipage_DELETE
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma
		
		my $page = $ctx->model('Wiki')->page($wikiname);
		
		my $new = $page->create_version_from_string('', WWW::DataWiki->FMT_NT);
		if ($new)
		{
			$new->store_meta( WWW::DataWiki::Utils->ctx_to_provenance($ctx) );
				
			$ctx->set_http_status_code(200 => 'OK');
			$ctx->stash(resource => $new);
		}
		else
		{
			WWW::DataWiki::Exception->throw(500 => 'Resource could not be deleted.');
		}			
	}

	action wikipage_HEAD { goto &wikipage_GET; }

	action wikipage_OPTIONS
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };
		my $help = $ctx->model('Wiki')->help($wikiname, $v);
		$ctx->stash(resource => $help);
	}

	action wikihistory_GET
	{
		my ($wikiname) = @{ $ctx->req->captures };
		my $wikipage = $ctx->model('Wiki')->page($wikiname);
		WWW::DataWiki::Exception->throw(404 => 'Page not found')
			unless defined $wikipage and defined $wikipage->latest_version;
		$ctx->stash(resource => $wikipage);

		$ctx->res->headers->push_header(
			Link => sprintf('<%s>; anchor="%s"; rel="timemap"', $wikipage->history_iri->uri, $wikipage->page_iri->uri),
			);

		if (defined $wikipage and $ctx->req->method =~ m'^(GET|HEAD)$'i
		and WWW::DataWiki::Controller::Query->req_is_query($ctx->req))
		{
			$ctx->detach('Controller::Query', 'query');
		}
	}
	
	action wikihistory_POST 
	{
		my ($wikiname) = @{ $ctx->req->captures };
		my $wikipage = $ctx->model('Wiki')->page($wikiname);
		WWW::DataWiki::Exception->throw(404 => 'Page not found')
			unless defined $wikipage and defined $wikipage->latest_version;
		$ctx->stash(resource => $wikipage);
		
		$ctx->res->headers->push_header(
			Link => sprintf('<%s>; anchor="%s"; rel="timemap"', $wikipage->history_iri->uri, $wikipage->page_iri->uri),
			);
		
		if (WWW::DataWiki::Controller::Query->req_is_query($ctx->req))
		{
			$ctx->detach('Controller::Query', 'query');
		}
		else
		{
			WWW::DataWiki::Exception->throw(400 => 'Unsupported POST',
				'This resource accepts POST requests using SPARQL Protocol 1.0.');
		}
	}

	action wikihistory_HEAD { goto &wikihistory_GET; }	

	action wikioptions_GET
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };
		my $help = $ctx->model('Wiki')->help($wikiname, $v);
		$ctx->stash(resource => $help);
	}

	action wikioptions_OPTIONS { goto &wikioptions_GET; }	
	action wikioptions_HEAD    { goto &wikioptions_GET; }	

	action wikieditor_GET
	{
		my ($wikiname) = @{ $ctx->req->captures };
		my $editor = $ctx->model('Wiki')->editor($wikiname);
		WWW::DataWiki::Exception->throw(404 => 'Page not found')
			unless defined $editor;
		$ctx->stash(resource => $editor);
	}

	action wikieditor_OPTIONS { goto &wikieditor_GET; }	
	action wikieditor_HEAD    { goto &wikieditor_GET; }	

	action wikieditor_POST 
	{
		my ($wikiname, undef, $v) = @{ $ctx->req->captures };		
		$v =~ s/^,// if defined $v; # trim leading comma

		my $page = $ctx->model('Wiki')->page($wikiname);
		
		if (defined (my $fmt = WWW::DataWiki::Utils->canonicalise_rdf_format($ctx->req->params->{'content-type'})))
		{
			my $data = $ctx->req->params->{body};
				
			my $new = $page->create_version_from_string($data, $fmt);
			if ($new)
			{
				$new->store_meta( WWW::DataWiki::Utils->ctx_to_provenance($ctx) );				
				$ctx->stash(resource => $new);
				
				$ctx->res->redirect($new->version_iri->uri, 303);
				$ctx->detach;
			}
			else
			{
				WWW::DataWiki::Exception->throw(500 => 'Error creating new version');
			}			
		}
		else
		{
			WWW::DataWiki::Exception->throw(415 => 'Unsupported media type',
				'Accepts Notation 3 (preferred), and most common RDF serializations.');
		}
	}
}

1;
