package SQL::Entity::Relationship;

use warnings;
use strict;
use Carp 'confess';
use vars qw(@EXPORT_OK %EXPORT_TAGS $VERSION);
use SQL::Entity::Condition;

$VERSION = '0.01';
use base 'Exporter';

@EXPORT_OK = qw(sql_relationship);
%EXPORT_TAGS = (all => \@EXPORT_OK);

use Abstract::Meta::Class ':all';

=head1 NAME

SQL::Entity::Relationship - Entities Relationship abstraction layer.

=head1 SYNOPSIS

    use SQL::Entity::Relationship ':all';
    use SQL::Entity::Column ':all';
    use SQL::Entity::Table;
    use SQL::Entity::Condition ':all';

    my $dept = SQL::Entity::Table->new(
        name    => 'dept',
        alias   => 'd',
        columns => [
            sql_column(name => 'deptno'),
            sql_column(name => 'dname')
        ],
    );
    my $emp  = SQL::Entity->new(
        name                  => 'emp',
        primary_key		  => ['empno'],
        unique_expression     => 'rowid',
        columns               => [
            sql_column(name => 'ename'),
            sql_column(name => 'empno'),
            sql_column(name => 'deptno')
        ],
    );
    $emp->add_to_one_relationships(sql_relationship(
        target_entity => $dept,
        condition     => sql_cond($dept->column('deptno'), '=', $entity->column('deptno'))
    ));

=head1 DESCRIPTION

Represents relationship between entities.

=head2 EXPORT

sql_relationship by all tag.

=head2 ATTRIBUTES

=over

=item name

Name of the relationship

=cut

has '$.name';


=item target_entity

=cut

has '$.target_entity' => (associated_class => 'SQL::Entity');


=item condition

=cut

has '$.condition' => (associated_class => 'SQL::Entity::Condition');


=item join_columns

=cut

has '@.join_columns';


=item order_by

=cut

has '$.order_by';


=back

=head2 METHODS

=over

=item initialise

=cut

sub initialise {
    my ($self) = @_;
    $self->name($self->target_entity->id)
        unless $self->name;
}


=item join_condition

Return join condition.

=cut

sub join_condition {
    my ($self, $entity, $bind_variables, $entity_condition) = @_;
    my $join_condition = $self->join_columns_condition($entity);
    my $condition = $self->condition;
    $condition = $condition && $join_condition
        ? $join_condition->and($condition)
        : $condition || $join_condition;
    $condition = $entity_condition ? $condition->and($entity_condition) : $condition;
    $condition;
}



=item join_condition_as_string

Return SQL condition fragment.

=cut

sub join_condition_as_string {
    my ($self, $entity, $bind_variables, $entity_condition) = @_;
    my $condition = $self->join_condition($entity, $bind_variables, $entity_condition);
    my $target_entity = $self->target_entity;
    my %query_columns = $entity->query_columns;
    $condition->as_string(\%query_columns, $bind_variables, $entity);
}


=item join_columns_values

Returns join columns values.

=cut

sub join_columns_values {
    my ($self, $entity) = @_;
    my $target_entity = $self->target_entity;
    if($entity->to_one_relationship($self->name)) {
        $target_entity = $entity;
        $entity =  $self->target_entity;
    } 
    my @join_columns = $self->join_columns or return;
    my @primary_key = $entity->primary_key or confess "primary key must be defined for entity " . $entity->name;
    
    my @result;
    for my $i (0 .. $#primary_key) {
        my $column = $entity->column($primary_key[$i])
            or confess "unknown primary key column: "  . $primary_key[$i] . " on " . $entity->name;
        push @result, $column;

        my $join_column = $target_entity->column($join_columns[$i])
            or confess "unknown foreign key column: " . $join_columns[$i] . " on " . $target_entity->name;
        push @result, $join_column;
    }
    @result;
    
}

=item join_columns_condition

Returns condition for join columns.

=cut

sub join_columns_condition {
    my ($self, $entity) = @_;
    my @condition = $self->join_columns_values($entity);
    SQL::Entity::Condition->struct_to_condition(@condition);
}


=item order_by_clause

Returns order by sql fragment.

=cut

sub order_by_clause {
    my ($self) = @_;
    my $order_by = $self->order_by;
    $order_by ? " ORDER BY ${order_by}" : "";
}


=item associate_the_other_end

Associated the other end.

=cut

sub associate_the_other_end {
    my ($self, $entity) = @_;
    my $target_entity = $self->target_entity;
    $target_entity->add_to_one_relationships(sql_relationship(
        name => ($entity->id || $entity->name),
        target_entity => $entity,
        condition     => $self->condition,
        ($self->join_columns ? (join_columns => [$self->join_columns ]) : ())
    ));
}


=item sql_relationship

Creates a new relation object.

=cut

sub sql_relationship {
    __PACKAGE__->new(@_);
}



1;

__END__


=back

=head1 SEE ALSO

L<SQL::Entity>
L<SQL::Entity::Column>

=head1 COPYRIGHT AND LICENSE

The SQL::Entity::Relationship module is free software. You may distribute under the terms of
either the GNU General Public License or the Artistic License, as specified in
the Perl README file.

=head1 AUTHOR

Adrian Witas, adrian@webapp.strefa.pl

=cut