package Silki::Controller::Wiki;
BEGIN {
  $Silki::Controller::Wiki::VERSION = '0.09';
}

use strict;
use warnings;
use namespace::autoclean;

use DateTime::Format::W3CDTF 0.05;
use Email::Address;
use File::Basename qw( dirname );
use Path::Class ();
use Silki::Config;
use Silki::Formatter::HTMLToWiki;
use Silki::I18N qw( loc );
use Silki::Schema::Page;
use Silki::Schema::Role;
use Silki::Schema::Wiki;
use Silki::Util qw( string_is_empty );
use XML::Atom::SimpleFeed;

use Moose;

BEGIN { extends 'Silki::Controller::Base' }

with qw(
    Silki::Role::Controller::Pager
    Silki::Role::Controller::RevisionsAtomFeed
    Silki::Role::Controller::User
    Silki::Role::Controller::WikitextHandler
);

sub _set_wiki : Chained('/') : PathPart('wiki') : CaptureArgs(1) {
    my $self      = shift;
    my $c         = shift;
    my $wiki_name = shift;

    my $wiki = Silki::Schema::Wiki->new( short_name => $wiki_name );

    $c->redirect_and_detach( $c->domain()->uri( with_host => 1 ) )
        unless $wiki;

    $self->_require_permission_for_wiki( $c, $wiki, 'Read' );

    my $front_page = Silki::Schema::Page->new(
        title   => $wiki->front_page_title(),
        wiki_id => $wiki->wiki_id(),
    );

    $c->add_tab($_)
        for (
        {
            uri     => $wiki->uri(),
            label   => $wiki->title(),
            tooltip => loc( '%1 dashboard', $wiki->title() ),
            id      => 'dashboard',
        }, {
            uri     => $front_page->uri(),
            label   => loc('Front Page'),
            tooltip => loc( '%1 Front Page', $wiki->title() ),
            id      => 'front-page',
        }, {
            uri     => $wiki->uri( view => 'recent' ),
            label   => loc('Recent Changes'),
            tooltip => loc('Recent activity in this wiki'),
            id      => 'recent-changes',
        },
        );

    $c->stash()->{wiki} = $wiki;
}

sub wiki : Chained('_set_wiki') : PathPart('') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub wiki_GET_html {
    my $self = shift;
    my $c    = shift;

    my $uri = $c->stash()->{wiki}->uri( view => 'dashboard' );

    $c->redirect_and_detach($uri);
}

sub delete_confirmation : Chained('_set_wiki') : PathPart('delete_confirmation') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    $c->stash()->{template} = '/wiki/delete-confirmation';
}

sub wiki_DELETE {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    my $wiki = $c->stash()->{wiki};

    my $msg = loc( 'Deleted the wiki %1', $wiki->title() );

    my $domain = $wiki->domain();

    $wiki->delete( user => $c->user() );

    $c->session_object()->add_message($msg);

    $c->redirect_and_detach( $domain->uri( with_host => 1 ) );
}

sub dashboard : Chained('_set_wiki') : PathPart('dashboard') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $c->tab_by_id('dashboard')->set_is_selected(1);

    my $wiki = $c->stash()->{wiki};

    $c->stash()->{changes} = $wiki->revisions( limit => 10 );

    $c->stash()->{views} = $wiki->recently_viewed_pages( limit => 10 );

    $c->stash()->{tags} = $wiki->popular_tags( limit => 10 );

    $c->stash()->{users} = $wiki->active_users( limit => 10 );

    $c->stash()->{template} = '/wiki/dashboard';
}

sub recent : Chained('_set_wiki') : PathPart('recent') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $c->tab_by_id('recent-changes')->set_is_selected(1);

    my $wiki = $c->stash()->{wiki};

    my ( $limit, $offset )
        = $self->_make_pager( $c, $wiki->revision_count() );

    $c->stash()->{pages} = $wiki->revisions(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/recent';
}

sub recent_atom : Chained('_set_wiki') : PathPart('recent.atom') : Args(0) {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $revisions = $wiki->revisions( limit => 50 );

    $self->_output_atom_feed_for_revisions(
        $c,
        $revisions,
        loc( 'Recent Changes in %1', $wiki->title() ),
        $wiki->uri( view => 'recent',      with_host => 1 ),
        $wiki->uri( view => 'recent.atom', with_host => 1 ),
    );
}

