package Statocles::Page;
$Statocles::Page::VERSION = '0.073';
# ABSTRACT: Base class for rendering objects

use Statocles::Base 'Role';
use Statocles::Template;
use Statocles::Util qw( uniq_by );

#pod =attr site
#pod
#pod The site this page is part of.
#pod
#pod =cut

has site => (
    is => 'ro',
    isa => InstanceOf['Statocles::Site'],
    lazy => 1,
    default => sub { $Statocles::SITE },
);

#pod =attr app
#pod
#pod The application this page came from, so we can give it to the templates.
#pod
#pod =cut

has app => (
    is => 'ro',
    isa => ConsumerOf['Statocles::App'],
);

#pod =attr path
#pod
#pod The absolute URL path to save this page to.
#pod
#pod =cut

has path => (
    is => 'rw',
    isa => Path,
    coerce => Path->coercion,
    required => 1,
);

#pod =attr title
#pod
#pod The title of the page. Any unsafe characters in the title (C<E<lt>>,
#pod C<E<gt>>, C<">, and C<&>) will be escaped by the template, so no HTML
#pod allowed.
#pod
#pod =cut

has title => (
    is => 'rw',
    isa => Str,
    default => '',
);

#pod =attr type
#pod
#pod The MIME type of this page. By default, will use the L<path's|/path> file extension
#pod to detect a likely type.
#pod
#pod =cut

our %TYPES = (
    # text
    html => 'text/html',
    markdown => 'text/markdown',
    css => 'text/css',

    # image
    jpg => 'image/jpeg',
    jpeg => 'image/jpeg',
    png => 'image/png',
    gif => 'image/gif',

    # application
    rss => 'application/rss+xml',
    atom => 'application/atom+xml',
    js => 'application/javascript',
    json => 'application/json',
);

has type => (
    is => 'ro',
    isa => Str,
    lazy => 1,
    default => sub {
        my ( $self ) = @_;
        my ( $ext ) = $self->path =~ /[.]([^.]+)$/;
        return $TYPES{ $ext };
    },
);

#pod =attr date
#pod
#pod The date of this page. Used for last updated date and blog post dates.
#pod
#pod =cut

has date => (
    is => 'rw',
    isa => DateTimeObj,
    coerce => DateTimeObj->coercion,
    lazy => 1,
    default => sub { DateTime::Moonpig->now },
);

#pod =attr data
#pod
#pod A hash of additional template variables for this page.
#pod
#pod =cut

has data => (
    is => 'ro',
    isa => HashRef,
    default => sub { {} },
);

#pod =attr links
#pod
#pod A hash of arrays of links to pages related to this page. Possible keys:
#pod
#pod     feed        - Feed pages related to this page
#pod     alternate   - Alternate versions of this page posted to other sites
#pod     stylesheet  - Additional stylesheets for this page
#pod     script      - Additional scripts for this page
#pod
#pod Each item in the array is a L<link object|Statocles::Link>. The most common
#pod attributes are:
#pod
#pod     text        - The text of the link
#pod     href        - The page for the link
#pod     type        - The MIME type of the link, optional
#pod
#pod =cut

has _links => (
    is => 'ro',
    isa => LinkHash,
    lazy => 1,
    default => sub { +{} },
    coerce => LinkHash->coercion,
    init_arg => 'links',
);

#pod =attr images
#pod
#pod A hash of images related to this page. Each value should be an L<image
#pod object|Statocles::Image>.  These are used by themes to show images next
#pod to articles, thumbnails, and/or shortcut icons.
#pod
#pod =cut

has _images => (
    is => 'ro',
    isa => HashRef[InstanceOf['Statocles::Image']],
    lazy => 1,
    default => sub { +{} },
    init_arg => 'images',
);

#pod =attr markdown
#pod
#pod The markdown object to render document Markdown. Defaults to L<the markdown
#pod attribute from the Site object|Statocles::Site/markdown>.
#pod
#pod Any object with a "markdown" method will work.
#pod
#pod =cut

has markdown => (
    is => 'rw',
    isa => HasMethods['markdown'],
    default => sub { $_[0]->site->markdown },
);

#pod =attr template
#pod
#pod The main L<template|Statocles::Template> for this page. The result will be
#pod wrapped in the L<layout template|/layout>.
#pod
#pod =cut

my @template_attrs = (
    is => 'rw',
    isa => InstanceOf['Statocles::Template'],
    coerce => Statocles::Template->coercion,
    default => sub {
        Statocles::Template->new( content => '<%= content %>' ),
    },
);

has template => @template_attrs;

#pod =attr layout
#pod
#pod The layout L<template|Statocles::Template> for this page, which will wrap the content generated by the
#pod L<template|/template>.
#pod
#pod =cut

has layout => @template_attrs;

