package Authorization::RBAC::Backend::DBIx;
$Authorization::RBAC::Backend::DBIx::VERSION = '0.09';
use Moose::Role;
use base 'DBIx::Class::Schema';
use Carp qw/croak/;
use Path::Class;
use Hash::Merge;

use FindBin '$Bin';
require UNIVERSAL::require;


my $attrs = {
             # starting with v3.3, SQLite supports the "IF EXISTS" clause to "DROP TABLE",
             # even though SQL::Translator::Producer::SQLite 1.59 isn't passed along this option
             # see https://rt.cpan.org/Ticket/Display.html?id=48688
             sqlite_version => 3.3,
             add_drop_table => 0,
             no_comments => 0,
             RaiseError => 1,
             PrintError => 0,
            };

has dsn          => (
                     is        => 'rw',
                     default   => sub {
                       my $self = shift;
                       return $self->model_config->{'connect_info'}->{dsn};
                     }
                    );

has model_config => (
                     is         => 'rw',
                     lazy_build => 1,
                    );

has 'schema'     => (
                     is        => 'rw',
                     predicate => 'has_schema',
                     lazy_build      => 1,
                    );

has typeobjs   => (
                     is         => 'rw',
                     default => sub{ return shift->schema->resultset('Typeobj')->search; }
                    );

has permissions   => (
                     is         => 'rw',
                     default => sub{ return shift->schema->resultset('Permission')->search; }
                    );


sub _build_model_config {
  my $self = shift;
  my $config       = $self->conf;
  my $model_config = $config->{$self->config->{model}}
    or croak "'Authorization::RBAC:model' is not defined in conf file !";
  return $model_config
}

sub get_operations{
  my ($self, $operations) = @_;

  my @ops;
  foreach my $op  ( @$operations ) {
    push( @ops, $self->schema->resultset('Operation')->search({ name => $op})->single );
  }
  return @ops;
}

sub get_permission{
  my ($self, $role, $op, $obj) = @_;

  my $typeobj    = ref($obj);
  $typeobj =~ s/.*:://;
  my $typeobj_rs = $self->schema->resultset('Typeobj')->search({ name => $typeobj})->single;

  my $permission = $self->schema->resultset('Permission')->search({ role_id      => $role->id,
                                                                    typeobj_id   => $typeobj_rs->id,
                                                                    obj_id       => $obj->id,
                                                                    operation_id => $op->id
                                                                  })->single;

  my $parent_field = $self->config->{typeobj}->{$typeobj}->{parent_field} || 'parent';

  if ( $permission ) {
    return ($permission->value, $permission->inheritable);
  }
  # Search permission on parents
  elsif ( $obj->can( $parent_field) ) {

    if ( $obj->$parent_field ){

      my $typeobj_parent    = ref($obj->$parent_field);
      $typeobj_parent =~ s/.*:://;
      $self->_log("  [??] Search inherited permissions on parents ${typeobj_parent}_" . $obj->$parent_field->id . "...");
      my ( $result, $inheritable)  = $self->get_permission($role, $op, $obj->$parent_field);
      if ( $inheritable || ! $result  ) {
        return ($result, $inheritable);
      }
    }
  }
  # No permission and no parent =>
  else {
      $self->_log("  No permission found :(");
      return 0;
  }
}


sub _connect_info {
  my $self = shift;

  my $model_config = $self->model_config;

  my ($dsn, $user, $password, $unicode_option, $db_type);
  eval {
    if (!$dsn)
      {
        if (ref $model_config->{'connect_info'}) {

          $dsn      = $model_config->{'connect_info'}->{dsn};
          $user     = $model_config->{'connect_info'}->{user};
          $password = $model_config->{'connect_info'}->{password};

          # Determine database type amongst: SQLite, Pg or MySQL
          $dsn =~ m/^dbi:(\w+)/;
          $db_type = lc($1);
          my %unicode_connection_for_db = (
                'sqlite' => { sqlite_unicode    => 1 },
                'pg'     => { pg_enable_utf8    => 1 },
                'mysql'  => { mysql_enable_utf8 => 1 },

                );
          $unicode_option = $unicode_connection_for_db{$db_type};
        }
        else {
          $dsn = $model_config->{'connect_info'};
        }
      }
  };

  if ($@) {
    die "Your DSN line in " . $self->conf . " doesn't look like a valid DSN.";
  }
  die "No valid Data Source Name (DSN).\n" if !$dsn;
  $dsn =~ s/__HOME__/$FindBin::Bin\/\.\./g;

  if ( $db_type eq 'sqlite' ){
    $dsn =~ m/.*:(.*)$/;
    my $dir = dir($1)->parent;
    $dir->mkpath;
  }

  my $merge    = Hash::Merge->new( 'LEFT_PRECEDENT' );
  my $allattrs = $merge->merge( $unicode_option, $attrs );

  return $dsn, $user, $password, $allattrs;
}


sub _build_schema {
  my $self = shift;

  my $schema_class = $self->model_config->{schema_class};
  $schema_class->require or die $@;

  my ($dsn, $user, $pass, $args ) = $self->_connect_info;
  my $schema = $schema_class->connect($dsn, $user, $pass, $attrs )
      or die "Failed to connect to database";
  return $schema;
}

=head1 NAME

Authorization::RBAC::Backend::DBIx - Backend 'DBIx' for Authorization::RBAC

=head1 VERSION

version 0.09

=head1 CONFIGURATION

         $rbac = Authorization::RBAC->new(conf => 't/permfromdb.yml');

         # with t/conf/permfromdb.yml :

         Model::RBAC:
           schema_class: Schema::RBAC
           connect_info:
             dsn: dbi:SQLite:t/db/permsfromdb.db

         Authorization::RBAC:
           debug: 0
           backend:
             name: DBIx
             model: Model::RBAC

=head2 REQUIRED SCHEMA

See t/lib/Schema/RBAC/Result/

User -> UserRole -> Role

Role -> Permission -> Object ( -> TypeObj )
                   -> Operation

=head1 PROVIDED METHODS

=head2 get_operations( $operations )

=head2 get_permission( $role, $op, $obj )

=head1 AUTHOR

Daniel Brosseau, C<< <dab at catapulse.org> >>

=head1 LICENSE AND COPYRIGHT

Copyright 2011 Daniel Brosseau.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut

1;
