package DBIx::Class::Schema::AuditLog::Structure;
{
  $DBIx::Class::Schema::AuditLog::Structure::VERSION = '0.4.0';
}

use base qw/DBIx::Class::Schema/;

use strict;
use warnings;

require DBIx::Class::Schema::AuditLog::Structure::Action;
require DBIx::Class::Schema::AuditLog::Structure::AuditedTable;
require DBIx::Class::Schema::AuditLog::Structure::Change;
require DBIx::Class::Schema::AuditLog::Structure::Changeset;
require DBIx::Class::Schema::AuditLog::Structure::Field;
require DBIx::Class::Schema::AuditLog::Structure::User;

__PACKAGE__->mk_group_accessors( simple => '_current_changeset_container' );

sub _current_changeset {
    my $self = shift;
    my $ref  = $self->_current_changeset_container;

    return $ref && $ref->{changeset};
}


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

    $self->throw_exception('Cannot set changeset manually. Use txn_do.')
        if @args;

    # we only want to create a changeset if the action (insert/update/delete)
    # is being run from txn_do -- the txn_do method in
    # DBIx::Class::Schema::AuditLog sets local
    # _current_changeset_container->{changeset} &
    # _current_changeset_container->{args} variables in the scope
    # of each transaction
    if (   defined $self->_current_changeset_container
        && defined $self->_current_changeset_container->{changeset} )
    {

        my $id = $self->_current_changeset;

        unless ($id) {
            my $changeset = $self->audit_log_create_changeset(
                $self->_current_changeset_container->{args} );
            $self->_current_changeset_container->{changeset} = $changeset->id;
            $id = $changeset->id;
        }

        return $id;
    }

    return;
}


sub audit_log_create_changeset {
    my $self           = shift;
    my $changeset_data = shift;

    my ( $changeset, $user );

    if ( $changeset_data->{user} ) {
        $user = $self->resultset('AuditLogUser')
            ->find_or_create( { name => $changeset_data->{user} } );

        $changeset = $user->create_related( 'Changeset',
            { description => $changeset_data->{description} } );
    }
    else {
        $changeset = $self->resultset('AuditLogChangeset')
            ->create( { description => $changeset_data->{description} } );
    }

    return $changeset;
}


sub audit_log_create_action {
    my $self        = shift;
    my $action_data = shift;

    my $changeset = $self->current_changeset;

    if ($changeset) {
        my $table = $self->resultset('AuditLogAuditedTable')
            ->find_or_create( { name => $action_data->{table} } );

        return (
            $self->resultset('AuditLogChangeset')->find($changeset)
                ->create_related(
                'Action',
                {   audited_row   => $action_data->{row},
                    audited_table => $table->id,
                    type          => $action_data->{type},
                }
                ),
            $table
        );
    }

    return;
}

sub get_changes {
    my $self    = shift;
    my $options = shift;

    my $audited_row  = $options->{id};
    my $change_order = $options->{change_order} || 'desc';
    my $field_name   = $options->{field};
    my $table_name   = $options->{table};
    my $timestamp    = $options->{timestamp};
    my $action_types = $options->{action_types}
        || [ 'insert', 'update', 'delete' ];

    # row and table are required for all changes
    return if !$audited_row || !$table_name;

    my $table = $self->resultset('AuditLogAuditedTable')
        ->find( { name => $table_name, } );

    # cannot get changes if the specified table hasn't been logged
    return unless defined $table;

    my $changeset_criteria = {};
    $changeset_criteria->{timestamp} = $timestamp if $timestamp;
    my $changesets = $self->resultset('AuditLogChangeset')
        ->search_rs( $changeset_criteria );

    my $actions = $changesets->search_related(
        'Action',
        {   audited_table => $table->id,
            audited_row   => $audited_row,
            type          => $action_types,
        }
    );

    if ( $actions && $actions->count ) {
        my $field = $table->find_related( 'Field', { name => $field_name } )
            if $field_name;

        # if field is passed and the passed field wasn't found in the Field
        # table set field id to -1 to ensure a $changes object with ->count =
        # 0 is returned
        my $criteria = {};
        if ( $field_name ) {
            $criteria->{field} = $field ? $field->id : -1;
        }

        my $changes = $actions->search_related( 'Change', $criteria,
            { order_by => 'me.id ' . $change_order, } );
        return $changes;
    }

    return;
}

1;

__END__

=pod

=head1 NAME

DBIx::Class::Schema::AuditLog::Structure

=head1 VERSION

version 0.4.0

=head2 current_changeset

Returns the changeset that is currently in process.

This is localized to the scope of each transaction.

=head2 audit_log_create_changeset

Creates a new Changeset for audited Actions.

Will create a new Audit Log User if ncessary.

=head2 audit_log_create_action

Creates a related Action for the current Changeset.

Also will create an AuditedTable for the new action if
it doesn't already exist.

=head2 get_changes

Returns DBIC resultset of audit changes.

Takes a passed options hashref.

Required:
    id: row id from the table that was audited
    table:  name of the table that was audited
            this must include the schema name
            for databases that have multiple schemas

Optional:
    action_types: array ref of action types: [ delete, insert, update ]
    change_order: sets the order to return the results, either asc, or desc
                  defaults to desc
    field: name of the field that was audited
    timestamp: timestamp of the changeset to search by
               takes a standard dbic where clause for a field,
               eg:
                   '2012-07-09-15.25.18'
                or
                   { '>=' , '2012-07-09-15.25.18; }
               the timestamp must already be in the format that the
               database stores in

=head1 AUTHOR

Mark Jubenville <ioncache@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Mark Jubenville.

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

=cut
