package Webservice::InterMine::Query::Roles::WriteOutAble;

use MooseX::Role::WithOverloading;
requires(
    qw/name view sort_order logic joins
      path_descriptions model_name
      constraints coded_constraints/
);

use XML::DOM;

=head1 NAME 

Webservice::InterMine::Query::Roles::WriteOutAble - Composable behaviour for serialisable queries

=head1 DESCRIPTION 

This module provides composable behaviour for serialising queries

=head1 REQUIRES

This module demands its consumer provide the following methods:

=over 4

=item * coded_constraints
=item * constraints
=item * joins
=item * logic
=item * model_name
=item * name
=item * pathdescriptions
=item * sort_order
=item * view

=back

=cut

use overload (
    '""' => 'stringify',
    fallback => 1,
);

=head1 METHODS

=head2 stringify

Returns a string representation of this query. This takes the form 
of xml. For invalid queries, the string "<query>Invalid</query>" is returned.

=cut

sub stringify {
    my $self = shift;
    if (my $string = eval { $self->to_xml} ) {
        return $string;
    } else {
        return "<query>Invalid</query>";
    }
}

sub query_attributes {
    my $self  = shift;
    my %query = (
        name      => $self->name,
        view      => $self->joined_view(' '),
        sortOrder => $self->sort_order,
        model     => $self->model_name,
    );
    $query{longDescription} = $self->description if $self->description;
    $query{constraintLogic} = $self->logic->code
      if ( $self->coded_constraints > 1 );
    return %query;
}

sub apply_attributes_to_element {
    my $self    = shift;
    my $element = shift;
    my $doc = $element->getOwnerDocument;
    my %attrs   = @_;
    while ( my ( $tag, $value ) = each %attrs ) {
        if (ref $value eq 'ARRAY') {
            for (@$value) {
            my $sub_elem = $doc->createElement($tag);
            my $text = $doc->createTextNode($_);
            $sub_elem->appendChild($text);
            $element->appendChild($sub_elem);
            }
        } else {
            $element->setAttribute( $tag => $value );
        }
    }
}

has has_been_written_out => (
    traits => ['Bool'],
    is => 'rw',
    default => 0,
    handles => {
        mark_written => 'set',
        mark_dirty   => 'unset',
        needs_writing => 'not',
    },
);

has written_form => (
    isa => 'Str',
    init_arg => undef,
    writer => 'cache_xml',
    reader => 'retrieve_xml',
);

after [qw(
    add_constraint add_join add_view add_outer_join add_sort_order add_pathdescription
    set_logic set_sort_order remove clear_path_descriptions clear_logic clear_joins
    clear_constraints clear_view clear_sort_order
        )] => sub {
    my $self = shift;
    $self->mark_dirty;
};

=head2 to_xml

Returns an xml representation of the query. If the query is invalid, 
an error with be thrown.

=cut

sub to_xml {
    my $self = shift;
    if ($self->needs_writing) {
        $self->cache_xml($self->to_DOM->toString);
        $self->mark_written;
    }
    return $self->retrieve_xml;
}

=head2 to_query_xml

Returns the xml suitable for passing to query service calls with (stripped of
any headers in the case of templates and saved queries)

=cut

sub to_query_xml {
    my $self = shift;
    my $dom = $self->to_DOM;
    my ($query) = ($dom->getTagName eq "query") 
            ? $dom 
            : $dom->getElementsByTagName("query");
    die "no query element found in DOM"
        unless $query;
    return $query->toString;
}

sub to_DOM {
    my $self = shift;

    my $doc   = new XML::DOM::Document;
    my $query = $doc->createElement('query');

    $self->apply_attributes_to_element( $query,
        $self->query_attributes );

    my @elements = qw(
      path_descriptions
      joins
      all_constraints
    );

    for my $meth (@elements) {
        for my $e ( $self->$meth ) {
            my $elem = $doc->createElement( $e->element_name );
            $self->apply_attributes_to_element( $elem, $e->to_hash );
            $query->appendChild($elem);
        }
    }
    return $query;
}

1;

__END__

=head1 SEE ALSO

=over 4

=item * L<Webservice::InterMine::Cookbook> for guide on how to use these modules.

=item * L<Webservice::InterMine::Query>

=item * L<Webservice::InterMine::Service>

=item * L<Webservice::InterMine::Query::Template>

=item * L<Webservice::InterMine::Query::Saved>

=back

=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::Query::Roles::WriteOutAble

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 - 2011 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
