package DBIx::dbMan;

=comment
	dbMan 0.30
	(c) Copyright 1999-2003 by Ing. Milan Sorm, sorm@pef.mendelu.cz
	All rights reserved.

	This software provides some functionality in database managing
	(SQL console).

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

	But I don't wish dbMan's using in military sector (army, NATO etc.).
	You can't use dbMan if you are from U.S.A., Germany or Israel
	(due to their militaristic government).
=cut

use strict;
use vars qw/$VERSION/;
use DBIx::dbMan::Config;	# configuration handling package
use DBIx::dbMan::Lang;		# I18N package - EXPERIMENTAL
use DBIx::dbMan::DBI;		# dbMan DBI interface package
use DBIx::dbMan::MemPool;	# dbMan memory management system package

$VERSION = '0.30';

# constructor, arguments are hash of style -option => value, stored in internal attributes hash
sub new {
	my $class = shift;
	my $obj = bless { @_ }, $class;
	return $obj;
}

# main loop of dbMan life-cycle, called from exe file
sub start {
	my $obj = shift;	# main dbMan core object

	$obj->{-trace} = 0;	# standard extension tracing activity - DISABLED

	# what interface exe file want ??? making package name from it
	my $interface = $obj->{-interface};
	$interface = 'DBIx/dbMan/Interface/'.$interface.'.pm';

	# we try to require interface package - found in @INC, syntax check,
	# load it by require instead of use because we know only filename
	eval { require $interface; };
	if ($@) { 			# if something goes wrong
		$interface =~ s/\//::/g;  $interface =~ s/\.pm$//;

		# bad information for user :-(
		print STDERR "Can't locate interface module $interface\n";
		return;		# see you later...
	}

	# making class name from interface package filename
	$interface =~ s/\//::/g;  $interface =~ s/\.pm$//;

	# creating memory management object - mempool
	$obj->{mempool} = new DBIx::dbMan::MemPool;

	# creating configuration object
	$obj->{config} = new DBIx::dbMan::Config;

	# creating I18N specifics object with configuration object as argument
	$obj->{lang} = new DBIx::dbMan::Lang -config => $obj->{config};

	# creating loaded interface object, all objects as arguments
	# included dbMan core object
	$obj->{interface} = $interface->new(-config => $obj->{config},
		-lang => $obj->{lang}, -mempool => $obj->{mempool}, -core => $obj);

	# we have interface now, we can produce messages and errors by object
	# method $obj->{interface}->print('what we can say to user...')

	# dbMan interface, please introduce us to our user (welcome message, splash etc.)
	$obj->{interface}->hello();

	# creating dbMan DBI object - encapsulation of DBI with multiple connections
	# support, configuration, interface and mempool as arguments
	$obj->{dbi} = new DBIx::dbMan::DBI -config => $obj->{config},
		-interface => $obj->{interface}, -mempool => $obj->{mempool};

	# looking for and loading all extensions
	$obj->load_extensions;

	# main loop derived by interface - get_action & handle_action calling cycle
	# NOT CALLED if we are in $main::TEST mode (tested initialization from make test)
	$obj->{interface}->loop() unless defined $main::TEST && $main::TEST;

	# unloading all loaded extensions
	$obj->unload_extensions;

	# close all opened DBI connections by dbMan DBI object
	$obj->{dbi}->close_all();

	# dbMan interface, please say good bye to our user...
	$obj->{interface}->goodbye();

	# test result OK if we are in $main::TEST mode (tested initialization from make test)
	$main::TEST_RESULT = 1 if defined $main::TEST && $main::TEST;

	# program must correctly exit if we want 'test ok' for make test' tests
	exit if $main::TEST_RESULT;
}

