package ARDB::Plugins;

use strict;
use warnings;

use Carp;

use Carp::Assert;

use ARDB::Common;
#$ARDB::DEBUG = 1;

#########################################################################
##                          constructor                                ##
#########################################################################

sub new
 {
  my $class = shift;
  my $home  = shift;
  my $ardb  = shift;

  my $plugins_path = $home . '/plugins';
  $plugins_path    =~ s/\/\//\//g;
  
  my $self = 
   {
    'prefixes'     => {},
    'record_types' => {},
    'plugin-path'  => $plugins_path,
    'home'         => $plugins_path,
    'start-list'   => undef,
    'ok'           => 'ok',
    'ardb'         => $ardb,
   };

  bless $self, $class;

  $self -> init;

  return $self;

 }

sub start_list
 {
  my $self = shift;
  
  if (ref $self -> {'start-list'} eq 'ARRAY')
   {
    return $self -> {'start-list'};
   }
  
  ########################################################
  #print 'plugins path [', $self->{plugin-path}, "]\n";
  ########################################################
  
  my $plugin_path = $self -> {'plugin-path'};

  debug "reading plugin directories from '$plugin_path'";

  my @independent_plugins;
  
  opendir ( PROCESSING_PLUGINS, $plugin_path . '/Processing/' );
  
  my @plugins;
  my %plugin_homes;

  my @plugin_ids =
    grep { 
        !/^\.+$/ 
	and -d "$plugin_path/Processing/$_" 
	  and !/^CVS$/
      } readdir ( PROCESSING_PLUGINS );
  

  foreach ( @plugin_ids ) {
    push @plugins, "ARDB::Plugin::Processing::$_";
    $plugin_homes{"ARDB::Plugin::Processing::$_"} = "$plugin_path/Processing/$_";
  }

  closedir ( PROCESSING_PLUGINS );

  my %plugins;
  my @start_list = ();

  debug "found plugins: ". join (", ", @plugins);
    
  
  foreach my $plugin_name ( @plugins )
   {
    my $id = shift @plugin_ids;
    
    debug "using $plugin_name";
    eval "use $plugin_name;";

    if ( $@ )
     {
      critical ( "cannot load plugin $plugin_name, error: '$@'" );
      die "Cannot load ARDB plugin $plugin_name: $@";
      next;
     }

#    elsif ( $plugin_name =~ /^ardb::plugin::processing::/i )
    if( 1 )
     {
      my $plugins_list;
      
      debug "try read dependencies of '$plugin_name'";
      
      eval "\$plugins_list =  $plugin_name -> require";
      unless ( $@ )
       {
        if ( scalar @$plugins_list )
         {
          foreach ( @$plugins_list )
           {
            debug "plugin '$plugin_name' require '$_'";
           
            push @{ $plugins{$_} -> {prerequisite} }, $plugin_name;
            $plugins{$plugin_name} -> {required} -> {$_} = 1;
           }
         }
        else
         {
          debug "plugin '$plugin_name' have no dependecies";
          push @independent_plugins, $plugin_name;
         }
       }
     }
    else
     {
      debug "strange plugin '$plugin_name'";
      
      push @independent_plugins, $plugin_name;
     }

   }
  
  debug "push plugins into start list";
  
  my $deep = 0;
  while ( scalar @independent_plugins )
   {
    debug "at $deep level found independent plugins: ".
     join (", ", @independent_plugins);
    my @independent_candidate = ();
    foreach my $plugin_name ( @independent_plugins )
     {
      push @start_list, [$plugin_name, $deep, $plugin_homes{$plugin_name} ];
      
      foreach my $prerequisite ( @{ $plugins{$plugin_name} -> {'prerequisite'} } )
       {
        delete $plugins{$prerequisite} -> {'required'} -> {$plugin_name};
        
        push ( @independent_candidate, $prerequisite )
         unless ( scalar %{ $plugins{$prerequisite} -> {'required'} } );
       }
     }
    $deep++;
    @independent_plugins = @independent_candidate;
    
   }
 
  $self -> {'start-list'} = \@start_list;
  
  return $self -> {'start-list'};
 }


sub init
 {
  my $self = shift;
  
  my $start_list = $self -> start_list;
  
  

  ########################################################
  #print 'start list [', @start_list, "]\n";
  ########################################################

  foreach my $plugin_ref ( @$start_list )
   {
    #die Dumper $plugin_ref;
    my $plugin_name = $plugin_ref -> [0];
    my $plugin_deep = $plugin_ref -> [1];
    my $plugin_home = $plugin_ref -> [2];

    my $plugin;

    $plugin = "$plugin_name" -> new ( $plugin_home ) ;

    unless ( defined $plugin )
     {
      critical ( "cannot create $plugin_name object, error: $@");
     }
    
    unless ( $plugin -> status )
     {
      critical ( "cannot initialize $plugin_name object, error: $@")
       unless ( $plugin -> init );
     }
    
#    if ( $plugin_name =~ /^ardb::plugin::input::/i )
#     {
#      my $prefixes = "$plugin_name" -> get_prefixes ;
#      foreach (@$prefixes)
#       {
#        $self -> {prefixes} -> {$_} = $plugin_name ;
#       }
#     }
#    elsif ( $plugin_name =~ /^ardb::plugin::processing::/i )
     {
      my $record_types = "$plugin_name" -> get_record_types;
      foreach ( @$record_types )
       {
        push @{ $self -> {record_types} -> {$_} }, $plugin ;
       }
     }
   }
 }


sub process_record
 {
  my $self   = shift;
  my $record = shift;

  my $result = 'ok';

  my $record_type = $record -> type;

  debug "search for plugin, responsible for a current record-type";
  
  my $plugins = $self -> {record_types} -> {$record_type};
  
  foreach my $plugin ( @$plugins )
   {
    assert( $plugin );

    debug "try storing record with $plugin";
    $plugin -> process_record ( $record, $self->{ardb} )
      or $result = undef;
    
   }
  return $result;
 }

########################################################
### obsolete, replaced by ARDB::ObjectDB::get_record ###
########################################################

sub get_record
 {
  my $self   = shift;
  my $handle = shift;
  my $work_plugin_name;
  debug "search for plugin, responsible for a current template handle";
  while ( my ( $handle_prefix, $plugin_name ) = each ( %{ $self -> {prefixes} } ) )
   {
    if ( $handle =~ /^$handle_prefix/ )
     {
      $work_plugin_name = $plugin_name;
      #last;
     }
   }
  unless ( defined $work_plugin_name )
   {
    return undef;
   }
  # print $work_plugin_name, "\n";
  # print "ARDB::Plugins::$work_plugin_name" -> status;
  # "ARDB::Plugins::$work_plugin_name" -> init;
  return "$work_plugin_name" -> get_record ( $handle );
 }

1;

=pod

=head1 NAME

ARDB::Plugin - class for management ARDB's plugins

=head1 methods

=over 1

=item get_start_list

each Processing plugin have method required, it return ARRAY required
plugins. / no examples at this time /



этот метод возвращает список plugins так, как они должны стартовать. тип
возвращаемого значения - ARRAY, значения элементов - ARRAY, первый элемент
которого - название plugin, второе - вложенность.

! имена plugins в секции required configuration.xml и имена plugins
возвращаемые методом get_start_list не имеют префикса ARDB::Plugins

=back

=cut
