package Common::DB::Deploy::DatabaseManager;

use strict;
use warnings;

use DBI;

use constant TABLE_NAME   => 'db_changelog';
use constant LOCK_TIMEOUT => 10;
use constant DB_CATALOG   => {
    rscommon => 'rscommon',
    c_       => 'rps',
    bp_      => 'dtmv',
};
use constant DB_ACCESSORS => {
    driver           => 'mysql',
    engine           => 'innodb',
    charset          => 'utf8',
    host             => 'localhost',
    port             => 3306,
    database         => 'mysql',
    user             => 'root',
    pass             => '',
    raise_error      => 1,
    connect_retries  => 3,    # times
    connect_timeout  => 1,    # sec
    check_connection => 0,
};

sub new {
    my ($class, %args) = @_;

    my $self = bless {
        oLogger        => $args{oLogger},
        table_name     => $args{table_name} || TABLE_NAME,
        _dbh           => undef,
    }, $class;

    $self->_init(\%args);
    return $self;
}

sub _init {
    my ($self, $hOption) = @_;

    while ( my($key, $val) = each %{ +DB_ACCESSORS } ) {
        (my $method = $key) =~ s/_(.*)/\u$1/g;
        if ( exists $hOption->{$key} && defined $hOption->{$key} ) {
            $val = $hOption->{$key};
        }
        $self->$method($val);
    }

    if ( $self->database && $self->user ) {
        $self->connect;
    }

    return 1;
}

sub logger {
    return shift->{oLogger};
}

sub driver {
    my $self = shift;
    $self->{driver} = shift if @_;
    return $self->{driver};
}

sub engine {
    my $self = shift;
    $self->{engine} = shift if @_;
    return $self->{engine};
}

sub charset {
    my $self = shift;
    $self->{charset} = shift if @_;
    return $self->{charset};
}

sub host {
    my $self = shift;
    $self->{host} = shift if @_;
    return $self->{host};
}

sub port {
    my $self = shift;
    $self->{port} = shift if @_;
    return $self->{port};
}

sub database {
    my $self = shift;
    $self->{database} = shift if @_;
    return $self->{database};
}

sub user {
    my $self = shift;
    $self->{user} = shift if @_;
    return $self->{user};
}

sub pass {
    my $self = shift;
    $self->{pass} = shift if @_;
    return $self->{pass};
}

sub raiseError {
    my ($self, $value) = @_;

    $self->{raise_error} = $value if defined $value;
    return $self->{raise_error};
}

sub connectRetries {
    my ($self, $value) = @_;

    if ( $value && $value =~ /^[1-9]+$/ ) {
        $self->{connect_retries} = $value;
    }

    return $self->{connect_retries};
}

sub connectTimeout {
    my ($self, $value) = @_;

    if ( $value && $value =~ /^[1-9]+$/ ) {
        $self->{connect_timeout} = $value;
    }

    return $self->{connect_timeout};
}

sub checkConnection {
    my $self = shift;
    $self->{check_connection} = shift if @_;
    return $self->{check_connection};
}

sub dbh {
    return shift->{_dbh};
}

sub tableName {
    return shift->{table_name};
}

