package Lire::PluginManager;

use strict;

use vars qw/ $instance %plugin_types /;

use Carp;

use Lire::Config;
use Lire::Error qw/ file_not_readable /;
use Lire::OldDlfAdapter;
use Lire::Utils qw/ file_content check_param check_object_param /;

$instance = new Lire::PluginManager();

%plugin_types = ( 'output_format' => 'Lire::OutputFormat',
                  'chart_type'     => 'Lire::ChartType',
                  'dlf_analyser'  => 'Lire::DlfAnalyser',
                  'dlf_converter' => 'Lire::DlfConverter', );

sub new {
    return bless {  'output_format' => {},
                    'dlf_analyser'  => {},
                    'dlf_converter' => {},
                    'chart_type'    => {},
                 }, shift;
}

=pod

=head1 NAME

Lire::PluginManager - Manages the Lire plugins.


=head1 SYNOPSIS

  use Lire::PluginManager;

  Lire::PluginManager->register_default_plugins();

  my $plugin = new MyDlfConverter();
  Lire::PluginManager->register_plugin( $plufin );

  my $plugins = Lire::PluginManager->plugins( 'dlf_analyser' );

  my $converter = Lire::PluginManager->get_plugin('dlf_analyser', 'combined');

=head1 DESCRIPTION

All method can be called as class method on the Lire::PlugingManager
module or on an instance.

=head2 PLUGIN TYPES

There are four kind of plugins defined in Lire: dlf_analyser,
dlf_converter, chart_type and output_format.

=head2 instance()

Returns the singleton instance of the PluginManager

=cut

sub instance {
    return $instance;
}