sub attachments : Chained('_set_wiki') : PathPart('attachments') : Args(0) {
    my $self = shift;
    my $c    = shift;

    my $count = $c->stash()->{wiki}->file_count();

    my ( $limit, $offset ) = $self->_make_pager( $c, $count );

    $c->stash()->{file_count} = $count;
    $c->stash()->{files}      = $c->stash()->{wiki}->files(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/attachments';
}

sub file_collection : Chained('_set_wiki') : PathPart('files') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub file_collection_POST {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Upload' );

    my $upload = $c->request()->upload('file');

    $self->_handle_upload(
        $c,
        $upload,
        $c->stash()->{wiki}->uri( view => 'attachments' ),
    );

    $c->session_object()->add_message( loc('The file has been uploaded.' ) );
    $c->redirect_and_detach( $c->stash()->{wiki}->uri( view => 'attachments' ) );
}

sub orphans : Chained('_set_wiki') : PathPart('orphans') : Args(0) {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $count = $wiki->orphaned_page_count();

    my ( $limit, $offset ) = $self->_make_pager( $c, $count );

    $c->stash()->{orphan_count} = $count;
    $c->stash()->{orphans}      = $wiki->orphaned_pages(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/orphans';
}

sub wanted : Chained('_set_wiki') : PathPart('wanted') : Args(0) {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $count = $wiki->wanted_page_count();

    my ( $limit, $offset ) = $self->_make_pager( $c, $count );

    $c->stash()->{wanted_count} = $count;
    $c->stash()->{wanted}       = $wiki->wanted_pages(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/wanted';
}

sub users : Chained('_set_wiki') : PathPart('users') : Args(0) {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $count = $wiki->member_count();

    my ( $limit, $offset ) = $self->_make_pager( $c, $count );

    $c->stash()->{users} = $wiki->members(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/users';
}

sub settings : Chained('_set_wiki') : PathPart('settings') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Manage' );

    $c->stash()->{template} = '/wiki/settings';
}

sub permissions_form : Chained('_set_wiki') : PathPart('permissions_form') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Manage' );

    $c->stash()->{template} = '/wiki/permissions-form';
}

sub permissions : Chained('_set_wiki') : PathPart('permissions') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub permissions_PUT {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    $self->_require_permission_for_wiki( $c, $wiki, 'Manage' );

    my $perms = $c->request()->params()->{permissions};

    $wiki->set_permissions($perms);

    my $perm_loc = loc($perms);
    $c->session_object()->add_message( loc('Permissions for this wiki have been set to %1', $perm_loc ) );

    $c->redirect_and_detach( $wiki->uri( view => 'permissions_form' ) );
}

sub members_form : Chained('_set_wiki') : PathPart('members_form') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Manage' );

    $c->stash()->{members} = $c->stash()->{wiki}->members();

    $c->stash()->{template} = '/wiki/members-form';
}

sub members : Chained('_set_wiki') : PathPart('members') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub members_PUT {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    $self->_require_permission_for_wiki( $c, $wiki, 'Manage' );

    $self->_process_existing_member_changes($c);
    $self->_process_new_members($c);

    $c->redirect_and_detach( $wiki->uri( view => 'members_form' ) );
}

sub _process_existing_member_changes {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    for my $user_id ( $c->request()->param('members') ) {
        next if $user_id == $c->user()->user_id();

        my $role_id = $c->request()->params()->{ 'role_for_' . $user_id };

        my $user = Silki::Schema::User->new( user_id => $user_id );
        if ( !$role_id ) {
            $wiki->remove_user( user => $user );
            $c->session_object()
                ->add_message(
                loc( '%1 was removed as a wiki member.', $user->best_name() )
                );
        }
        else {
            my $role = Silki::Schema::Role->new( role_id => $role_id );
            my $current_role = $user->role_in_wiki($wiki);
            next if $role->role_id() == $current_role->role_id();

            $wiki->add_user( user => $user, role => $role );
            if ( $role->name eq 'Admin' ) {
                $c->session_object()->add_message(
                    loc(
                        '%1 is now an admin for this wiki.',
                        $user->best_name()
                    )
                );
            }
            else {
                $c->session_object()->add_message(
                    loc(
                        '%1 is no longer an admin for this wiki.',
                        $user->best_name()
                    )
                );
            }
        }
    }
}

sub _process_new_members {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $params = $c->request()->params();

    for my $address ( Email::Address->parse( $params->{new_members} ) ) {
        my %message
            = string_is_empty( $params->{message} )
            ? ()
            : ( message => $params->{message} );

        my $user = Silki::Schema::User->new( email_address => $address->address() );
        if ($user) {
            if ( $user->is_wiki_member($wiki) ) {
                $c->session_object()->add_message(
                    loc(
                        '%1 is already a member of this wiki.',
                        $user->best_name()
                    )
                );
                next;
            }

            if ( $user->requires_activation() ) {
                $c->session_object()->add_message(
                    loc(
                        'An unactived account for %1 already exists. Once the account is activated, this user will be able to access this wiki.',
                        $user->best_name()
                    )
                );
            }
            else {
                $c->session_object()->add_message(
                    loc(
                        '%1 is now a member of this wiki.',
                        $user->best_name()
                    )
                );
            }

            $user->send_invitation_email(
                wiki   => $wiki,
                sender => $c->user(),
                %message,
            );
        }
        else {
            $user = Silki::Schema::User->insert(
                requires_activation => 1,
                disable_login       => 1,
                email_address       => $address->address(),
                (
                    $address->phrase()
                    ? ( display_name => $address->phrase() )
                    : ()
                ),
                user => $c->user(),
            );

            $user->send_activation_email(
                wiki   => $wiki,
                sender => $c->user(),
                %message,
            );

            $c->session_object()->add_message(
                loc(
                    'A user account for %1 has been created, and this person has been invited to join this wiki.',
                    $address->address()
                )
            );
        }

        $wiki->add_user(
            user => $user,
            role => Silki::Schema::Role->Member(),
        );
    }
}

sub new_page_form : Chained('_set_wiki') : PathPart('new_page_form') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Edit' );

    $c->stash()->{title}    = $c->request()->params()->{title};
    $c->stash()->{template} = '/wiki/new-page-form';
}