#pod =attr search_change_frequency
#pod
#pod How frequently a search engine should check this page for changes. This is used
#pod in the L<sitemap.xml|http://www.sitemaps.org> to give hints to search engines.
#pod
#pod Should be one of:
#pod
#pod     always
#pod     hourly
#pod     daily
#pod     weekly
#pod     monthly
#pod     yearly
#pod     never
#pod
#pod Defaults to C<weekly>.
#pod
#pod B<NOTE:> This is only a hint to search engines, not a command. Pages marked C<hourly>
#pod may be checked less often, and pages marked C<never> may still be checked once in a
#pod while. C<never> is mainly used for archived pages or permanent links.
#pod
#pod =cut

has search_change_frequency => (
    is => 'rw',
    isa => Enum[qw( always hourly daily weekly monthly yearly never )],
    default => sub { 'weekly' },
);

#pod =attr search_priority
#pod
#pod How high should this page rank in search results compared to similar pages on
#pod this site?  This is used in the L<sitemap.xml|http://www.sitemaps.org> to rank
#pod individual, full pages more highly than aggregate, list pages.
#pod
#pod Value should be between C<0.0> and C<1.0>. The default is C<0.5>.
#pod
#pod This is only used to decide which pages are more important for the search
#pod engine to crawl, and which pages within your site should be given to users. It
#pod does not improve your rankings compared to other sites. See L<the sitemap
#pod protocol|http://sitemaps.org> for details.
#pod
#pod =cut

has search_priority => (
    is => 'rw',
    isa => Num,
    default => sub { 0.5 },
);

# _rendered_html
#
# The HTML rendered from the page. Cached.

has _rendered_html => (
    is => 'rw',
    isa => Str,
    predicate => '_has_rendered_html',
);

#pod =method vars
#pod
#pod     my %vars = $page->vars;
#pod
#pod Get extra template variables for this page
#pod
#pod =cut

sub vars {
    my ( $self ) = @_;
    return (
        app => $self->app,
        site => $self->site,
        self => $self,
    );
}

#pod =method render
#pod
#pod     my $html = $page->render( %vars );
#pod
#pod Render the page, using the L<template|Statocles::Page/template> and wrapping
#pod with the L<layout|Statocles::Page/layout>. Give any extra C<%vars> to the
#pod template, layout, and page C<content> method (if applicable).
#pod
#pod The result of this method is cached.
#pod
#pod =cut

sub render {
    my ( $self, %args ) = @_;

    if ( $self->_has_rendered_html ) {
        $self->site->log->debug( 'Render page (cached): ' . $self->path );
        return $self->_rendered_html;
    }

    $self->site->log->debug( 'Render page: ' . $self->path );

    my %vars = (
        %{ $self->data },
        %args,
        $self->vars,
    );

    my $content = $self->template->render(
        # XXX: This is suboptimal. Isn't vars() enough?
        ( $self->can( 'content' ) ? ( content => $self->content( %vars ) ) : () ),
        %vars,
    );

    my $html = $self->layout->render(
        content => $content,
        %vars,
    );

    $self->_rendered_html( $html );
    return $html;
}

#pod =method links
#pod
#pod     my @links = $page->links( $key );
#pod     my $link = $page->links( $key );
#pod     $page->links( $key => $add_link );
#pod
#pod Get or append to the links set for the given key. See L<the links
#pod attribute|/links> for some commonly-used keys.
#pod
#pod If only one argument is given, returns a list of L<link
#pod objects|Statocles::Link>. In scalar context, returns the first link in
#pod the list.
#pod
#pod If two or more arguments are given, append the new links to the given
#pod key. C<$add_link> may be a URL string, a hash reference of L<link
#pod attributes|Statocles::Link/ATTRIBUTES>, or a L<Statocles::Link
#pod object|Statocles::Link>. When adding links, nothing is returned.
#pod
#pod =cut

sub links {
    my ( $self, $name, @add_links ) = @_;
    if ( @add_links ) {
        push @{ $self->_links->{ $name } }, map { Link->coerce( $_ ) } @add_links;
        return;
    }
    my @links = uniq_by { $_->href }
        $self->_links->{ $name } ? @{ $self->_links->{ $name } } : ();
    return wantarray ? @links : $links[0];
}

#pod =method images
#pod
#pod     my $image = $page->images( $key );
#pod
#pod Get the images for the given key. See L<the images attribute|/images> for some
#pod commonly-used keys. Returns an L<image object|Statocles::Image>.
#pod
#pod =cut

sub images {
    my ( $self, $name ) = @_;
    # This exists here as a placeholder in case we ever need to handle
    # arrays of images, which I anticipate will happen when we build
    # image galleries or want to be able to pick a single random image
    # from an array.
    return $self->_images->{ $name };
}

#pod =method basename
#pod
#pod     my $name = $page->basename;
#pod
#pod Get the base file name of this page. Everything after the last C</>.
#pod
#pod =cut

sub basename {
    my ( $self ) = @_;
    return $self->path->basename;
}