sub check_type {
    my ( $type ) = @_;

    check_param( $type, 'type' );
    return 1 if exists $plugin_types{$type};

    my @types = map { "'$_'" } sort keys %plugin_types;
    croak "'type' parameter should be one of " . join ( ", ", @types[0..$#types-1] ) . " or $types[-1]: '$type'";
}

=pod

=head2 plugin_names( $type )

Returns an array reference containing the names of all registered
plugin of type $type.

=cut

sub plugin_names {
    my ( $self, $type ) = @_;
    $self = $instance unless ref $self;

    check_type( $type );

    return [ keys %{$self->{$type}} ];
}

=pod

=head2 plugins( $type )

Returns an array reference containing all the Plugin objects registerd
for $type.

=cut

sub plugins {
    my ( $self, $type ) = @_;
    $self = $instance unless ref $self;

    check_type( $type );

    return [ values %{$self->{$type}} ];
}

=pod

=head2 has_plugin( $type, $name )

Returns true if there is a Lire::Plugin named $name registered for type
$type.

=cut

sub has_plugin {
    my ( $self, $type, $name ) = @_;
    $self = $instance unless ref $self;

    check_param( $name, 'name' );
    check_type( $type );

    return exists $self->{$type}{$name};
}

=pod

=head2 get_plugin( $type, $name )

Returns the Lire::Plugin object named $name of type $type. Croaks when
there is no plugin registerd under that name.

=cut

sub get_plugin {
    my ( $self, $type, $name ) = @_;
    $self = $instance unless ref $self;

    check_param( $name, 'name' );
    check_type( $type );

    croak "no '$type' plugin '$name' registered"
      unless $self->has_plugin( $type, $name );

    return $self->{$type}{$name};
}

=pod

=head2 register_plugin( $plugin )

Registers the Lire::Plugin $plugin. The type and name of the plugin
are queried using the Lire::Plugin interface. An error will be thrown
if there is already a plugin registered under the same name and type.

=cut

sub register_plugin {
    my ( $self, $plugin ) = @_;
    $self = $instance unless ref $self;

    check_object_param( $plugin, 'plugin', 'Lire::Plugin',  );

    my $type = $plugin->type();

    croak( "plugin '", $plugin->name(), "' has an unknown type: '$type'" )
      unless exists $plugin_types{$type};

    croak( "'$type' plugin should be a '$plugin_types{$type}' instance: '",
           ref($plugin), "'" )
      unless UNIVERSAL::isa( $plugin, $plugin_types{$type} );

    croak( "there is already a '$type' plugin registered under name '",
           $plugin->name(), "'" )
      if $self->has_plugin( $type, $plugin->name() );

    $self->{$type}{$plugin->name()} = $plugin;

    return;
}

=pod

=head2 unregister_plugin( $type, $name )

Unregister the plugin $name of type $type. The method will dies if
there is no such plugin.

=cut

sub unregister_plugin {
    my ( $self, $type, $name) = @_;
    $self = $instance unless ref $self;

    check_param( $name, 'name' );
    check_type( $type );

    croak "no '$type' plugin '$name' registered"
      unless $self->has_plugin( $type, $name );

    delete $self->{$type}{$name};

    return;
}

=pod

=head2 analysers_by_src( $schema )

Returns in an array reference all the analysers that declare $schema
as their src_schema().

=cut

sub analysers_by_src {
    my ( $self, $schema ) = @_;

    check_param( $schema, 'schema' );

    my @result = ();
    foreach my $analyser ( @{$self->plugins( 'dlf_analyser' )} ) {
        push @result, $analyser
          if $analyser->src_schema() eq $schema;
    }
    return \@result;
}

=pod

=head2 analysers_by_dst( $schema )

Returns in an array reference all the analysers that declare $schema
as their dst_schema().

=cut

sub analysers_by_dst {
    my ( $self, $schema ) = @_;

    check_param( $schema, 'schema' );

    my @result = ();
    foreach my $analyser ( @{$self->plugins( 'dlf_analyser' )} ) {
        push @result, $analyser
          if $analyser->dst_schema() eq $schema;
    }

    return \@result;
}

=pod 

=head2 register_default_plugins()

This method will load and initialize all the plugins available. The
plugins are registered by executing all the perl scripts found in the
directories listed in the 'plugins_init_path' configuration variable.

These scripts should call Lire::PluginManager->register_plugin() to
register the plugins.

For compatibility with previous versions of Lire, it will also
registers the DlfConverters initialized using older mechanism.

=cut

sub register_default_plugins {
    my $self = $_[0];
    $self = $instance unless ref $self;

    my $init_files =
      $self->_init_files( Lire::Config->get( 'plugins_init_path' ) );
    foreach my $init_file ( @$init_files ) {
        eval file_content( $init_file );
        warn( "error in initialiser '$init_file': $@\n" )
          if $@;
    }

    $self->_create_old_dlf_adapters();
    $self->_load_dlf_adapters();

    return;
}

sub _create_old_dlf_adapters {
    my $self = $_[0];

    my $convertors_dir = Lire::Config->get( 'lr_old_convertors_dir' );
    my $address_file = Lire::Config->get( 'lr_old_address_file' );
    my $service2schema = $self->_parse_old_map_file( $address_file );
    while ( my ($service, $schema) = each %$service2schema ) {
        unless ( Lire::DlfSchema->has_superservice( $schema )) {
            warn "invalid superservice '$schema' assigned to service '$service'\n";
            next;
        }
        my $script = $convertors_dir .'/' . $service . '2dlf';
        if ( -x $script ) {
            my $adapter = new Lire::OldDlfAdapter( $schema, $script );
            $self->register_plugin( $adapter );
        } else {
            warn "can't find executable $service" . "2dlf in $convertors_dir\n";
        }
    }
}

sub _parse_old_map_file {
    my ($self, $file) = @_;

    my %map = ();

    open my $fh, $file
      or croak( file_not_readable( $file ) );
    my $line;
    while ( defined($line = <$fh>) ) {
        next if $line =~ /^#/;       # Skip comments
        next if $line =~ /^\s*$/;    # Skip empty lines
        my ($key, $value) = $line =~ /^(\S+)\s+(\S+)\s*(#.*)?$/
          or warn "can't parse line $. of file '$file'\n";
        $map{$key} = $value
          if defined $key;
    }
    close $fh;

    return \%map;
}

sub _load_dlf_adapters {
    my $self = $_[0];

    my $init_files =
      $self->_init_files( Lire::Config->get( 'lr_converters_init_path' ) );
    foreach my $init_file ( @$init_files )
    {
        my $initializer = eval {  file_content( $init_file ) };
        if ( $@ ) {
            warn "error reading DLF converter initializer file '$init_file': $@\n";
            next;
        }
        my @converters = eval $initializer;
        if ( $@ ) {
            warn "error while running initializer in '$init_file': $@\n";
            next;
        }
        foreach my $c ( @converters ) {
            if ( UNIVERSAL::isa( $c, 'Lire::DlfConverter' ) ) {
                $self->register_plugin( $c );
            } else {
                warn "initializaer '$init_file' didn't return a Lire::DlfConverter object: $c\n";
            }
        }
    }
}

sub _init_files {
    my ( $self, $dirs ) = @_;

    my @initializers = ();
    my %dirs;
    foreach my $dir ( @$dirs ) {
        next if exists $dirs{$dir};
        $dirs{$dir} = 'done';
        opendir my $dh, $dir
          or croak "opendir failed '$dir': $!";
        foreach my $file ( map { "$dir/$_" } readdir $dh ) {
            next unless -f $file;
            push @initializers, $file;
        }
        closedir $dh;
    }

    return \@initializers;
}

# keep perl happy
1;

__END__

=pod

=head1 SEE ALSO

Lire::Plugin(3pm), Lire::OutputFormat(3pm), Lire::DlfAnalyser(3pm),
Lire::DlfConverter(3pm)

=head1 AUTHOR

  Francis J. Lacoste <flacoste@logreport.org>

=head1 VERSION

$Id: PluginManager.pm,v 1.8 2006/07/23 13:16:29 vanbaal Exp $

=head1 COPYRIGHT

Copyright (C) 2004 Stichting LogReport Foundation LogReport@LogReport.org

This file is part of Lire.

Lire is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html.

=cut
