package MusicBrainz::Server::Controller;
use Moose;
BEGIN { extends 'Catalyst::Controller'; }

use Carp;
use Data::Page;
use MusicBrainz::Server::Edit::Exceptions;
use MusicBrainz::Server::Constants qw( $UNTRUSTED_FLAG $EDIT_COUNT_LIMIT );
use MusicBrainz::Server::Translation qw( l ln );
use MusicBrainz::Server::Validation qw( is_positive_integer );
use Try::Tiny;

__PACKAGE__->config(
    form_namespace => 'MusicBrainz::Server::Form',
    paging_limit => 50,
);

sub not_found : Private
{
    my ($self, $c) = @_;
    $c->response->status(404);
    $c->stash( template => $self->action_namespace . '/not_found.tt' );
    $c->detach;
}

sub invalid_mbid
{
    my ($self, $c, $id) = @_;
    $c->stash( message  => l("'{id}' is not a valid MusicBrainz ID", { id => $id }) );
    $c->detach('/error_400');
}

sub create_action
{
    my $self = shift;
    my %args = @_;

    if (exists $args{attributes}{'Form'})
    {
        $args{_attr_params} = delete $args{attributes}{'Form'};
        push @{ $args{attributes}{ActionClass} },
            'MusicBrainz::Server::Action::Form';
    }

    $self->SUPER::create_action(%args);
}

=head2 submit_and_validate

Submit a form, and modify volatile privileges from form data. This
could mean changing the users temporary session privileges (disabling
auto-editing, for example).
=cut

sub submit_and_validate
{
    my ($self, $c) = @_;
    if ($c->form_posted && $self->form->validate($c->req->body_params))
    {
        if ($self->form->isa('MusicBrainz::Server::Form'))
        {
            $self->form->check_volatile_prefs($c);
        }

        return 1;
    }
    else
    {
        return;
    }
}

