package Kwiki::Atom;
use strict;
use warnings;
use Kwiki::Plugin '-Base';
use Kwiki::Display;
use mixin 'Kwiki::Installer';
our $VERSION = '0.15';

use XML::Atom;
use XML::Atom::Feed;
use XML::Atom::Link;
use XML::Atom::Entry;
use XML::Atom::Content;

use DateTime;
use Kwiki::Atom::Server;

use constant ATOM_TYPE => "application/atom+xml";

const class_id      => 'atom';
const class_title   => 'Atom';
const css_file      => 'atom.css';
const config_file   => 'atom.yaml';
const cgi_class     => 'Kwiki::Atom::CGI';
const server_class  => 'Kwiki::Atom::Server';

field depth => 0;
field 'headers';
field 'server';

sub process {
}

sub register {
    my $registry = shift;
    $registry->add(action => 'atom_edit');
    $registry->add(action => 'atom_feed');
    $registry->add(action => 'atom_post');
    $registry->add(toolbar => 'recent_changes_atom_button', 
                   template => 'recent_changes_atom_button.html',
                   show_for => ['recent_changes'],
                  );
    $registry->add(toolbar => 'edit_atom_button', 
                   template => 'edit_atom_button.html',
                   show_for => ['display'],
                   params_class => $self->class_id,
                  );
}

sub fill_links {
    my $name = eval { $self->hub->cgi->page_name };
    my $url = CGI->new->url;
    push @{ $self->hub->{links}{all} }, ($name ? {
        rel => 'alternate',
        type => ATOM_TYPE,
        href => "$url?action=atom_edit;page_name=". $self->pages->current->uri,
    } : ()), {
        rel => 'service.feed',
        type => ATOM_TYPE,
        href => "$url?action=atom_feed",
    }, {
        rel => 'service.post',
        type => ATOM_TYPE,
        href => "$url?action=atom_post",
    };
    return;
}

sub toolbar_params {
#    require YAML;
#    open X, '>>/tmp/post.log';
#    print X "POSTDATA:\n", $self->cgi->POSTDATA, "\n";
#    print X "HEADERS:\n", YAML::Dump(\%ENV), $/;
#    close X;
    return () unless $ENV{CONTENT_TYPE} and
                    ($ENV{CONTENT_TYPE} eq ATOM_TYPE
                  or $ENV{CONTENT_TYPE} =~ m{^\w+/xml}); # XXX ecto XXX

    $self->atom_post;

    my %header = &Spoon::Cookie::content_type;
    print CGI::header(%header);
    print $self->server->print;
    exit;
}

sub fill_header {
    $self->wrap_header if !$self->headers;
    $self->headers( [ @{$self->headers||[]}, @_ ] );
}

sub wrap_header {
    my $server = $self->server($self->server_class->new);

    my %accept = map { $_ => 1 }
                 $server->request_header('Accept') =~ m{([^\s,]+/[^;,]+)}g;
    my $content_type = 'text/xml'; # fallback
    foreach my $try_type (qw(
        application/atom+xml
        application/x.atom+xml
        application/xml
    )) {
        $accept{$try_type} or next;
        $content_type = $try_type;
        last;
    }

    $content_type .= '; charset=UTF-8';
    $server->response_content_type($content_type);
    $server->client($self);

    $self->hub->headers->content_type($content_type);
}

