use 5.006;
use warnings;
use strict;
use Carp;

package Template::Declare;
use Template::Declare::Buffer;

$Template::Declare::VERSION = "0.21";

use base 'Class::Data::Inheritable';
__PACKAGE__->mk_classdata('roots');
__PACKAGE__->mk_classdata('aliases');
__PACKAGE__->mk_classdata('alias_metadata');
__PACKAGE__->mk_classdata('templates');
__PACKAGE__->mk_classdata('private_templates');
__PACKAGE__->mk_classdata('buffer_stack');
__PACKAGE__->mk_classdata('imported_into');

__PACKAGE__->roots( [] );
__PACKAGE__->aliases(           {} );
__PACKAGE__->alias_metadata(    {} );
__PACKAGE__->templates(         {} );
__PACKAGE__->private_templates( {} );
__PACKAGE__->buffer_stack( [] );

__PACKAGE__->new_buffer_frame();

use vars qw/$TEMPLATE_VARS/;

=head1 NAME

Template::Declare - Perlish declarative templates

=head1 SYNOPSIS

C<Template::Declare> is a pure-perl declarative HTML templating system. 

Yes.  Another one. There are many others like it, but this one is ours.

A few key features and buzzwords

=over

=item All templates are 100% pure perl code

=item Simple declarative syntax

=item No angle brackets

=item Mixins

=item Inheritance

=item Public and private templates

=back


=head1 USAGE


=head2 Basic usage

 package MyApp::Templates;
 use Template::Declare::Tags;
 use base 'Template::Declare';

 template simple => sub {
    html {
        head {}
        body {
            p {'Hello, world wide web!'};
            }
        }
 };

 package main;
 use Template::Declare;
 Template::Declare->init( roots => ['MyApp::Templates']);
 print Template::Declare->show( 'simple');

 # Output:
 #
 #
 # <html>
 #  <head></head>
 #  <body>
 #   <p>Hello, world wide web!
 #   </p>
 #  </body>
 # </html>


=head2 A slightly more advanced example

In this example, we'll show off how to set attributes on HTML tags, how to call other templates and how to declare a I<private> template that can't be called directly.

 package MyApp::Templates;
 use Template::Declare::Tags;
 use base 'Template::Declare';

 private template 'header' => sub {
        head {
            title { 'This is a webpage'};
            meta { attr { generator => "This is not your father's frontpage"}}
        }
 };

 template simple => sub {
    html {
        show('header');
        body {
            p { attr { class => 'greeting'};
                'Hello, world wide web!'};
            }
        }
 };

 package main;
 use Template::Declare;
 Template::Declare->init( roots => ['MyApp::Templates']);
 print Template::Declare->show( 'simple');

 # Output:
 #
 #  <html>
 #  <head>
 #   <title>This is a webpage
 #   </title>
 #   <meta generator="This is not your father&#39;s frontpage" />
 #  </head>
 #  <body>
 #   <p class="greeting">Hello, world wide web!
 #   </p>
 #  </body>
 # </html>
 

=head2 Multiple template roots (search paths)

=head2 Inheritance

=head2 Aliasing

=head1 METHODS

=head2 init

This I<class method> initializes the C<Template::Declare> system.

=over

=item roots

=back

=cut 

sub init {
    my $class = shift;
    my %args  = (@_);

    if ( $args{'roots'} ) {
        $class->roots( $args{'roots'} );
    }

}

sub new_buffer_frame {
    my $buffer = Template::Declare::Buffer->new();
    unshift @{ __PACKAGE__->buffer_stack }, $buffer;

}

sub end_buffer_frame {
    shift @{ __PACKAGE__->buffer_stack };
}

sub buffer {
    unless ( __PACKAGE__->buffer_stack->[0] ) {
        Carp::confess( __PACKAGE__ . "->buffer called with no buffer" );
    }
    return __PACKAGE__->buffer_stack->[0];
}

=head2 show TEMPLATE_NAME

Call C<show> with a C<template_name> and C<Template::Declare> will
render that template. Content generated by show can be accessed with
the C<output> method if the output method you've chosen returns content
instead of outputting it directly.

