package DBIx::dbMan::DBI;

use strict;
use locale;
use vars qw/$AUTOLOAD/;

use POSIX;
use DBIx::dbMan::Config;
use DBIx::dbMan::MemPool;
use DBI;

our $VERSION = '0.15';

1;

sub new {
    my $class = shift;
    my $obj = bless { @_ }, $class;

    $obj->clear_all_connections;
    $obj->load_groups();
    $obj->load_connections;

    return $obj;
}

sub connectiondir {
    my $obj = shift;

    return $ENV{ DBMAN_CONNECTIONDIR } if $ENV{ DBMAN_CONNECTIONDIR };

    return $obj->{ -config }->connection_dir if $obj->{ -config }->connection_dir;

    mkdir $ENV{ HOME } . '/.dbman/connections' unless -d $ENV{ HOME } . '/.dbman/connections';

    return $ENV{ HOME } . '/.dbman/connections';
}

sub groupdir {
    my $obj = shift;

    return $ENV{ DBMAN_GROUPDIR } if $ENV{ DBMAN_GROUPDIR };

    mkdir $ENV{ HOME } . '/.dbman/groups' unless -d $ENV{ HOME } . '/.dbman/groups';

    return $ENV{ HOME } . '/.dbman/groups';
}

sub clear_all_connections {
    my $obj = shift;
    $obj->{ connections } = {};
}

sub load_group {
    my ( $obj, $name ) = @_;

    my $gdir = $obj->groupdir();
    return -1 unless -d $gdir;
    $gdir =~ s/\/$//;
    return -2 unless -f "$gdir/$name";

    return new DBIx::dbMan::Config -file => "$gdir/$name";
}

sub load_groups {
    my $obj = shift;

    my $sdir   = $obj->groupdir;
    my %groups = ();

    if ( -d $sdir ) {
        opendir S, $sdir;
        for my $group ( grep ! /^\.\.?/, readdir S ) {
            $groups{ $group } = $obj->load_group( $group );
        }
        closedir S;
    }

    $obj->{ _groups } = \%groups;
}

sub get_group {
    my ( $obj, $group ) = @_;

    return $obj->{ _groups }->{ $group };
}

sub load_connections {
    my $obj = shift;

    my $cdir = $obj->connectiondir;
    return -1 unless -d $cdir;

    opendir D, $cdir;
    $obj->load_connection( $_ ) for grep ! /^\.\.?/, readdir D;
    closedir D;

    my $current = '';
    $current = $obj->{ -config }->current_connection if $obj->{ -config }->current_connection;
    $obj->{ -interface }->add_to_actionlist(
        {
            action    => 'CONNECTION',
            operation => 'use', what => $current
        }
    );
}

sub load_connection {
    my ( $obj, $name ) = @_;

    my $cdir = $obj->connectiondir;
    return -1 unless -d $cdir;
    $cdir =~ s/\/$//;
    return -2 unless -f "$cdir/$name";

    my $lcfg                = new DBIx::dbMan::Config -file => "$cdir/$name";

    my %processed_groups    = ();
    if ( $lcfg->group() ) {
        my $something_processed = 1;
        while ( $something_processed ) {
            $something_processed = 0;
            for ( $lcfg->group() ) {
                next if $processed_groups{ $_ };
                ++$something_processed;
                print STDERR "Error: Can't use group '$_' for connection '$name'\n" unless $lcfg->merge( $obj->get_group( $_ ) );
                ++$processed_groups{ $_ };
            }
        }
    }

    my %connection;
    $connection{ $_ } = $lcfg->$_ for $lcfg->all_tags;
    $obj->{ connections }->{ $name } = \%connection;

    $obj->{ -interface }->add_to_actionlist(
        {
            action    => 'CONNECTION',
            operation => 'open', what => $name
        }
    ) if lc $lcfg->auto_login eq 'yes';
}