sub make_entry {
    my ($page, $depth, $flavor) = @_;
    my $url = $self->server->uri;

    my $author = XML::Atom::Person->new;
    $author->name($page->metadata->edit_by);

    my $link_html = XML::Atom::Link->new;
    $link_html->type('text/html');
    $link_html->rel('alternate');
    $link_html->href("$url?".$page->uri);
    $link_html->title('');

    my $link_edit = XML::Atom::Link->new;
    $link_edit->type(ATOM_TYPE);
    $link_edit->rel('service.edit');
    $link_edit->href("$url?action=atom_edit;page_name=".$page->uri);
    $link_edit->title('');

    my $entry = XML::Atom::Entry->new;
    $entry->title($page->title);

    my $content = XML::Atom::Content->new;
    my $elem = $content->elem;
    my $text = ($content->LIBXML) ? 'XML::LibXML::Text'
                                  : 'XML::XPath::Node::Text';
    if ($flavor and $flavor eq 'html')  {
        $content->type('text/html');

        my $data = $page->to_html;
        my $copy = qq(<div xmlns="http://www.w3.org/1999/xhtml">$data</div>);
        my $node;
        local $@;
        eval {
            if ($content->LIBXML) {
                require XML::LibXML;
                my $parser = XML::LibXML->new;
                my $tree = $parser->parse_string($copy);
                $node = $tree->getDocumentElement;
            } else {
                require XML::XPath;
                my $xp = XML::XPath->new(xml => $copy);
                $node = (($xp->find('/')->get_nodelist)[0]->getChildNodes)[0]
                    if $xp;
            }
        };
        if (!$@ && $node) {
            $elem->appendChild($node);
            $elem->setAttribute('mode', 'xml');
        } else {
            $elem->appendChild($text->new($data));
            $elem->setAttribute('mode', 'escaped');
        }
    }
    else {
        $content->type('text/plain');
        $elem->appendChild($text->new($page->content));
        $elem->setAttribute('mode', 'escaped');
    }

    $entry->content($content);
    $entry->summary('');
    $entry->issued( DateTime->from_epoch( epoch => $page->io->ctime || time )->iso8601 . 'Z' );
    $entry->modified( DateTime->from_epoch( epoch => $page->io->mtime || time )->iso8601 . 'Z' );
    $entry->id("$url?".$page->uri);

    $entry->author($author);
    $entry->add_link($link_html);
    $entry->add_link($link_edit);

    return $entry;
}

sub update_page {
    my $page = shift;
    my $method = $self->server->request_method;
    my $entry = eval { $self->server->atom_body };

    if (!$entry) {
        print "Status: 400\n\n";
        $self->fill_header( -status => 400 );
        return;
    }

    if (!$page) {
        my $title = $entry->title;
        if ($entry->content->type =~ /\bx?html\b/i) {
            require HTML::Entities;
            HTML::Entities::decode_entities($title);
        }
        $page = $self->pages->new_page($title);

        if ($page->exists and $method eq 'POST') {
            $self->server->response_code(409);
            $self->server->{_error} = 'This page already exists';
            $self->fill_header(
                -status => 409,
                -type => 'text/plain',
                -warning => 'This page already exists',
            );
            return undef;
        }
    }

    $self->hub->users->current->name(
        eval { $self->server->get_auth_info->{Username} }
            || $self->hub->config->user_default_name
    );
    my $body = $entry->content->body;
    if ($entry->content->type =~ /\bx?html\b/i) {
        $body =~ s/<[^>]+>//g;
        require HTML::Entities;
        HTML::Entities::decode_entities($body);
    }
    $page->content($body);
    $page->update->store;

    return $page;
}

sub atom_list {
    my $url = $self->server->uri;

    my $link_feed = XML::Atom::Link->new;
    $link_feed->type(ATOM_TYPE);
    $link_feed->rel('service.feed');
    $link_feed->title($self->config->site_title);
    $link_feed->href("$url?action=atom_feed");

    my $link_post = XML::Atom::Link->new;
    $link_post->type(ATOM_TYPE);
    $link_post->rel('service.post');
    $link_post->title($self->config->site_title);
    $link_post->href("$url?action=atom_post");

    my $feed = XML::Atom::Feed->new;
    $feed->title($self->config->site_title);
    $feed->info($self->config->site_title);
    $feed->add_link($link_feed);
    $feed->add_link($link_post);
    $feed->modified(DateTime->now->iso8601 . 'Z');

    $self->munge($feed->as_xml);
}

sub atom_post {
    $self->fill_header;
    return $self->atom_list if $self->server->request_method eq 'GET';

    $self->server->{request_content} = $self->cgi->POSTDATA
        if $self->server->request_method eq 'POST';

    $self->server->run;
    $self->server->print;
}

sub atom_edit {
    $self->fill_header;
    $self->server->run;
    $self->server->print;
}

sub atom_feed {
    $self->fill_header;

    my $depth = $self->cgi->depth;
    my $flavor = $self->cgi->flavor;
    my $pages = [
        sort {
            $b->modified_time <=> $a->modified_time 
        } ($depth ? $self->pages->recent_by_count($depth) : $self->pages->all)
    ];

    my $timestamp = @$pages ? $pages->[0]->metadata->edit_unixtime : time;

    my $cache = eval { $self->hub->load_class('cache') }
      or return $self->generate($pages, $depth, $flavor, $timestamp);

    $cache->process(
        sub { $self->generate($pages, $depth, $flavor, $timestamp) },
        'atom', $depth, $flavor, $timestamp, int(time / 600)
    );
}

