The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Webservice::InterMine::TemplateFactory;

=head1 NAME

Webservice::InterMine::TemplateFactory - Service for making Webservice::InterMine
template queries using the web service

=head1 SYNOPSIS

  my $factory = Webservice::InterMine::TemplateFactory->new($xml, $model);

  my @templates = $factory->get_templates;
  my $template  = $factory->get_template($name);
  my @matching_templates = $factory->search_for($keyword);

=head1 AUTHOR

FlyMine C<< <support@flymine.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<support@flymine.org>.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Webservice::InterMine::TemplateFactory

You can also look for information at:

=over 4

=item * FlyMine

L<http://www.flymine.org>

=back

=head1 COPYRIGHT & LICENSE

Copyright 2010 FlyMine, all rights reserved.

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

=head1 FUNCTIONS

=cut

use Moose;
with 'Webservice::InterMine::Role::ModelOwner';
use MooseX::Types::Moose qw/Str HashRef/;
use Webservice::InterMine::Types qw/File Service Template DomNode/;
use Webservice::InterMine::Query::Template;
use XML::DOM;
use Carp qw/carp confess/;

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;

    if ( @_ == 1 && ref $_[0] eq 'ARRAY' ) {
        confess "Not enough elements in arrayref to new"
          if ( @{ $_[0] } < 3 );
        return $class->$orig(
            service       => $_[0]->[0],
            model         => $_[0]->[1],
            source_string => $_[0]->[2],
        );
    } else {
        return $class->$orig(@_);
    }
};

sub BUILD {
    my $self = shift;

    unless ( $self->source_string ) {
        confess
"source xml must be passed to a TemplateFactory as either a string or a file";
    }
}

has service => (
    is       => 'ro',
    isa      => Service,
    required => 1,
);

has source_file => (
    is      => 'ro',
    isa     => File,
    trigger => \&set_xml,
);

has source_string => (
    is      => 'ro',
    writer  => '_set_source_string',
    isa     => Str,
    trigger => \&process_xml,
);

has template_list => (
    traits  => ['Hash'],
    isa     => HashRef[Template],
    default => sub { {} },
    handles => {
        _set_template      => "set",
        _get_template      => "get",
        _get_templates     => "values",
        _get_parsed_count  => 'count',
    },
);

has xml_list => (
    traits => ['Hash'],
    isa => HashRef[DomNode],
    default => sub { {} },
    handles => {
        _set_template_xml   => 'set',
        _get_template_xml   => 'get',
        get_template_names  => "keys",
        get_template_count  => 'count',
    },
);

sub set_xml {
    my $self = shift;
    my $file = shift;
    open( my $XMLFH, '<', $file )
      or confess "Cannot read from xml file, $!";
    my $xml = join( '', <$XMLFH> );
    close $XMLFH
      or confess "EEK, what happened there? $!";
    $self->_set_source_string($xml);
}

sub process_xml {
    my $self             = shift;
    my $xml              = shift;
    my $parser = XML::DOM::Parser->new;
    my $doc = eval {$parser->parse($xml)};
    if (my $e = $@) {
        confess "Error parsing template XML: '$xml';", $e;
    }
    my @templates = $doc->getElementsByTagName('template');
    for my $template (@templates) {
        my $name = $template->getAttribute('name');
        if ($self->_get_template_xml($name)) {
            confess "Found two templates with the same name: $name";
        }
        warn "FOUND TEMPLATE $name" if $ENV{DEBUG};
        $self->_set_template_xml($name, $template);
    }
}

sub get_template_by_name {
    my ($self, $name) = @_;
    if (my $template = $self->_get_template($name)) {
        return $template;
    }
    if (my $xml = $self->_get_template_xml($name)) {
        warn "PARSING $name" if $ENV{DEBUG};
        my $t = eval {
            Webservice::InterMine::Query::Template->new(
                service       => $self->service,
                model         => $self->model,
                source_string => $xml->toString(),
            );
        };
        if (my $e = $@) {
            confess "Error parsing template $name: (", $xml->toString(), ") ", $e;
        } 
        $self->_set_template($name, $t);
        return $t;
    } else {
        return;
    }
}

sub get_templates {
    my $self = shift;
    if ($self->_get_parsed_count == $self->get_template_count) {
        return $self->_get_templates;
    } else {
        return grep {defined} map {eval{$self->get_template_by_name($_)}} $self->get_template_names;
    }
}

__PACKAGE__->meta->make_immutable;
no Moose;

1;

__END__

=head1 AUTHOR

Alex Kalderimis C<< <dev@intermine.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<dev@intermine.org>.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Webservice::InterMine

You can also look for information at:

=over 4

=item * Webservice::InterMine

L<http://www.intermine.org>

=item * Documentation

L<http://www.intermine.org/perlapi>

=back

=head1 COPYRIGHT AND LICENSE

Copyright 2006 - 2010 FlyMine, all rights reserved.

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

=cut