(If called in scalar context, this method will also just return the
content when available).



=cut

sub show {
    my $class    = shift;
    my $template = shift;
    local %Template::Declare::Tags::ELEMENT_ID_CACHE = ();
    return Template::Declare::Tags::show_page($template => \@_);
}

=head2 alias

 alias Some::Clever::Mixin under '/mixin';

=cut

sub alias {
    my $alias_into   = caller(0);
    my $mixin        = shift;
    my $prepend_path = shift;
    my $package_vars = shift;

    $prepend_path =~ s|/+/|/|g;
    $prepend_path =~ s|/$||;

    my $alias_key = $mixin . " " . $prepend_path;
    push @{ Template::Declare->aliases->{$alias_into} }, $alias_key;
    $alias_into->alias_metadata()->{$alias_key} = {
        class        => $mixin,
        path         => $prepend_path,
        package_vars => $package_vars
    };

}

=head2 import_templates


 import_templates Wifty::UI::something under '/something';


=cut

sub import_templates {
    return undef if $_[0] eq 'Template::Declare';
    my $import_into      = caller(0);
    my $import_from_base = shift;
    my $prepend_path     = shift;

    $prepend_path =~ s|/+/|/|g;
    $prepend_path =~ s|/$||;
    $import_from_base->imported_into($prepend_path);

    my @packages;
    {
        no strict 'refs';
        @packages = ( @{ $import_from_base . "::ISA" }, $import_from_base );
    }
    foreach my $import_from (@packages) {
        foreach my $template_name ( @{ __PACKAGE__->templates()->{$import_from} } ) {
            my $code = $import_from->_find_template_sub( _template_name_to_sub($template_name));
            $import_into->register_template( $prepend_path . "/" . $template_name, $code );
        }
        foreach my $template_name ( @{ __PACKAGE__->private_templates()->{$import_from} } ) {
            my $code = $import_from->_find_template_sub( _template_name_to_private_sub($template_name) );
            $import_into->register_private_template( $prepend_path . "/" . $template_name, $code );
        }
    }

}

=head2 path_for $template

 Returns the path for the template name to be used for show, adjusted
 with paths used in import_templates.

=cut

sub path_for {
    my $class = shift;
    my $template = shift;
    return ($class->imported_into ||'') . '/' . $template;
}

=head2 has_template PACKAGE TEMPLATE_NAME SHOW_PRIVATE

Takes a package, template name and a boolean. The boolean determines whether to show private templates.

Returns a reference to the template's code if found. Otherwise, returns undef.

This method is an alias for L</resolve_template>

=cut

sub has_template {
   return resolve_template(@_);
}

sub _has_template {

    # Otherwise find only in specific package
    my $pkg           = shift;
    my $template_name = shift;
    my $show_private  = 0 || shift;

    if ( my $coderef = $pkg->_find_template_sub( _template_name_to_sub($template_name) ) ) {
        return $coderef;
    } elsif ( $show_private and $coderef = $pkg->_find_template_sub( _template_name_to_private_sub($template_name))) {
        return $coderef;
    }

    return undef;
}

sub _has_aliased_template {
    my $package       = shift;
    my $template_name = shift;
    my $show_private  = shift;

    foreach my $alias_key ( @{ Template::Declare->aliases->{$package} } ) {
        my $alias_info   = $package->alias_metadata()->{$alias_key};
        my $alias_prefix = $alias_info->{path};
        my $alias_class  = $alias_info->{class};
        my $package_vars = $alias_info->{package_vars};

        $template_name = "/$template_name";

        if ( $template_name =~ m{$alias_prefix/(.*)$} ) {
            my $dispatch_to_template = $1;
            if (my $coderef = $alias_class->resolve_template( $dispatch_to_template, $show_private)) {

                return sub {
                    shift @_;  # Get rid of the passed-in "$self" class.
                    local $TEMPLATE_VARS->{$alias_class} = $package_vars;
                    &$coderef($alias_class,@_);
                };
            }

        }

    }
}

=head2 resolve_template TEMPLATE_PATH INCLUDE_PRIVATE_TEMPLATES