sub open {
    my ( $obj, $name ) = @_;

    return -3 unless exists $obj->{ connections }->{ $name };
    return -4 if $obj->{ connections }->{ $name }->{ -logged };
    return -1 unless grep { $_ eq $obj->{ connections }->{ $name }->{ driver } } $obj->driverlist;

    my %vars = qw/PrintError 0 RaiseError 0 AutoCommit 1 LongTruncOk 1/;

    # in case Oracle we need from 11R2 provide information about supported signals
    if ( $obj->{ connections }->{ $name }->{ driver } eq 'Oracle' ) {
        $vars{ ora_connect_with_default_signals } = [ 'INT' ];
    }

    if ( $obj->{ connections }->{ $name }->{ config } ) {
        for ( split /;\s*/, $obj->{ connections }->{ $name }->{ config } ) {
            if ( /^\s*(\S+?)\s*=\s*(\S+)\s*$/ ) {
                my ( $var, $val ) = ( $1, $2 );
                next if $var eq 'AutoCommit';    # everything unless transactions
                $val = eval $val if $val =~ /^\[(.*)\]$/ || $val =~ /^\{(.*)\}$/;
                $vars{ $var } = $val;
            }
        }
    }

    my $dbi = DBI->connect(
        'dbi:' . $obj->{ connections }->{ $name }->{ driver } . ':' . $obj->{ connections }->{ $name }->{ dsn },
        $obj->{ connections }->{ $name }->{ login },
        $obj->{ connections }->{ $name }->{ password },
        \%vars
    );

    return -2 unless defined $dbi;

    $obj->{ connections }->{ $name }->{ -dbi }     = $dbi;
    $obj->{ connections }->{ $name }->{ -mempool } = new DBIx::dbMan::MemPool;
    $obj->{ connections }->{ $name }->{ -logged }  = 1;
    $obj->{ -interface }->add_to_actionlist( { action => 'AUTO_SQL', connection => $name } );

    return 0;
}

sub driverlist {
    my $obj = shift;
    return DBI->available_drivers;
}

sub close {
    my ( $obj, $name ) = @_;

    return -1 unless exists $obj->{ connections }->{ $name };
    return -2 unless $obj->{ connections }->{ $name }->{ -logged };

    $obj->set_current() if $obj->{ current } eq $name;
    $obj->discard_profile_data();
    delete $obj->{ connections }->{ $name }->{ -logged };
    $obj->{ connections }->{ $name }->{ -dbi }->disconnect();
    undef $obj->{ connections }->{ $name }->{ -dbi };
    undef $obj->{ connections }->{ $name }->{ -mempool };

    return 0;
}

sub close_all {
    my $obj = shift;
    for my $name ( keys %{ $obj->{ connections } } ) {
        if ( $obj->{ connections }->{ $name }->{ -logged } ) {
            $obj->close( $name );
            $obj->{ -interface }->print( "Disconnected from $name.\n" );

            # we can't move this message to extension - close_all called when
            # destroying DBI object (handle event collapsed :(, no OUTPUT event exist)
        }
    }
}

sub DESTROY {
    my $obj = shift;
    $obj->close_all;
}

sub list {
    my ( $obj, $what ) = @_;
    my @returned = ();

    for my $name ( keys %{ $obj->{ connections } } ) {
        my %r = %{ $obj->{ connections }->{ $name } };
        next if ( $what eq 'inactive' and $r{ -logged } ) || ( $what eq 'active' and ! $r{ -logged } );
        $r{ name } = $name;
        push @returned, \%r;
    }

    return [ sort { $a->{ name } cmp $b->{ name } } @returned ];
}

sub autosql {
    my $obj = shift;

    return -1 unless $obj->{ current };
    return -2 unless exists $obj->{ connections }->{ $obj->{ current } };
    return $obj->{ connections }->{ $obj->{ current } }->{ autosql };
}

sub silent_autosql {
    my $obj = shift;

    return -1 unless $obj->{ current };
    return -2 unless exists $obj->{ connections }->{ $obj->{ current } };
    return $obj->{ connections }->{ $obj->{ current } }->{ silent_autosql };
}

sub set_current {
    my ( $obj, $name ) = @_;

    return 9999 if $obj->{ current } eq $name;

    unless ( $name ) { delete $obj->{ current }; return 1; }

    return -1 unless exists $obj->{ connections }->{ $name };
    return -2 unless $obj->{ connections }->{ $name }->{ -logged };

    $obj->{ current } = $name;
    return 0;
}

sub current {
    my $obj = shift;
    return $obj->{ current };
}

