package Bookmarks::List;

use Moose;

use Encode;
use HTTP::Date qw{time2iso time2isoz};

has bookmarks => (is => 'ro');
has query  => (is => 'ro');
has tags   => (
    is => 'ro',
    default => sub { [] },
);
has limit  => (is => 'ro');
has offset => (is => 'ro');
has results => ( is => 'ro' );
has title => (
    is => 'ro',
    builder => '_build_title',
    lazy => 1,
);

sub _get_list_links {
    my $self = shift;
    my ($self_type, $query) = @_;

    # remove extraneous blank ?q= parameters
    delete $query->{q} if defined $query->{q} && $query->{q} eq '';
    
    my @links = (
        {
            text => 'JSON',
            type => 'application/json',
            query => {
                %$query,
                format => 'json',
            },
        },
        {
            text => 'XBEL',
            type => 'application/xml',
            query => {
                %$query,
                format => 'xbel',
            },
        },
        {
            text => 'Atom',
            type => 'application/atom+xml',
            path => 'feed',
            query => {
                %$query,
            },
        },
        {
            text => 'CSV',
            type => 'text/csv',
            query => {
                %$query,
                format => 'csv',
            },
        },
        {
            text => 'URI List',
            type => 'text/uri-list',
            query => {
                %$query,
                format => 'text',
            },
        },
        {
            text => 'HTML',
            type => 'text/html',
            query => {
                %$query,
            },
        },
    );

    for my $link (@links) {
        $link->{rel}  = $link->{type} eq $self_type ? 'self' : 'alternate';
        $link->{href} = URI->new_abs($link->{path} || '', $self->bookmarks->base_uri);
        $link->{href}->query_form($link->{query});
    }

    return @links;
}

sub _build_title {
    my $self = shift;
    return 'Bookmarks' . (@{ $self->tags } ? " tagged as " . join(' & ', @{ $self->tags }) : '') . ($self->query ? " matching '" . $self->query . "'" : '');
}

sub as_json {
    my $self = shift;
    require JSON;
    my $json = decode_utf8(
        JSON->new->utf8->convert_blessed->encode({
            bookmarks => $self->results,
        })
    );
    return [200, ['Content-Type' => 'application/json; charset=UTF-8'], [$json]];
}

sub as_xbel {
    my $self = shift;
    require XML::XBEL;
    #TODO: conditional support; if XML::XBEL is not present, return a 5xx response

    my $xbel = XML::XBEL->new;

    $xbel->new_document({
        title => $self->title,
    });

    for my $bookmark (@{ $self->results }) {
        my $cdatetime = time2isoz $bookmark->ctime;
        my $mdatetime = time2isoz $bookmark->mtime;
        # make the timestamps W3C-correct
        s/ /T/ foreach ($cdatetime, $mdatetime);

        $xbel->add_bookmark({
            href     => $bookmark->uri,
            title    => $bookmark->title,
            desc     => 'Tags: ' . join(', ', @{ $bookmark->tags }),
            added    => $cdatetime,
            #XXX: are we sure that modified is the mtime of the bookmark or the resource?
            modified => $mdatetime,
        });
    }

    return [200, ['Content-Type' => 'application/xml; charset=UTF-8'], [$xbel->toString]];
}

sub as_text {
    my $self = shift;
    my $text = join '', 
        map {
            sprintf "# %s\n# Tags: %s\n%s\n",
            $_->title,
            join(', ', @{ $_->tags }), 
            $_->uri
        } @{ $self->results };
    return [200, ['Content-Type' => 'text/uri-list; charset=UTF-8'], [$text]];
}

sub as_csv {
    my $self = shift;
    require Text::CSV::Encoded;
    my $csv = Text::CSV::Encoded->new({ encoding_out => 'utf8' });
    my $text = qq{id,uri,title,tags,ctime,mtime\n};
    for my $bookmark (@{ $self->results }) {
        my $success = $csv->combine(
            $bookmark->id,
            $bookmark->uri,
            $bookmark->title,
            join(' ', @{ $bookmark->tags }),
            $bookmark->ctime,
            $bookmark->mtime,
        );
        $text .= $csv->string . "\n" if $success;
    }

    # include the local timestamp in the attchment filename
    my $dt = time2iso;
    $dt =~ s/[^\d]//g;

    my $filename = sprintf(
        'bookmarks-%s-%s.csv',
        join('_', @{ $self->tags }),
        $dt,
    );

    return [200, ['Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => sprintf('attachement; filename="%s"', $filename)], [$text]];
}

sub as_html {
    my $self = shift;

    require Template;
    my $template = Template->new;

    my @all_tags = $self->bookmarks->get_tags({ selected => @{ $self->tags }[0] });
    my @cotags = $self->bookmarks->get_cotags({
        query  => $self->query,
        tag    => $self->tags,
    });

    $template->process(
        'list.tt',
        {
            base_url     => $self->bookmarks->base_uri,
            title        => $self->title,
            query        => $self->query,
            selected_tag => @{ $self->tags }[0],
            search_tags  => $self->tags,
            links        => [ $self->_get_list_links('text/html', { q => $self->query, tag => $self->tags }) ],
            all_tags     => \@all_tags,
            cotags       => \@cotags,
            resources    => $self->results,
        },
        \my $output,
    );
    return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
}

sub as_atom {
    my $self = shift;

    require XML::Atom;
    $XML::Atom::DefaultVersion = "1.0";

    require XML::Atom::Feed;
    require XML::Atom::Entry;
    require XML::Atom::Link;
    require XML::Atom::Category;

    my $feed = XML::Atom::Feed->new;
    $feed->title($self->title);

    for my $link ($self->_get_list_links('application/atom+xml', { q => $self->query, tag => $self->tags })) {
        my $atom_link = XML::Atom::Link->new;
        $atom_link->type($link->{type});
        $atom_link->rel($link->{rel});
        $atom_link->href($link->{href}->canonical);
        $feed->add_link($atom_link);
    }

    for my $bookmark (@{ $self->results }) {
        my $entry = XML::Atom::Entry->new;
        $entry->id($bookmark->bookmark_uri->canonical);
        $entry->title($bookmark->title);
        
        my $link = XML::Atom::Link->new;
        $link->href($bookmark->uri);
        $entry->add_link($link);
        
        $entry->summary('Tags: ' . join(', ', @{ $bookmark->tags }));

        my $cdatetime = time2isoz $bookmark->ctime;
        my $mdatetime = time2isoz $bookmark->mtime;
        # make the timestamp W3C-correct
        s/ /T/ foreach ($cdatetime, $mdatetime);
        $entry->published($cdatetime);
        $entry->updated($mdatetime);
        
        for my $tag (@{ $bookmark->tags }) {
            my $category = XML::Atom::Category->new;
            $category->term($tag);
            $entry->add_category($category);
        }

        $feed->add_entry($entry);
    }

    return [200, ['Content-Type' => 'application/atom+xml; charset=UTF-8'], [$feed->as_xml]];
}

1;