sub generate {
    my ($pages, $depth, $flavor, $timestamp) = @_;

    my $datetime = DateTime->from_epoch( epoch => $timestamp );
    my $url = $self->server->uri;
    my $link_html = XML::Atom::Link->new;
    $link_html->type('text/html');
    $link_html->rel('alternate');
    $link_html->title($self->config->site_title);
    $link_html->href($url);

    my $link_post = XML::Atom::Link->new;
    $link_post->type('application/atom+xml');
    $link_post->rel('service.post');
    $link_post->title($self->config->site_title);
    $link_post->href("$url?action=atom_post");

    my $feed = XML::Atom::Feed->new;
    $feed->title($self->config->site_title);
    $feed->info($self->config->site_title);
    $feed->add_link($link_html);
    $feed->add_link($link_post);
    $feed->modified($datetime->iso8601 . 'Z');

    my $author = XML::Atom::Person->new;
    $author->name($self->config->site_url);

    $self->config->script_name($url);

    for my $page (@$pages) {
        $feed->add_entry( $self->make_entry($page, $depth, $flavor) );
    }

    $self->munge($feed->as_xml);
}

sub munge {
    my $xml = shift;
    $xml =~ /<\?xml/ or $xml = qq(<?xml version="1.0"?>$xml);
    $xml =~ s/version="1.0"(?![^>]*encoding=)/version="1.0" encoding="UTF-8"/;
    $xml =~ s/(<\w+)/$1 version="0.3"/;
    $xml =~ s{\?>}{?><?xml-stylesheet type="text/css" href="css/atom.css"?>};
    return $xml;
}

package Kwiki::Atom::CGI;
use Kwiki::CGI '-base';

cgi 'depth';
cgi 'flavor';
cgi 'POSTDATA';

1;

package Kwiki::Atom;

1;

__DATA__

=head1 NAME 

Kwiki::Atom - Kwiki Atom Plugin

=head1 VERSION

This document describes version 0.15 of Kwiki::Atom, released
April 1, 2005.

=head1 SYNOPSIS

    % cd /path/to/kwiki
    % kwiki -add Kwiki::Atom

=head1 DESCRIPTION

This Kwiki plugin provides Atom 0.3 integration with Kwiki.

If you plan to offer your Atom feeds to the public, please consider installing
the B<Kwiki::Cache> module, which can significantly reduce the server load.

For more info about this kind of integration, please refer to
L<http://www.xml.com/pub/a/2004/04/14/atomwiki.html>.

Currently, this plugin has been tested with the following AtomAPI clients:

=over 4

=item * wxAtomClient.py

L<http://piki.bitworking.org/piki.cgi>

=item * ecto

L<http://ecto.kung-foo.tv/>

=back

=head1 AUTHOR

Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>

=head1 COPYRIGHT

Copyright 2004, 2005 by Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

See http://www.perl.com/perl/misc/Artistic.html

=cut
__config/atom.yaml__
site_description: The Kwiki Wiki
site_url: http://localhost/par/
__template/tt2/recent_changes_atom_button.html__
<!-- BEGIN recent_changes_atom_button.html -->
<a href="[% script_name %]?action=atom_feed;depth=15;flavor=html" title="AtomFeed">
[% INCLUDE recent_changes_atom_button_icon.html %]
</a>
<!-- END recent_changes_atom_button.html -->
__template/tt2/recent_changes_atom_button_icon.html__
<!-- BEGIN recent_changes_atom_button_icon.html -->
Atom
<!-- END recent_changes_atom_button_icon.html -->
__icons/gnome/template/recent_changes_atom_button_icon.html__
<!-- BEGIN recent_changes_atom_button_icon.html -->
<img src="icons/gnome/image/atom_feed.png" alt="Atom" />
<!-- END recent_changes_atom_button_icon.html -->
__icons/gnome/image/atom_feed.png__
iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPBAMAAADJ+Ih5AAAAMFBMVEX////yZ2fh4eH5+fm+
v7/r6+u3SEjV1dWmenqzs7PPz8/Hx8elpaXGxsbMzMyUlJQfgNlcAAAAAXRSTlMAQObYZgAA
ABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjQuNqQzgxcAAAB7SURBVHjaY2DAAEy3d++NelfM
wBBrwHzziuodA4ZFDOvKy51YFjBctdVqy7iieoCh6JGFYFqr7wQGewU1QbE8/g0MlUBGWpb9
BIbNSy3S0lqnTmC4YLeyLeNIwQGGCwxaV+ZMYjjA8IiB+c7PUJ0CBrvbWyZZvduKsBMAMi0q
dW1+s4IAAAAASUVORK5CYII=
__template/tt2/edit_atom_button.html__
<!-- BEGIN edit_atom_button.html -->
<a href="[% script_name %]?action=atom_edit;page_name=[% page_uri %]" title="AtomEdit">
[% INCLUDE edit_atom_button_icon.html %]
</a>
<!-- END edit_button.html -->
__template/tt2/edit_atom_button_icon.html__
<!-- BEGIN edit_atom_button_icon.html -->
Atom
<!-- END edit_atom_button_icon.html -->