sub drop_connection {
    my ( $obj, $name ) = @_;
    return -1 unless exists $obj->{ connections }->{ $name };
    $obj->close( $name ) if $obj->{ connections }->{ $name }->{ -logged };
    delete $obj->{ connections }->{ $name };
    return 0;
}

sub create_connection {
    my ( $obj, $name, $p ) = @_;
    my %parms = %$p;

    return -1 if exists $obj->{ connections }->{ $name };

    $obj->{ connections }->{ $name } = \%parms;
    return 100 + $obj->open( $name ) if lc $parms{ auto_login } eq 'yes';
    return 0;
}

sub save_connection {
    my $obj  = shift;
    my $name = shift;

    return -1 unless exists $obj->{ connections }->{ $name };

    my $cdir = $obj->connectiondir;
    mkdir $cdir unless -d $cdir;
    return -1   unless -d $cdir;
    $cdir =~ s/\/$//;
    CORE::open F, ">$cdir/$name" or return -2;
    for ( qw/driver dsn login password auto_login config/ ) {
        print F "$_ " . $obj->{ connections }->{ $name }->{ $_ } . "\n"
            if exists $obj->{ connections }->{ $name }->{ $_ }
            and $obj->{ connections }->{ $name }->{ $_ } ne '';
    }
    CORE::close F;
    chmod 0600, "$cdir/$name";
    return 0;
}

sub destroy_connection {
    my $obj  = shift;
    my $name = shift;

    my $cdir = $obj->connectiondir;
    return -1 unless -d $cdir;
    $cdir =~ s/\/$//;
    return 1 unless -e "$cdir/$name";
    unlink "$cdir/$name";
    return -2 if -e "$cdir/$name";
    return 0;
}

sub is_permanent_connection {
    my $obj  = shift;
    my $name = shift;
    my $cdir = $obj->connectiondir;
    return 0 unless -d $cdir;
    $cdir =~ s/\/$//;
    return -e "$cdir/$name";
}

sub trans_begin {
    my $obj = shift;
    return -1 unless $obj->{ current };
    $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ AutoCommit } = 0;
}

sub longreadlen {
    my $obj  = shift;
    my $long = shift;
    $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ LongReadLen } = $long if $long;
    return $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ LongReadLen };
}

sub trans_end {
    my $obj = shift;
    return -1 unless $obj->{ current };
    $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ AutoCommit } = 1;
}

sub in_transaction {
    my $obj = shift;
    return 0 unless $obj->{ current };
    return not $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ AutoCommit };
}

sub driver {
    my $obj = shift;
    return undef unless $obj->{ current };
    return $obj->{ connections }->{ $obj->{ current } }->{ driver };
}

sub login {
    my $obj = shift;
    return undef unless $obj->{ current };
    return $obj->{ connections }->{ $obj->{ current } }->{ login };
}

sub prompt_color {
    my $obj = shift;
    return undef unless $obj->{ current };
    return $obj->{ connections }->{ $obj->{ current } }->{ prompt_color };
}

sub AUTOLOAD {
    my $obj = shift;

    $AUTOLOAD =~ s/^DBIx::dbMan::DBI:://g;
    return undef unless $obj->{ current };
    return undef unless exists $obj->{ connections }->{ $obj->{ current } };
    return undef unless $obj->{ connections }->{ $obj->{ current } }->{ -logged };
    return undef unless defined $obj->{ connections }->{ $obj->{ current } }->{ -dbi };

    my $dbi = $obj->{ connections }->{ $obj->{ current } }->{ -dbi };

    return $dbi->$AUTOLOAD( @_ );
}

sub set {
    my ( $obj, $var, $val ) = @_;
    return unless $obj->{ current };

    $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ $var } = $val;
}

sub get {
    my ( $obj, $var ) = @_;
    return undef unless $obj->{ current };
    return $obj->{ connections }->{ $obj->{ current } }->{ -dbi }->{ $var };
}

sub discard_profile_data {
    my $obj = shift;
    return unless $obj->{ current };

    #	$obj->{connections}->{$obj->{current}}->{-dbi}->{Profile}->{Data} = undef;
}

sub mempool {
    my $obj = shift;
    return undef unless $obj->{ current };
    return $obj->{ connections }->{ $obj->{ current } }->{ -mempool };
}