sub connect {
    my $self = shift;

    return $self->dbh if $self->dbh;
    my $retries = $self->connectRetries();

    my $dsn = "DBI:$self->{driver}:database=$self->{database};host=$self->{host};port=$self->{port}";
    while ($retries--) {
        eval {
            $self->logger->debug("Connecting to DB: $self->{database} HOST: $self->{host} PORT: $self->{port}");
            $self->{_dbh} = DBI->connect(
                $dsn,
                $self->user,
                $self->pass,
                {
                    RaiseError => $self->raiseError,
                    PrintError => $self->raiseError,
                },
            );
        };
        last unless $@;
        sleep $self->connectTimeout();
    }

    my $error = "Can't connect to database: $dsn;user=$self->{user}";
    # either production mode or development mode with a predefined database host
    if ( my $e = $@ ) {
        $self->logger->logdie($e);
    }
    # non-production mode, checking localhost database
    elsif ( !$self->raiseError && !$self->dbh ) {
        $self->logger->info($error) && die;
    }

    if ( $self->dbh && $self->driver =~ /mysql/i ) {
        $self->dbh->{AutoCommit} = 1;
        $self->dbh->{mysql_auto_reconnect} = 1;
        $self->dbh->do("SET NAMES '$self->{charset}'");

        if ($self->dbh->{mysql_serverversion} && $self->dbh->{mysql_serverversion} >= 50700) {
            $self->dbh->do("SET DEFAULT_STORAGE_ENGINE='$self->{'engine'}'");
        }
        else {
            $self->dbh->do("SET STORAGE_ENGINE='$self->{engine}'");
        }
    }

    return $self->dbh;
}

sub disconnect {
    my $self = shift;

    $self->logger->debug("Disconnect from DB.");
    if ( $self->dbh && $self->dbh->disconnect() ) {
        $self->_init({});
        undef $self->{_dbh};
        return 1;
    }

    return 0;
}

sub isConnected {
    my $self = shift;

    my $status = 0;
    return $status unless $self->dbh;

    my $sth = $self->dbh->prepare('SELECT 1');
    $sth->execute();
    $status = $sth->fetchrow_array() || 0;
    $sth->finish();

    return $status;
}

sub select {
    my ($self, $query, @params) = @_;

    $self->logger->debug( "Execute select: $query Params: ", join(', ', @params) );

    my @results;
    eval {
        my $sth = $self->dbh->prepare($query);
        $sth->execute(@params);
        while (my $hRow = $sth->fetchrow_hashref()) {
            push @results, $hRow;
        }
        $sth->finish();
    };

    if ( my $e = $@ ) {
        $self->logger->logdie($e);
    }

    return wantarray ? @results : \@results;
}

sub select_col_array {
    my ($self, $query, @params) = @_;

    $self->logger->debug( "Execute select: \n$query Params: ", join(', ', @params) );

    my $aResult;
    eval {
        if (@params) {
            $aResult = $self->dbh->selectcol_arrayref($query, { Columns => [1] }, @params);
        } else {
            $aResult = $self->dbh->selectcol_arrayref($query, { Columns => [1] });
        }
    };

    if ( my $e = $@ ) {
        $self->logger->logdie($e);
    }

    return wantarray ? @$aResult : $aResult;
}

sub query {
    my ($self, $query, @params) = @_;

    $self->logger->debug( "Execute query: \n$query Params: ", join(', ', @params) );

    eval {
        if (@params) {
            my $sth = $self->dbh->prepare($query);
            $sth->execute(@params);
            $sth->finish();
        } else {
            $self->dbh->do($query);
        }
    };

    if ( my $e = $@ ) {
        $self->logger->logdie($e);
    }

    return 1;
}

sub getLocalDatabases {
    my $self = shift;

    my $aResult = $self->select_col_array('SHOW DATABASES');
    return map { $_ => undef } @$aResult;
}

sub databaseCatalog {
    my $self = shift;

    foreach my $prefix ( keys %{ +DB_CATALOG } ) {
        return DB_CATALOG->{$prefix} if $self->database =~ /^$prefix/i;
    }

    return;
}

sub getLock {
    my ($self, $name, $timeout) = @_;

    $timeout ||= LOCK_TIMEOUT;

    my ($hResult) = $self->select('
        SELECT GET_LOCK(?, ?)',
        $name,
        $timeout
    );

    my ($lock) = values %$hResult;

    return $lock;
}

sub releaseLock {
    my ($self, $name) = @_;

    $self->query('
        SELECT RELEASE_LOCK(?)',
        $name
    );
}

sub DESTROY {
    my $self = shift;
}


1;