# looking for and loading extensions
sub load_extensions {
	my $obj = shift;		# main dbMan core object

	$obj->{extensions} = [];	# currently loaded extensions = no extensions

	# 1st phase : candidate searching algorithm
	my %candidates = ();		# what are my candidates for extensions ?
	for my $dir ($obj->extensions_directories) {	# all extensions directories
		opendir D,$dir;				# search in directory
		for (grep /\.pm$/,readdir D) { 		# for each found package
			eval { require "$dir/$_"; };	# try to require
			next if $@;			# not candidate if fail
			s/\.pm$//;			# make class name from filename
			my $candidate = "DBIx::dbMan::Extension::".$_;

			# search for extension version limit (class method) - low and high
			my ($low,$high) = ('',''); 
			eval { ($low,$high) = $candidate->for_version(); };

			# not candidate if our version isn't between low and high
			# we must delete filename from include list
			if (($low and $VERSION < $low) or ($high and $VERSION > $high)) 
				{ delete $INC{"$dir/$_.pm"};  next; }

			# fetching identification from extension (class method)
			my $id = '';  eval { $id = $candidate->IDENTIFICATION(); };

			# not candidate if identification not specified
			unless ($id or $@) { delete $INC{"$dir/$_.pm"};  next; }

			# parsing identification AUTHOR-MODULE-VERSION
			my ($ident,$ver) = ($id =~ /^(.*)-(.*)$/);

			# not candidate if AUTHOR-MODULE isn't overloaded
			if ($ident eq '000001-000001') { delete $INC{"$dir/$_.pm"};  next; }

			# deleting filename from include list
			delete $INC{"$dir/$_.pm"};

			# not candidate if exist this identification with same or higher version
			next if exists $candidates{$ident} && $candidates{$ident}->{-ver} >= $ver;

			# save candidate to candidates list
			$candidates{$ident} = 
				{ -file => "$dir/$_.pm", -candidate => $candidate, -ver => $ver }; 
		};
		
		closedir D;			# close searched directory
	}

	# 2nd phase : candidate loading algorithm
	my %extensions = ();			# all objects of extensions

	$obj->{extension_iterator} = 0;		# randomize iterator
	for my $candidate (keys %candidates) {	# for each candidate
		my $ext = undef;		# undefined extension
		eval {				# try require file and create object
			require $candidates{$candidate}->{-file};

			# object pass all five instances of base objects as argument
			$ext = $candidates{$candidate}->{-candidate}->new(
				-config => $obj->{config}, 
				-interface => $obj->{interface},
				-dbi => $obj->{dbi},
				-core => $obj,
				-mempool => $obj->{mempool});
		};
		if (defined $ext and not $@) {	# successful loading ?
			my $preference = 0;	# standard preference level
			eval { $preference = $ext->preference(); }; # trying to fetch preference

			# sorting criteria are: preference, random iterator
			# saving sort criteria for later using
			$ext->{'___sort_criteria___'} = $preference.'_'.$obj->{extension_iterator};

			# save instance of object to hash indexed by preference
			$extensions{$preference.'_'.$obj->{extension_iterator}} = $ext;

			++$obj->{extension_iterator};	# increase random iterator
		}
	}

	# 3rd phase : building candidates list sorted by preference (for action handling)
	for (sort { 	# sorting criteria - first time by preference, second time loading order
			my ($fa,$sa,$fb,$sb) = split /_/,$a.'_'.$b; 
			($fa == $fb)?($sa <=> $sb):($fb <=> $fa);
		} keys %extensions) {		# for all loaded extensions

		# save extension into sorted list
		push @{$obj->{extensions}},$extensions{$_};

		# call init() for initializing extension (all extensions in correct order)
		$extensions{$_}->init();
	}

	# all extensions are loaded and sorted by preference into $obj->{extensions} list
}

# unloading all extensions
sub unload_extensions {
	my $obj = shift;		# main dbMan core object

	for (@{$obj->{extensions}}) {	# for all extensions in standard order
		$_->done();		# call done() for finalizing extension
		undef $_; 		# destroy extension instance of object
	}
}

# produce list of all extensions directories
sub extensions_directories {
	my $obj = shift;		# main dbMan core object

	# grep criteria - only directories which contains DBIx/dbMan/Extension subfolder are wanted
	# tested dirs are: @INC, extensions_dir configuration directive, current folder
	# WARNING: i must call extensions_dir in list context if I want list of directories
	return grep { -d $_ } map { my $t = $_;  $t =~ s/\/$//; "$t/DBIx/dbMan/Extension" } 
		(@INC,($obj->{config}->extensions_dir?($obj->{config}->extensions_dir):()),'.');
}

# show tracing record via interface object
sub trace {
	my ($obj,$direction,$where,%action) = @_;	# main dbMan core object,
	# direction string (passed to interface), extension object and action record

	# change $where to readable form
	$where =~ s/=.*$//;  $where =~ s/^DBIx::dbMan::Extension:://;  my $params = '';
	for (sort keys %action) {	# for all actions
		next if $_ eq 'action';	# action tag ignore
		my $p = $action{$_};  $p = "'$p'" if $p !~ /^[-a-z0-9_.]+$/i;	# stringify
		$params .= ", " if $params;  $params .= "$_: $p";	# concat
	}

	# change non-selected chars in $params to <hexa> style
	$params = join '',		# joining transformed chars
		map { ($_ >= 32 && $_ <= 254 && $_ != 127)?chr:sprintf "<%02x>",$_; }
		unpack "C*",$params;		# disassemble $params into chars

	# sending tracing report via interface object
	$obj->{interface}->print("$direction $where / $action{action} / $params\n");
}

# main loop for handling one action
sub handle_action {
	my ($obj, %action) = @_;		# main dbMan core object, action to process
		
	for my $ext (@{$obj->{extensions}}) {	# going down through all extensions in preference order
		$action{processed} = undef;
		last if $action{action} eq 'NONE';	# stop on NONE actions

		my $acts = undef;
		eval { $acts = $ext->known_actions; };	# hack - which actions extension want ???
		next if $@ && defined $acts && ref $acts eq 'ARRAY' && 
				! grep { $_ eq $action{action} } @$acts;   # use hacked knowledge

		$obj->trace("<==",$ext,%action) if $obj->{-trace};	# trace if user want

		$action{processed} = undef;		# standard behaviour - action not processed
		eval { %action = $ext->handle_action(%action); };	# handling action
		$obj->{interface}->print("Exception catched: $@\n") if $@;	# error - exception

		$obj->trace("==>",$ext,%action) if $obj->{-trace};	# trace if user want

		return %action unless $action{processed};	# action wasn't processed corectly 
			# ... prefix probably set - return to get_event (and called once again we hope)
	}

	# action processed correctly, good bye with modified action record
	$action{processed} = 1;
	return %action;
}

1;	# all is O.K.