sub _insert_edit {
    my ($self, $c, $form, %opts) = @_;

    if (!$c->user->has_confirmed_email_address) {
        $c->detach('/error_401');
    }

    my $privs = $c->user->privileges;
    if ($form->field('make_votable') &&
        $form->field('make_votable')->value) {
        $privs |= $UNTRUSTED_FLAG;
    }

    my $edit;
    try {
        $edit = $c->model('Edit')->create(
            editor => $c->user,
            privileges => $privs,
            %opts
        );
    }
    catch {
        if (ref($_) eq 'MusicBrainz::Server::Edit::Exceptions::NoChanges') {
            $c->stash( makes_no_changes => 1 );
        }
        else {
            use Data::Dumper;
            croak "The edit could not be created.\n" .
                "POST: " . Dumper($c->req->params) . "\n" .
                "Exception:" . Dumper($_);
        }
    };

    if (defined $edit &&
            $form->does('MusicBrainz::Server::Form::Role::Edit') &&
            $form->field('edit_note')->value) {
        $c->model('EditNote')->add_note($edit->id, {
            text      => $form->field('edit_note')->value,
            editor_id => $c->user->id,
        });
    }

    if (defined $edit)
    {
      if (not defined $c->stash->{edit_ids}) {
        $c->stash->{edit_ids} = [ $edit->id ];
        $c->stash->{num_open_edits} = $edit->is_open;
      } else {
        push(@{$c->stash->{edit_ids}}, $edit->id );
        $c->stash->{num_open_edits}++ if $edit->is_open;
      }

      my %args = ( num_edits => scalar(@{$c->stash->{edit_ids}}),
                   num_open_edits => $c->stash->{num_open_edits} );
      my $first_edit_id = $c->stash->{edit_ids}->[0];
      my @edit_ids = @{$c->stash->{edit_ids}};
      $args{edit_ids} = "#" . join(", #", @edit_ids[0.. ($#edit_ids > 2 ? 2 : $#edit_ids)]) . ($#edit_ids>2?", ...":"");
      $args{edit_url} =
        (($args{num_edits} == 1)
         ? $c->uri_for_action('/edit/show', [ $first_edit_id ])
         : $c->uri_for_action('/edit/search',
                              { 'conditions.0.field'=>'id',
                                'conditions.0.operator'=>'BETWEEN',
                                'conditions.0.args.0'=>$first_edit_id,
                                'conditions.0.args.1'=>$c->stash->{edit_ids}->[-1]
                              }));

      if ($args{num_open_edits} == 0) {
        # All autoedits
        $c->flash->{message} =
          ln('Thank you, your {edit_url|edit} ({edit_ids}) has been automatically accepted and applied.',
             'Thank you, your {num_edits} {edit_url|edits} ({edit_ids}) have been automatically accepted and applied.',
             $args{num_edits}, \%args);
      } elsif ($args{num_open_edits} == $args{num_edits}) {
        # All open edits
        $c->flash->{message} =
          ln('Thank you, your {edit_url|edit} ({edit_ids}) has been entered into the edit queue for peer review.',
             'Thank you, your {num_edits} {edit_url|edits} ({edit_ids}) have been entered into the edit queue for peer review.',
             $args{num_edits}, \%args);
      } else {
        # Mixture of both
        # Even though the singular case is impossible (since 1 edit must be either an autoedit or open),
        # it is included since gettext uses the singular case as a key.
        $c->flash->{message} =
          ln('Thank you, your {edit_url|edit} ({edit_ids}) has been entered, with {num_open_edits} in the edit queue for peer review, and the rest automatically accepted and applied.',
             'Thank you, your {num_edits} {edit_url|edits} ({edit_ids}) have been entered, with {num_open_edits} in the edit queue for peer review, and the rest automatically accepted and applied.',
             $args{num_edits}, \%args);
      }
    }

    return $edit;
}

sub edit_action
{
    my ($self, $c, %opts) = @_;

    if (!$c->user->has_confirmed_email_address) {
        $c->detach('/error_401');
    }

    my %form_args = %{ $opts{form_args} || {}};
    $form_args{init_object} = $opts{item} if exists $opts{item};
    my $form = $c->form( form => $opts{form}, ctx => $c, %form_args );

    if ($c->form_posted && $form->submitted_and_valid($c->req->body_params)) {
        return if exists $opts{pre_creation} && !$opts{pre_creation}->($form);

        my @options = (map { $_->name => $_->value } $form->edit_fields);
        my %extra   = %{ $opts{edit_args} || {} };

        my $edit;
        $c->model('MB')->with_transaction(sub {
            $edit = $self->_insert_edit(
                $c, $form,
                edit_type => $opts{type},
                @options,
                %extra
            );

            # the on_creation hook is only called when an edit was entered.
            # the post_creation hook is always called.
            my $post_creation_changes = $opts{post_creation}->($edit, $form)
                if exists $opts{post_creation};

            $opts{on_creation}->($edit, $form) if $edit && exists $opts{on_creation};

            if ($post_creation_changes && $c->stash->{makes_no_changes}) {
                $c->stash( makes_no_changes => 0 );
            }
        });

        if ($opts{redirect} && !$opts{no_redirect} &&
                ($edit || !$c->stash->{makes_no_changes})) {
            $opts{redirect}->();
            $c->detach;
        }

        return $edit;
    }
    elsif (!$c->form_posted && %{ $c->req->query_params }) {
        my $merged = { ( %{$form->fif}, %{$c->req->query_params} ) };
        $form->process( params => $merged );
        $form->clear_errors;
    }
}

sub _search_final_page
{
    my ($self, $loader, $limit, $page) = @_;

    my $min = 1;
    my $max = $page;

    while (($max - $min) > 1)
    {
        my $middle = $min + (($max - $min) >> 1);

        my ($data, $total) = $loader->($limit, ($middle - 1) * $limit);
        if (scalar @$data > 0)
        {
            $min = $middle;
        }
        else
        {
            $max = $middle;
        }
    }

    return $min;
}


sub _load_paged
{
    my ($self, $c, $loader, %opts) = @_;

    my $prefix = $opts{prefix} || '';
    my $page = $c->request->query_params->{$prefix . "page"};
    $page = 1 unless is_positive_integer($page);

    my $LIMIT = $opts{limit} || $self->{paging_limit};

    my ($data, $total) = $loader->($LIMIT, ($page - 1) * $LIMIT);
    my $pager = Data::Page->new;

    if ($page > 1 && scalar @$data == 0)
    {
        my $page = $self->_search_final_page($loader, $LIMIT, $page);
        $c->response->redirect($c->request->uri_with({ ($prefix . 'page') => $page }));
        $c->detach;
    }

    $pager->entries_per_page($LIMIT);
    $pager->total_entries($total || 0);
    $pager->current_page($page);

    $c->stash( $prefix . "pager" => $pager,
               edit_count_limit => $EDIT_COUNT_LIMIT );
    return $data;
}

sub redirect_back
{
    my ($self, $c, $ignore, $fallback) = @_;

    my $url = $c->request->referer;

    $url = $c->uri_for($fallback)
        if !$url || $url =~ qr{$ignore};

    $c->response->redirect($url);
}

sub error {
    my ($self, $c, %args) = @_;
    my $status = $args{status} || 500;
    $c->response->status($status);
    $c->stash(
        template => "main/$status.tt",
        message => $args{message}
    );
    $c->detach;
}

1;
