package Bookmarks::Controller;

use Moose;

use Encode;
use HTTP::Date qw{time2str str2time};
use JSON;
use Bookmarks;
use URI;
use Template;

has dbname => (
    is => 'ro',
    required => 1,
);
has bookmarks => (
    is => 'ro',
    handles => [qw{get_bookmark}],
    builder => '_build_bookmarks',
    lazy => 1,
);
has base_uri => (
    is => 'ro',
    builder => '_build_base_uri',
    lazy => 1,
);
has request => (
    is => 'ro',
    required => 1,
);

sub _build_bookmarks {
    my $self = shift;
    return Bookmarks->new({
        dbname   => $self->dbname,
        base_uri => $self->base_uri,
    });
}

sub _build_base_uri {
    my $self = shift;
    my $url = $self->request->base;

    $url .= '/' unless $url =~ m{/$};
    return URI->new($url);
}

sub find_or_new {
    my $self = shift;

    my $bookmark = $self->bookmarks->get_bookmark({ uri => $self->request->param('uri') });
    if ($bookmark) {
        # redirect to the view of the existing bookmark
        return [301, [Location => $bookmark->bookmark_uri], []];
    } else {
        # bookmark was not found; show the form to create a new bookmark
        my $template = Template->new;
        $template->process(
            'bookmark.tt',
            { 
                bookmark => {
                    uri   => $self->request->param('uri'),
                    title => $self->request->param('title') || '',
                },
            },
            \my $output,
        );
        return [404, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
    }
}

sub list {
    my $self = shift;

    # list all the bookmarks 
    my $mtime = $self->bookmarks->get_last_modified_time;

    my $format = $self->request->param('format') || 'html';

    my @tags = grep { $_ ne '' } $self->request->param('tag');
    my $query = $self->request->param('q');
    my $limit = $self->request->param('limit');
    my $offset = $self->request->param('offset');

    my $list = $self->bookmarks->get_bookmarks({
        query  => $query,
        tag    => \@tags,
        limit  => $limit,
        offset => $offset,
    });

    my $as_format = "as_$format";
    if (!$list->meta->has_method($as_format)) {
        return [406, ['Content-Type' => 'text/plain; charset=UTF-8'], [qq{"$format" is not a supported format}]];
    }
    return $list->$as_format;
}

sub feed {
    my $self = shift;

    my $query = $self->request->param('q');
    my @tags = grep { $_ ne '' } $self->request->param('tag');

    # construct a feed from the most recent 12 bookmarks
    my $list = $self->bookmarks->get_bookmarks({ query => $query, tag => \@tags, limit => 12 });
    return $list->as_atom;
}

# returns 1 if there is an If-Modified-Since header and it is newer than the given $mtime
# returns 0 if there is an If-Modified-Since header but the $mtime is newer
# returns undef if there is no If-Modified-Since header
sub _request_is_newer_than {
    my $self = shift;
    my $mtime = shift;

    # check If-Modified-Since header to return cache response
    if ($self->request->env->{HTTP_IF_MODIFIED_SINCE}) {
        my $cache_time = str2time($self->request->env->{HTTP_IF_MODIFIED_SINCE});
        return $mtime <= $cache_time ? 1 : 0;
    } else {
        return;
    }
}

sub view {
    my ($self, $id) = @_;

    my $format = $self->request->param('format') || 'html';

    my $bookmark = $self->get_bookmark({ id => $id });
    if ($bookmark) {
        return [304, [], []] if $self->_request_is_newer_than($bookmark->mtime);

        my $last_modified = time2str($bookmark->mtime);
        
        if ($format eq 'json') {
            my $json = decode_utf8(JSON->new->utf8->convert_blessed->encode($bookmark));
            return [200, ['Content-Type' => 'application/json; charset=UTF-8', 'Last-Modified' => $last_modified], [$json]];
        } else {
            # display the bookmark form for this bookmark
            my $template = Template->new;
            $template->process(
                'bookmark.tt',
                { bookmark => $bookmark },
                \my $output,
            );
            return [200, ['Content-Type' => 'text/html; charset=UTF-8', 'Last-Modified' => $last_modified], [$output]];
        }
    } else {
        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]];
    }
}

sub view_field {
    my ($self, $id, $field) = @_;

    my $bookmark = $self->get_bookmark({ id => $id });
    if ($bookmark) {
        return [304, [], []] if $self->_request_is_newer_than($bookmark->mtime);

        # check whether the requested field is part of the bookmark
        if (!$bookmark->meta->has_attribute($field)) {
            return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], [qq{"$field" is not a valid bookmark data field}]];
        }

        # respond with just the requested field as plain text
        my $value = $bookmark->$field;
        my $last_modified = time2str($bookmark->mtime);
        return [200, ['Content-Type' => 'text/plain; charset=UTF-8', 'Last-Modified' => $last_modified], [ref $value eq 'ARRAY' ? join(' ', @{ $value }) : $value]];
    } else {
        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]];
    }
}

sub create_and_redirect {
    my $self = shift;

    my $uri   = $self->request->param('uri');
    my $title = $self->request->param('title');
    my @tags  = split ' ', $self->request->param('tags');

    my $bookmark = $self->bookmarks->add({
        uri   => $uri,
        title => $title,
        tags  => \@tags,
    });

    #TODO: not RESTful; the proper RESTful response would be a 201
    return [303, ['Location' => $bookmark->bookmark_uri->canonical], []];
}

sub update_and_redirect {
    my $self = shift;
    my $id = shift;

    my $bookmark = $self->bookmarks->get_bookmark({ id => $id });
    if ($bookmark) {
        # update the URI, title, and tags
        $bookmark->uri($self->request->param('uri'));
        $bookmark->title($self->request->param('title'));
        $bookmark->tags([ split ' ', $self->request->param('tags') || '' ]);

        # write to the database
        $self->bookmarks->update($bookmark);

        #TODO: not RESTful; proper response would be a 200
        return [303, ['Location' => $bookmark->bookmark_uri->canonical], []];
    } else {
        return [404, ['Content-Type' => 'text/plain; charset=UTF-8'], ["Boomark $id not found"]];
    }
}

1;