#pod =method dirname
#pod
#pod     my $dir = $page->dirname;
#pod
#pod Get the full directory to this page. Anything that isn't part of L</basename>.
#pod
#pod There will not be a trailing slash unless it is the root directory.
#pod
#pod =cut

sub dirname {
    my ( $self ) = @_;
    return $self->path->parent->stringify;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Statocles::Page - Base class for rendering objects

=head1 VERSION

version 0.073

=head1 DESCRIPTION

A Statocles::Page takes one or more L<documents|Statocles::Document> and
renders them into one or more HTML pages using a main L<template|/template>
and a L<layout template|/layout>.

=head1 ATTRIBUTES

=head2 site

The site this page is part of.

=head2 app

The application this page came from, so we can give it to the templates.

=head2 path

The absolute URL path to save this page to.

=head2 title

The title of the page. Any unsafe characters in the title (C<E<lt>>,
C<E<gt>>, C<">, and C<&>) will be escaped by the template, so no HTML
allowed.

=head2 type

The MIME type of this page. By default, will use the L<path's|/path> file extension
to detect a likely type.

=head2 date

The date of this page. Used for last updated date and blog post dates.

=head2 data

A hash of additional template variables for this page.

=head2 links

A hash of arrays of links to pages related to this page. Possible keys:

    feed        - Feed pages related to this page
    alternate   - Alternate versions of this page posted to other sites
    stylesheet  - Additional stylesheets for this page
    script      - Additional scripts for this page

Each item in the array is a L<link object|Statocles::Link>. The most common
attributes are:

    text        - The text of the link
    href        - The page for the link
    type        - The MIME type of the link, optional

=head2 images

A hash of images related to this page. Each value should be an L<image
object|Statocles::Image>.  These are used by themes to show images next
to articles, thumbnails, and/or shortcut icons.

=head2 markdown

The markdown object to render document Markdown. Defaults to L<the markdown
attribute from the Site object|Statocles::Site/markdown>.

Any object with a "markdown" method will work.

=head2 template

The main L<template|Statocles::Template> for this page. The result will be
wrapped in the L<layout template|/layout>.

=head2 layout

The layout L<template|Statocles::Template> for this page, which will wrap the content generated by the
L<template|/template>.

=head2 search_change_frequency

How frequently a search engine should check this page for changes. This is used
in the L<sitemap.xml|http://www.sitemaps.org> to give hints to search engines.

Should be one of:

    always
    hourly
    daily
    weekly
    monthly
    yearly
    never

Defaults to C<weekly>.

B<NOTE:> This is only a hint to search engines, not a command. Pages marked C<hourly>
may be checked less often, and pages marked C<never> may still be checked once in a
while. C<never> is mainly used for archived pages or permanent links.

=head2 search_priority

How high should this page rank in search results compared to similar pages on
this site?  This is used in the L<sitemap.xml|http://www.sitemaps.org> to rank
individual, full pages more highly than aggregate, list pages.

Value should be between C<0.0> and C<1.0>. The default is C<0.5>.

This is only used to decide which pages are more important for the search
engine to crawl, and which pages within your site should be given to users. It
does not improve your rankings compared to other sites. See L<the sitemap
protocol|http://sitemaps.org> for details.

=head1 METHODS

=head2 vars

    my %vars = $page->vars;

Get extra template variables for this page

=head2 render

    my $html = $page->render( %vars );

Render the page, using the L<template|Statocles::Page/template> and wrapping
with the L<layout|Statocles::Page/layout>. Give any extra C<%vars> to the
template, layout, and page C<content> method (if applicable).

The result of this method is cached.

=head2 links

    my @links = $page->links( $key );
    my $link = $page->links( $key );
    $page->links( $key => $add_link );

Get or append to the links set for the given key. See L<the links
attribute|/links> for some commonly-used keys.

If only one argument is given, returns a list of L<link
objects|Statocles::Link>. In scalar context, returns the first link in
the list.

If two or more arguments are given, append the new links to the given
key. C<$add_link> may be a URL string, a hash reference of L<link
attributes|Statocles::Link/ATTRIBUTES>, or a L<Statocles::Link
object|Statocles::Link>. When adding links, nothing is returned.

=head2 images

    my $image = $page->images( $key );

Get the images for the given key. See L<the images attribute|/images> for some
commonly-used keys. Returns an L<image object|Statocles::Image>.

=head2 basename

    my $name = $page->basename;

Get the base file name of this page. Everything after the last C</>.

=head2 dirname

    my $dir = $page->dirname;

Get the full directory to this page. Anything that isn't part of L</basename>.

There will not be a trailing slash unless it is the root directory.

=head1 SEE ALSO

=over

=item L<Statocles::Page::Document>

A page that renders a single document.

=item L<Statocles::Page::List>

A page that renders a list of other pages.

=back

=head1 AUTHOR

Doug Bell <preaction@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Doug Bell.

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

=cut
