package LISM::Storage::Queue;

use strict;
use base qw(LISM::Storage);
use Net::LDAP::Constant qw(:all);
use DBI;
use Encode;
use Time::HiRes qw(gettimeofday);
use Data::Dumper;

=head1 NAME

LISM::Storage::Queue - Queue storage for LISM

=head1 DESCRIPTION

This class implements the L<LISM::Storage> interface for Queue.

=head1 METHODS

=head2 init

Initialize queue.

=cut

sub init
{
    my $self = shift;

    return $self->SUPER::init();
}

sub _getConnect
{
    my $self = shift;

    return $self->_connect(\$self->{db});
}

sub _connect
{
    my $self = shift;
    my ($db) = @_;
    my $conf = $self->{_config};

    if (defined(${$db}) && (!defined($conf->{connection}[0]->{type}) || $conf->{connection}[0]->{type}[0] ne 'every')) {
        my $sth = ${$db}->prepare("select 'check' from $conf->{connection}[0]->{table}");
        if (!$sth || !$sth->execute) {
            $self->log(level => 'err', message => "Connection check($conf->{dsn}[0]) failed: ".${$db}->errstr);
            ${$db}->disconnect();
            undef(${$db});
        } else {
            return 0;
        }
        if ($sth) {
            $sth->finish;
        }
    }

    ${$db} = DBI->connect($conf->{dsn}[0], $conf->{admin}[0], $conf->{passwd}[0]);
    if (!${$db}) {
        $self->log(level => 'alert', message => "Can't connect $conf->{dsn}[0]: ".$DBI::errstr);
        return -1;
    }

    return 0;
}

sub _freeConnect
{
    my $self = shift;
    my $conf = $self->{_config};

    if ($self->{db}->err || (defined($conf->{connection}[0]->{type}) && $conf->{connection}[0]->{type}[0] eq 'every')) {
        $self->{db}->disconnect();

        undef($self->{db});
    }
}

sub _checkConfig
{
    my $self = shift;
    my $conf = $self->{_config};
    my $rc = 0;

    $rc = $self->SUPER::_checkConfig();
    if ($rc) {
        return $rc;
    }

    if (!defined($conf->{dsn}) || !defined($conf->{admin}) || !defined($conf->{passwd})) {
        $self->log(level => 'alert', message => "dsn, admin, passwd must be in db");
        return 1;
    }

    if (!defined($conf->{connection}[0]->{table})) {
        $conf->{connection}[0]->{table} = $conf->{table}[0];
    }

    return 0;
}

=pod

=head2 search($base, $scope, $deref, $sizeLim, $timeLim, $filterStr, $attrOnly, @attrs)

Always return LDAP_SUCCESS.

=cut

sub search
{
    return LDAP_SUCCESS;
}

=pod

=head2 modify($dn, @list)

Push modify operation to queue.

=cut

sub modify
{
    my $self = shift;
    my ($dn, @list) = @_;
    my $conf = $self->{_config};

    my $ldif = "dn: $dn\nchangetype: modify\n";
    while (@list > 0) {
        my $action = shift @list;
        my $attr = shift @list;
        my @values;

        while (@list > 0 && $list[0] ne "ADD" && $list[0] ne "DELETE" && $list[0] ne "REPLACE") {
            push(@values, shift @list);
        }

        $ldif = $ldif.lc($action).": $attr\n";
        foreach my $val (@values) {
            $ldif = "$ldif$attr: $val\n";
        }
        $ldif = "$ldif-\n";
    }

    return $self->_pushTask($dn, $ldif);
}

=pod

=head2 add($dn, $entryStr)

Push add operation to queue.

=cut

sub add
{
    my $self = shift;
    my ($dn,  $entryStr) = @_;
    my $conf = $self->{_config};

    my $ldif = "dn: $dn\nchangetype: add\n$entryStr";

    return $self->_pushTask($dn, $ldif);
}

=pod

=head2 modrdn($dn, $newrdn, $delFlag)

Push modrdn operation to queue.

=cut

sub modrdn
{
    my $self = shift;
    my ($dn, $newrdn, $delFlag) = @_;
    my $conf = $self->{_config};

    my $ldif = "dn: $dn\nchangetype: modrdn\nnewrdn: $newrdn\ndeleteoldrdn: $delFlag\n";

    return $self->_pushTask($dn, $ldif);
}

=pod

=head2 delete($dn)

Push delete operation to queue.

=cut

sub delete
{
    my $self = shift;
    my ($dn, $entryStr) = @_;
    my $conf = $self->{_config};

    my $ldif = "dn: $dn\nchangetype: delete\n";

    return $self->_pushTask($dn, $ldif);
}

sub _pushTask
{
    my $self = shift;
    my ($dn, $ldif) = @_;
    my $conf = $self->{_config};
    my $lism = $self->{lism};
    my $rc = LDAP_SUCCESS;

    my $retry = 0;
    while (1) {
        if (!$self->_getConnect()) {
            last;
        }

        if ($retry >= $conf->{connection}[0]->{retry}[0]) {
            return LDAP_SERVER_DOWN;
        }
        sleep $conf->{connection}[0]->{interval}[0];
        $retry++;
    }

    my ($sec, $microsec) = gettimeofday();
    my $timestamp = "$sec"."$microsec";
    my $tenant;
    if (defined($conf->{checktenant})) {
        my $checktenant = $conf->{checktenant}[0];
        ($tenant) = ($dn =~ /$checktenant/i);
    }
    my $binddn = defined($lism->{bind}{edn}) ? $lism->{bind}{edn} : $lism->{bind}{dn};
    $ldif = encode('utf8', $ldif);

    my $sql = "insert into $conf->{table}[0](timestamp, tenant, binddn, ldif) values(?, ?, ?, ?)";
    my $sth = $self->{db}->prepare($sql);
    if (!$sth) {
        $rc = LDAP_OTHER;
    } elsif (!$sth->execute($timestamp, $tenant, $binddn, $ldif)) {
        $rc = LDAP_OTHER;
    }

    $self->_freeConnect();

    return $rc;
}

=head1 SEE ALSO

L<LISM>,
L<LISM::Storage>

=head1 AUTHOR

Kaoru Sekiguchi, <sekiguchi.kaoru@secioss.co.jp>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2015 by Kaoru Sekiguchi

=cut

1;
