The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Net::FileMaker::XML::Database;
{
  $Net::FileMaker::XML::Database::VERSION = '0.064';
}

use strict;
use warnings;
use Net::FileMaker::XML::ResultSet;
use Carp;

use base qw(Net::FileMaker::XML);

# Particular methods have specific parameters that are optional, but need to be
# validated to mitigate sending bad parameters to the server.
my $acceptable_params = {
    'find'    => '-recid|-lop|-op|-max|-skip|-sortorder|-sortfield|-script|-script\.prefind|-script\.presort',    
    'findall' => '-recid|-lop|-op|-max|-skip|-sortorder|-sortfield|-script|-script\.prefind|-script\.presort',
    'findany' => '-recid|-lop|-op|-max|-skip|-sortorder|-sortfield|-script|-script\.prefind|-script\.presort',
    'delete'  => '-db|-lay|-recid|-script',
    'dup'     => '-db|-lay|-recid|-script',
    'edit'    => '-db|-lay|-recid|-modid|-script',
    'new'     => '-db|-lay|-script'
};

=head1 NAME

Net::FileMaker::XML::Database

=head1 SYNOPSIS

This module handles all the tasks with XML data. Don't call this module
directly, instead use L<Net::FileMaker::XML>.

    use Net::FileMaker::XML;
    my $fm = Net::FileMaker::XML->new(host => $host);
    my $db = $fm->database(db => $db, user => $user, pass => $pass);
    
    my $layouts = $db->layoutnames;
    my $scripts = $db->scriptnames;
    my $records = $db->findall( layout => $layout, params => { '-max' => '10'});
    my $records = $db->findany( layout => $layout, params => { '-skip' => '10'});

=head1 METHODS

=cut

sub new
{
    my($class, %args) = @_;

    my $self = {
        host      => $args{host},
        db        => $args{db},
        user      => $args{user},
        pass      => $args{pass},
        resultset => '/fmi/xml/fmresultset.xml',
                ua        => LWP::UserAgent->new,
                xml       => XML::Twig->new,
        uri => URI->new($args{host}),
    };

    bless $self , $class;
    return $self;
}

=head2 layoutnames

Returns an arrayref containing layouts accessible for the respective database.

=cut

sub layoutnames
{
    my $self = shift;
        my $xml = $self->_request(
                user      => $self->{user},
                pass      => $self->{pass},
                resultset => $self->{resultset},
                query     => '-layoutnames',
                params    => { '-db' => $self->{db} }
        );   


    return $self->_compose_arrayref('LAYOUT_NAME', $xml);
}

=head2 scriptnames

Returns an arrayref containing scripts accessible for the respective database.

=cut

sub scriptnames
{
    my $self = shift;
        my $xml = $self->_request(
                user      => $self->{user},
                pass      => $self->{pass},
                resultset => $self->{resultset},
                query     => '-scriptnames',
                params    => { '-db' => $self->{db} }
        );   


    return $self->_compose_arrayref('SCRIPT_NAME', $xml);
}

=head2 find(layout => $layout, params => { parameters })

Returns a L<Net::FileMaker::XML::ResultSet> for a specific database and layout.

=cut

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };

    $params = $self->_assert_params(
        type              => 'find',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params,
    );
    
    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-find',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}


=head2 findall(layout => $layout, params => { parameters }, nocheck => 1)

Returns a L<Net::FileMaker::XML::ResultSet> of all rows on a specific database
and layout. C<nocheck> is an optional argument that will skip checking of
parameters if set to 1.

=cut

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };

    $params = $self->_assert_params(
        type              => 'findall',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params
    );

    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-findall',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}

=head2 findany(layout => $layout, params => { parameters }, nocheck => 1)

Returns a L<Net::FileMaker::XML::ResultSet> of random rows on a specific
database and layout. C<nocheck> is an optional argument that will skip checking
of parameters if set to 1.

=cut

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };

    $params = $self->_assert_params(
        type              => 'findany',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params, 
    );

    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-findany',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}



=head2 edit(layout => $layout , recid => $recid , params => { params })

Updates the row with the fieldname/value pairs passed to params.
Returns a L<Net::FileMaker::XML::ResultSet> object.

=cut

#TODO: add tests to /t/01_xml

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };
    
    # just to make the recid param more visible than putting it into the params
    croak 'recid must be defined' if(! defined $args{recid});
    $params->{'-recid'}  = $args{recid};
    
    $params = $self->_assert_params(
        type              => 'edit',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params
    );
    
    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-edit',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}

=head2 remove(layout => $layout , recid => $recid , params => { params })

Deletes the record with that specific record id and returns a
L<Net::FileMaker::XML::ResultSet> object.

=cut

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };
    
    # just to make the recid param more visible than putting it into the params
    croak 'recid must be defined' if(! defined $args{recid});
    $params->{'-recid'}  = $args{recid};
    
    $params = $self->_assert_params(
        type              => 'delete',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params
    );
    
    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-delete',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}



=head2 insert(layout => $layout , recid => $recid , params => { params })

Creates a new record and populates that record with the fieldname/value pairs passed to params.

Returns an L<Net::FileMaker::XML::ResultSet> object.

=cut

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

    my $params = { 
        '-lay' => $args{layout},
        '-db'  => $self->{db}
    };
    
    $params = $self->_assert_params(
        type              => 'new',
        def_params        => $params,
        params            => $args{params},
        acceptable_params => $acceptable_params
    );
    
    my $xml = $self->_request(
            resultset => $self->{resultset}, 
            user      => $self->{user}, 
            pass      => $self->{pass}, 
            query     => '-new',
            params    => $params
    );

    return Net::FileMaker::XML::ResultSet->new(rs => $xml , db => $self);
}



=head2 total_rows(layout => $layout)

Returns a scalar with the total rows for a given layout.

=cut

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

    # Just do a findall with 1 record and parse the result. This might break on an empty database.
    my $xml = $self->_request(
        user      => $self->{user},
        pass      => $self->{pass},
        resultset => $self->{resultset},
        params    => {
            '-db'  => $self->{db}, 
            '-lay' => $args{layout}, 
            '-max' => '1' 
        },
        query     => '-findall'
    );

    return $xml;
}


1; # End of Net::FileMaker::XML::Database;