package Mojolicious::Plugin::XML::Loy;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Loader;
use Mojo::Util 'deprecated';
use XML::Loy;

our $VERSION = '0.05';

my %base_classes;

# Register Plugin
sub register {
  my ($plugin, $mojo, $param) = @_;

  my $namespace = 'XML::Loy::';
  my $max_size = 1024 * 1024;

  # Load parameter from Config file
  if (my $config_param = $mojo->config('XML-Loy')) {
    $param = { %$config_param, %$param };
  };

  # Set Namespace
  if (exists $param->{namespace}) {
    deprecated 'namespace parameter is DEPRECATED in favor of minus symbol';
    $namespace = delete $param->{namespace};
    $namespace .= '::' if $namespace;
  };

  if (exists $param->{max_size} && $param->{max_size} =~ /^\d+$/) {
    $max_size = delete $param->{max_size};
  };

  # Start Mojo::Loader instance
  my $loader = Mojo::Loader->new;

  # Create new XML helpers
  foreach my $helper (keys %$param) {

    # Already defined
    if (exists $mojo->renderer->helpers->{$helper}) {
      $mojo->log->debug("Helper '$helper' already defined");
      next;
    };

    my @helper = @{ $param->{ $helper } };
    my $base = shift @helper;

    # Deprecated in M::P::XML::Loy 0.4
    if ($base eq 'Loy') {
      deprecated 'Loy is DEPRECATED as a base in favor of -Loy';
      $base = '-Loy';
    };

    if ($base eq '-Loy') {
      $base = 'XML::Loy';
    }
    elsif ($base ~~ [qw/Atom XRD HostMeta ActivityStreams Atom::Threading/]) {
      deprecated $base . ' is DEPRECATED as a base in favor of -' . $base;
      $base = '-' . $base;
    };

    if (index($base, '-') == 0) {
      $base =~ s/^-//;
      $base = ($base eq 'Loy' ? 'XML::Loy' : $namespace . "$base");
    };

    # Load module if not loaded
    unless (exists $base_classes{$base}) {

      # Load base class
      if (my $e = $loader->load($base)) {
	for ($mojo->log) {
	  $_->error("Exception: $e")  if ref $e;
	  $_->error(qq{Unable to load base class "$base"});
	};
	next;
      };

      my $mime   = $base->mime;
      my $prefix = $base->_prefix;

      # Establish mime types
      if ($mime && $prefix) {

	# Apply mime type
	$mojo->types->type($prefix => $mime);
      };

      # module loaded
      $base_classes{$base} = [$prefix => $mime];
    };

    # Code generation for ad-hoc helper
    my $code = 'sub { shift;' .
      ' { use bytes; return if length("@_") > ' . $max_size . '} ' .
      ' my $doc = ' . $base . '->new( @_ );';

    # Extend base class
    if (@helper) {
      $code .= '$doc->extension(' .
	join(',', map( '"' . qq{$_"}, @helper)) .
      ");";
    };
    $code .= 'return $doc };';

    # Eval code
    my $code_ref = eval $code;

    # Evaluation error
    $mojo->log->fatal($@ . ': ' . $!) and next if $@;

    # Create helper
    $mojo->helper($helper => $code_ref);
  };

  # Plugin wasn't registered before
  unless (exists $mojo->renderer->helpers->{'new_xml'}) {

    # Default 'new_xml' helper
    $mojo->helper(
      new_xml => sub {
	shift;
	return XML::Loy->new( @_ );
      });


    # Add 'render_xml' helper
    $mojo->helper(
      render_xml => sub {
	my ($c, $xml) = @_;
	my $format = 'xml';

	# Check format based on mime type
	my $class = ref $xml;
	if ($base_classes{$class}) {
	  if ($base_classes{$class}->[0] && $base_classes{$class}->[1]) {
	    $format = $base_classes{$class}->[0];
	  };
	};

	# render XML with correct mime type
	return $c->render_data(
	  $xml->to_pretty_xml,
	  'format' => $format,
	  @_
	);
      });
  };
};


1;


__END__

=pod

=head1 NAME

Mojolicious::Plugin::XML::Loy - XML Generation with Mojolicious


=head1 SYNOPSIS

  # Mojolicious
  $mojo->plugin(
    'XML::Loy' => {
      new_activity => [-Atom, -ActivityStreams],
      new_hostmeta => [-XRD, -HostMeta],
      new_myXML    => [-Loy, -Atom, -Atom::Threading]
    });

  # In controllers use the generic new_xml helper
  my $xml = $c->new_xml('entry');

  # Create a new XML::Loy document
  my $xml = $app->new_xml('html');

  $xml->add('header')->add(
    title => 'XML-Loy example' => 'My Title!'
  );

  for ($xml->add('body' => { style => 'color: red' })) {
    $_->add(p => 'First paragraph');
    $_->add(p => { -type => 'raw' } => 'Second')
      ->add(b => 'paragraph');
  };

  # Render document with the correct mime-type
  $c->render_xml($xml);

  # Content-Type: application/xml
  # <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  # <html>
  #   <header>
  #
  #     <!-- My Title! -->
  #     <title>XML-Loy example</title>
  #   </header>
  #   <body style="color: red">
  #     <p>First paragraph</p>
  #     <p>Second<b>paragraph</b></p>
  #   </body>
  # </html>

  # Use newly created helper
  my $xrd = $c->new_hostmeta;

  # Create a document based on the defined xml profile
  $xrd->host('sojolicio.us');

  # Render document with the correct mime-type
  $c->render_xml($xrd);

  # Content-Type: application/xrd+xml
  # <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  # <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"
  #      xmlns:hm="http://host-meta.net/xrd/1.0"
  #      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  #   <hm:Host>sojolicio.us</hm:Host>
  # </XRD>


=head1 DESCRIPTION

L<Mojolicious::Plugin::XML::Loy> is a plugin to support
XML document generation based on L<XML::Loy>.


=head1 METHODS

L<Mojolicious::Plugin::XML::Loy> inherits all methods
from L<Mojolicious::Plugin> and implements the following
new ones.

=head2 register

  # Mojolicious
  $mojo->plugin('XML::Loy' => {
    max_size     => 2048,
    new_activity => [-Atom, -ActivityStreams]
  });

  # Mojolicious::Lite
  plugin 'XML::Loy' => {
    new_activity => [-Atom, -ActivityStreams]
  };

  # In your config file
  {
    'XML-Loy' => {
      new_activity => [-Atom, -ActivityStreams]
    }
  };

Called when registering the plugin.
Accepts new xml profiles, defined by the
name of the associated generation helper
and an array reference defining the profile.
The first element in the array is the base class,
followed by all extensions.
To create a helper extending the base class,
use C<-Loy> as the first element.

  $mojo->plugin('XML::Loy' => {
    new_myXML => [-Loy, 'MyXML::Loy::Extension']
  });

In addition to that, the C<max_size> in bytes of xml documents
to be parsed can be defined (defaults to C<1024 * 1024>).

All parameters can be set either on registration or
as part of the configuration file with the key C<XML-Loy>.

B<Note:> The C<namespace> parameter is DEPRECATED.


=head1 HELPERS

=head2 new_xml

  my $xml = $c->new_xml('entry');
  print $xml->to_pretty_xml;

Creates a new generic L<XML::Loy> document.
All helpers created on registration accept
the parameters as defined in the constructors of
the L<XML::Loy> base classes.


=head2 render_xml

  $c->render_xml($xml);
  $c->render_xml($xml, status => 404);

Renders documents based on L<XML::Loy>
using the defined mime-type of the base class.


=head1 DEPENDENCIES

L<Mojolicious>,
L<XML::Loy>.


=head1 AVAILABILITY

  https://github.com/Akron/Mojolicious-Plugin-XML-Loy


=head1 COPYRIGHT AND LICENSE

Copyright (C) 2011-2013, L<Nils Diewald|http://nils-diewald.de/>.

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

=cut