Turns a template path (C<TEMPLATE_PATH>) into a C<CODEREF>.  If the
boolean C<INCLUDE_PRIVATE_TEMPLATES> is true, resolves private template
in addition to public ones.

First it looks through all the valid Template::Declare roots. For each
root, it looks to see if the root has a template called $template_name
directly (or via an C<import> statement). Then it looks to see if there
are any L</alias>ed paths for the root with prefixes that match the
template we're looking for.

=cut

sub resolve_template {
    my $self          = shift;
    my $template_name = shift;
    my $show_private  = shift || 0;

    my @search_packages;

    # If we're being called as a class method on T::D it means "search in any package"
    # Otherwise, it means search only in this specific package"
    if ( $self eq 'Template::Declare' ) {
        @search_packages = reverse @{ Template::Declare->roots };
    } else {
        @search_packages = ($self);
    }

    foreach my $package (@search_packages) {
        next unless ( $package and $package->isa('Template::Declare') ); 
        if ( my $coderef = $package->_has_template( $template_name, $show_private ) ) {
            return $coderef;
        } elsif (  $coderef = $package->_has_aliased_template($template_name, $show_private) ) {
            return $coderef;
        }
    }
}


sub _find_template_sub {
    my $self    = shift;
    my $subname = shift;
    return $self->can($subname);
}

sub _template_name_to_sub {
    return _subname( "_jifty_template_", shift );

}

sub _template_name_to_private_sub {
    return _subname( "_jifty_private_template_", shift );
}

sub _subname {
    my $prefix = shift;
    my $template = shift || '';
    $template =~ s{/+}{/}g;
    $template =~ s{^/}{};
    return join( '', $prefix, $template );
}

=head2 register_template PACKAGE TEMPLATE_NAME CODEREF

This method registers a template called C<TEMPLATE_NAME> in package
C<PACKAGE>. As you might guess, C<CODEREF> defines the template's
implementation.

=cut

sub register_template {
    my $class         = shift;
    my $template_name = shift;
    my $code          = shift;
    push @{ __PACKAGE__->templates()->{$class} }, $template_name;
    _register_template( $class, _template_name_to_sub($template_name), $code )

}

=head2 register_template PACKAGE TEMPLATE_NAME CODEREF

This method registers a private template called C<TEMPLATE_NAME> in package
C<PACKAGE>. As you might guess, C<CODEREF> defines the template's
implementation. 

Private templates can't be called directly from user code but only from other 
templates.

=cut

sub register_private_template {
    my $class         = shift;
    my $template_name = shift;
    my $code          = shift;
    push @{ __PACKAGE__->private_templates()->{$class} }, $template_name;
    _register_template( $class, _template_name_to_private_sub($template_name), $code );

}

sub _register_template {
    my $self    = shift;
    my $class   = ref($self) || $self;
    my $subname = shift;
    my $coderef = shift;
    no strict 'refs';
    no warnings 'redefine';
    *{ $class . '::' . $subname } = $coderef;
}

sub package_variable {
    my $self = shift;
    my $var  = shift;
    if (@_) {
        $TEMPLATE_VARS->{$self}->{$var} = shift;
    }
    return $TEMPLATE_VARS->{$self}->{$var};
}

sub package_variables {
    my $self = shift;
    my $var  = shift;
    if (@_) {
        %{ $TEMPLATE_VARS->{$self} } = shift;
    }
    return $TEMPLATE_VARS->{$self};
}

=head1 BUGS

Crawling all over, baby. Be very, very careful. This code is so cutting edge, it can only be fashioned from carbon nanotubes.

Some specific bugs and design flaws that we'd love to see fixed

=over

=item Output isn't streamy.

=back

If you run into bugs or misfeatures, please report them to
C<bug-template-declare@rt.cpan.org>.


=head1 SEE ALSO

L<Jifty>

=head1 AUTHOR

Jesse Vincent <jesse@bestpractical.com>

=head1 COPYRIGHT

Copyright 2006-2007 Best Practical Solutions, LLC

=cut

1;