__icons/gnome/template/edit_atom_button_icon.html__
<!-- BEGIN edit_atom_button_icon.html -->
<img src="icons/gnome/image/atom_edit.png" alt="Atom" />
<!-- END edit_atom_button_icon.html -->

__icons/gnome/image/atom_edit.png__
iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPBAMAAADJ+Ih5AAAAMFBMVEX////yZ2fh4eH5+fm+
v7/r6+u3SEjV1dWmenqzs7PPz8/Hx8elpaXGxsbMzMyUlJQfgNlcAAAAAXRSTlMAQObYZgAA
ABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjQuNqQzgxcAAAB7SURBVHjaY2DAAEy3d++NelfM
wBBrwHzziuodA4ZFDOvKy51YFjBctdVqy7iieoCh6JGFYFqr7wQGewU1QbE8/g0MlUBGWpb9
BIbNSy3S0lqnTmC4YLeyLeNIwQGGCwxaV+ZMYjjA8IiB+c7PUJ0CBrvbWyZZvduKsBMAMi0q
dW1+s4IAAAAASUVORK5CYII=
__template/tt2/kwiki_begin.html__
<!-- BEGIN kwiki_begin.html -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>
[% IF hub.action == 'display' || 
      hub.action == 'edit' || 
      hub.action == 'revisions' 
%]
  [% hub.cgi.page_name %] -
[% END %]
[% IF hub.action != 'display' %]
  [% self.class_title %] - 
[% END %]
  [% site_title %]</title>
[% hub.load_class('atom').fill_links %]
[% FOR link = hub.links.all -%]
  <link rel="[% link.rel %]" type="[% link.type %]" href="[% link.href %]" />
[% END %]
[% FOR css_file = hub.css.files -%]
  <link rel="stylesheet" type="text/css" href="[% css_file %]" />
[% END -%]
[% FOR javascript_file = hub.javascript.files -%]
  <script type="text/javascript" src="[% javascript_file %]"></script>
[% END -%]
  <link rel="shortcut icon" href="" />
  <link rel="start" href="[% script_name %]" title="Home" />
</head>
<body>
<!-- END kwiki_begin.html -->
__css/atom.css__
feed {
  display:block;
  font-family:verdana, sans-serif;
  margin:2%;
  font-size:90%;
  color:#000000;
  background:#ffffff;
}

title {
  display:block;
  font-size:1.3em;
  color:inherit;
  background:inherit;
  font-weight:bold;
}

tagline, link {
  display:block;
  font-size:0.9em;
}

id, modified, url {
  display:none;
}

generator {
  display:block;
  font-size:0.9em;
}

info {
  display:block;
  margin:3em 4em 3em 4em;
  color:#CC3333;
  background:#FFFF66;
  border:solid #CCCC66 2px;
  text-align:center;
  padding:1.5em;
  font-family:mono;
  font-size:0.8em;
}

entry {
  display:block;
  color:inherit;
  background:inherit;
  padding:0;
  margin:1em 1em 2em 1em;
  
}

entry modified, entry name {
  display:inline;
  color:#999999;
  background:inherit;
  font-size:0.8em;
}

entry created, entry issued, entry id {
  display:none;
}

entry title {
  display:block;
  font-size:1em;
  font-weight:bold;
  color:inherit;
  background:inherit;
  padding:1em 1em 0em 1em;
  margin:0;
  border-top:solid 1px #dddddd;
}

img.floatright {
  padding-left: 1em;
  float: right;
}

img.floatleft {
  float: left;
  padding-right: 1em;
  padding-bottom: 0.2em;
}

summary {
  display:block;
  background: #FFFF88;
  font-size:0.9em;
  color:inherit;
  margin:1em;
  line-height:1.5em;
}

content {
  display:block;
  font-size:0.9em;
  color:inherit;
  background:inherit;
  margin:1em;
  line-height:1.5em;
}