package Plack::App::Gruntmaster;

use 5.014000;
use strict;
use warnings;
use parent qw/Plack::Component/;
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
our $VERSION = '5999.000_001';

use Apache2::Authen::Passphrase qw/pwcheck pwset/;
use CSS::Minifier::XS;
use File::Slurp qw/read_file/;
use HTTP::Negotiate qw/choose/;
use JavaScript::Minifier::XS;
use Plack::Request;
use Gruntmaster::Page::Generic;

my %handlers;

sub call {
	my $env = $_[1];
	my $r = Plack::Request->new($env);
	my @handlers = @{ $handlers{$r->method} // [] };
	for my $handler (@handlers) {
		my ($re, $obj) = @$handler;
		my @args;
		next unless @args = $r->path =~ m/^$re$/a;
		return $obj->($env, map { $_ // '' } @args);
	}

	if ($r->method eq 'GET' || $r->method eq 'HEAD') {
		my $article = $r->path eq '/' ? '/index' : $r->path;
		$article = substr $article, 1;
		$article =~ tr,/,_,;
		my @variants = grep { !/\.title$/ } <a/$article.*>;
		if (@variants) {
			my $lang = choose [ map { [$_, 1, 'text/html', undef, undef, $_, undef] } map { /\.(.+)$/ } @variants ], $r->headers;
			my $content = read_file "a/$article.$lang";
			my $title = read_file "a/$article.$lang.title";
			my $html = Gruntmaster::Page::Base::header($lang, $title) . $content . Gruntmaster::Page::Base::footer($lang);
			return [200, ['Content-Type' => 'text/html', 'Content-Language' => $lang, 'Vary' => 'Accept-Language', 'X-Forever' => 1, 'Cache-Control' => 'max-age=300'], [$html] ]
		}
	}

	[404, ['Content-Type' => 'text/plain'], ['Not found']]
}

sub get  { push @{$handlers{GET }}, [ @_ ] }
sub post { push @{$handlers{POST}}, [ @_ ] }
sub db   { $_[0]->{'gruntmaster.dbic'} }
sub reply { [200, ['Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache'], [ @_ ] ] }
sub admin { 0 }

sub old_handler {
	my ($pkg) = @_;
	$pkg = "Gruntmaster::Page::$pkg";
	eval "require $pkg" or die $@;
	sub {
		my ($env, @args) = @_;
		my $format = choose $pkg->variants, Plack::Request->new($env)->headers;
		$pkg->generate($format, $env, @args)
	}
}

use constant CONTENT_TYPES => +{
	c => 'text/x-csrc',
	cpp => 'text/x-c++src',
	cs => 'text/x-csharp', # Used by GNOME. Not in mime.types.
	java => 'text/x-java',
	pas => 'text/x-pascal',
	pl => 'text/x-perl',
	py => 'text/x-python',
};
use constant NOT_FOUND => [404, ['Content-Type' => 'text/plain'], ['Not found']];
use constant FORBIDDEN => [403, ['Content-Type' => 'text/plain'], ['Forbidden']];

BEGIN{
	my $word   = qr,(\w+),a;
	my $number = qr,(\d+),a;

	get qr,/css/$word\.css, => sub {
		my ($env, $theme) = @_;
		return [404, ['Content-Type' => 'text/plain'], [ 'Not found' ]] unless -e "css/themes/$theme.css";
		my $css = read_file "css/themes/$theme.css";
		$css .= read_file $_ for <css/*.css>;
		[200, ['Content-Type' => 'text/css', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [CSS::Minifier::XS::minify $css] ]
	};

	get qr,/js\.js, => sub {
		my $js;
		$js .= read_file $_ for <js/*.js>;
		[200, ['Content-Type' => 'application/javascript', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [JavaScript::Minifier::XS::minify $js] ]
	};

	get qr,/log/src/$number\.$word, => sub {
		my ($env, $job, $ext) = @_;
		$job = db($env)->job($job);
		return NOT_FOUND if $job->contest;
		return FORBIDDEN if !admin && ($job->private || $job->problem->private);
		[200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ]
	};

	get qr,/ct/$word/log/src/$number\.$word, => sub {
		my ($env, $ct, $job, $ext) = @_;
		$job = db($env)->job($job);
		$ct = db($env)->contest($ct);
		return NOT_FOUND if $job->contest->id ne $ct->id;
		return FORBIDDEN if !admin && ($job->private || !$ct->is_finished);
		[200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ]
	};

	get qr,/ct/$word/log/st, => sub {
		my($env, $ct) = @_;
		$env->{'gruntmaster.contest'} = $ct;
		old_handler('St')->($env);
	};

	get qr,/us/, => old_handler 'Us';
	get qr,/us/$word, => old_handler 'Us::Entry';

	get qr,/pb/, => old_handler 'Pb';

	get qr,/us/$word/pb/, => sub {
		my ($env, $us) = @_;
		$env->{'gruntmaster.user'} = $us;
		old_handler('Pb')->($env);
	};

	get qr,/ct/$word/pb/, => sub {
		my ($env, $ct) = @_;
		return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
		$env->{'gruntmaster.contest'} = $ct;
		old_handler('Pb')->($env);
	};

	get qr,/pb/$word, => sub {
		my ($env, $pb) = @_;
		return NOT_FOUND if !db($env)->problem($pb)->is_in_archive;
		return FORBIDDEN if !admin && db($env)->problem($pb)->private;
		old_handler('Pb::Entry')->(@_);
	};

	get qr,/ct/$word/pb/$word, => sub{
		my ($env, $ct, $pb) = @_;
		return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb);
		return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
		$env->{'gruntmaster.contest'} = $ct;
		old_handler('Pb::Entry')->($env, $pb);
	};

	post qr,/pb/$word/submit, => sub {
		my ($env, $pb) = @_;
		return NOT_FOUND if !db($env)->problem($pb)->is_in_archive;
		old_handler('Submit')->($env, $pb);
	};

	post qr,/ct/$word/pb/$word/submit, => sub {
		my ($env, $ct, $pb) = @_;
		return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb);
		return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
		$env->{'gruntmaster.contest'} = $ct;
		old_handler('Submit')->($env, $pb);
	};

	get qr,/log/(?:page-$number)?, => sub {
		my ($env, $page) = @_;
		$env->{'gruntmaster.page'} = $page || -1;
		old_handler('Log')->($env)
	};

	get qr,/ct/$word/log/(?:page-$number)?, => sub {
		my ($env, $ct, $page) = @_;
		return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
		$env->{'gruntmaster.contest'} = $ct;
		$env->{'gruntmaster.page'} = $page || -1;
		old_handler('Log')->($env)
	};

	get qr,/pb/$word/log/(?:page-$number)?, => sub {
		my ($env, $pb, $page) = @_;
		#return FORBIDDEN if !admin && db($pb)-> TODO
		$env->{'gruntmaster.problem'} = $pb;
		$env->{'gruntmaster.page'} = $page || -1;
		old_handler('Log')->($env)
	};

	get qr,/us/$word/log/(?:page-$number)?, => sub {
		my ($env, $us, $page) = @_;
		$env->{'gruntmaster.user'} = $us;
		$env->{'gruntmaster.page'} = $page || -1;
		old_handler('Log')->($env);
	};

	get qr,/log/$number, => sub{
		my ($env, $job) = @_;
		my $j = db($env)->job($job);
		return FORBIDDEN if !admin && ($j->private || $j->problem->private || ($j->contest && !$j->contest->is_finished));
		old_handler('Log::Entry')->($env, $job);
	};

	get qr,/ct/, => old_handler 'Ct';

	get qr,/ct/$word, => sub {
		my ($env, $ct) = @_;
		return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending;
		old_handler('Ct::Entry')->($env, $ct);
	};

	post qr,/action/passwd, => sub {
		my ($env) = @_;
		my $r = Plack::Request->new($env);
		my ($oldpass, $newpass, $confirm) = map {scalar $r->param($_)} 'password', 'new_password', 'confirm_new_password';

		return reply 'Incorrect password' unless eval { pwcheck $r->user, $oldpass; 1 };
		return reply 'The two passwords do not match' unless $newpass eq $confirm;

		pwset $r->user, $newpass;
		reply 'Password changed successfully';
	};

	post qr,/action/register, => old_handler 'Register';

}

1;
__END__