sub page_collection : Chained('_set_wiki') : PathPart('pages') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub page_collection_POST {
    my $self = shift;
    my $c    = shift;

    $self->_require_permission_for_wiki( $c, $c->stash()->{wiki}, 'Edit' );

    my $wiki = $c->stash()->{wiki};

    my $wikitext = eval { $self->_wikitext_from_form( $c, $wiki ) };

    if ( my $e = $@ ) {
        $c->redirect_with_error(
            errors    => $e,
            uri       => $wiki->uri( view => 'new_page_form' ),
            form_data => $c->request()->params(),
        );
    }

    my $page = Silki::Schema::Page->insert_with_content(
        title   => $c->request()->params()->{title},
        content => $wikitext,
        wiki_id => $wiki->wiki_id(),
        user_id => $c->user()->user_id(),
    );

    $c->redirect_and_detach( $page->uri() );
}

sub _set_user : Chained('_set_wiki') : PathPart('user') : CaptureArgs(1) {
}

sub _make_user_uri {
    my $self = shift;
    my $c    = shift;
    my $user = shift;
    my $view = shift || undef;

    my $real_view = 'user/' . $user->user_id();
    $real_view .= q{/} . $view if defined $view;

    return $c->stash()->{wiki}->uri( view => $real_view );
}

sub search : Chained('_set_wiki') : PathPart('search') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub search_GET_html {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    my $search = $c->request()->params()->{search};

    $c->redirect_and_detach( $wiki->uri() )
        if string_is_empty($search);

    ( my $pg_query = $search ) =~ s/\s+/ & /g;

    $c->stash()->{search_results} = $wiki->text_search( query => $pg_query );
    $c->stash()->{search} = $search;

    $c->stash()->{template} = '/wiki/search-results';
}

sub tag_collection : Chained('_set_wiki') : PathPart('tags') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub tag_collection_GET_html {
    my $self = shift;
    my $c    = shift;

    my $wiki = $c->stash()->{wiki};

    $c->stash()->{tag_count} = $wiki->tag_count();
    $c->stash()->{tags} = $wiki->popular_tags()
        if $c->stash()->{tag_count};

    $c->stash()->{template} = '/wiki/tags';
}

sub tag : Chained('_set_wiki') : PathPart('tag') : Args(1) : ActionClass('+Silki::Action::REST') {
}

sub tag_GET_html {
    my $self = shift;
    my $c    = shift;
    my $tag  = shift;

    my $wiki = $c->stash()->{wiki};

    $c->stash()->{tag} = $tag;
    $c->stash()->{page_count} = $wiki->pages_tagged_count( tag => $tag );
    $c->stash()->{pages} = $wiki->pages_tagged( tag => $tag )
        if $c->stash()->{page_count};

    $c->stash()->{template} = '/wiki/tag';
}

sub new_wiki_form : Path('/new_wiki_form') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    $c->stash()->{template} = '/wiki/new-wiki-form';
}

sub wiki_collection : Path('/wikis') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub wiki_collection_GET_html {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    my ( $limit, $offset ) = $self->_make_pager( $c, Silki::Schema::Wiki->Count() );

    $c->stash()->{wikis} = Silki::Schema::Wiki->All(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/wiki/wikis';
}

sub wiki_collection_POST {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    my %form_data = $c->request()->wiki_params();
    my $perms = $c->request()->params()->{permissions};

    my $wiki;

    eval {
        Silki::Schema->RunInTransaction(
            sub {
                $wiki = Silki::Schema::Wiki->insert(
                    %form_data,
                    user => $c->user(),
                );

                $wiki->set_permissions($perms);

                $wiki->add_user(
                    user => $c->user(),
                    role => Silki::Schema::Role->Admin(),
                );
            }
        );
    };

    if ( my $e = $@ ) {
        $c->redirect_with_error(
            error => $e,
            uri   => $c->domain()->application_uri( path => 'new_wiki_form' ),
            form_data => { %form_data, permissions => $perms },
        );
    }

    $c->redirect_and_detach( $wiki->uri() );
}

__PACKAGE__->meta()->make_immutable();

1;

# ABSTRACT: Controller class for wikis

__END__
=pod

=head1 NAME

Silki::Controller::Wiki - Controller class for wikis

=head1 VERSION

version 0.09

=head1 AUTHOR

  Dave Rolsky <autarch@urth.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2010 by Dave Rolsky.

This is free software, licensed under:

  The GNU Affero General Public License, Version 3, November 2007

=cut

