#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
package Common::DB::LogWhenChangedItem;

use strict;
use warnings;
use Carp;
use Data::Dumper;
use lib '/app/tools/common/lib';
use Common::Util 'secondsToDateTime';
use Common::DB::AutoLock;
use Common::RSDB;
use Common::RSApp;
use Common::Util qw(escape_mysql_regexp escape_mysql_like);
use Common::Assert;
use Common::Log;
use Common::DB::Item;
use Common::DB::Item::TableChangeLog;
use base 'Common::DB::Item';

use Data::Dumper;

# Abstract the action of creating log entries.  This way we can change the log package,
# add in additional arguments, etc.
#
sub _createChangeLogEntry {
    my ( $self, %args ) = @_;

    # This version we record the client_id.
    #
    $args{client_id} = Common::RSApp::GetClientID();

    my $logEntry = Common::DB::Item::TableChangeLog->Create(%args);
    $logEntry->save();
}

sub _doDelete {
    my ( $self, $dbo, $tableName, $where, $values ) = @_;

    $self->SUPER::_doDelete( $dbo, $tableName, $where, $values );

    if ( $self->_logChanges() ) {
        my @logWhere;

        my $item;
        foreach $item (@$where) {
            my $value = $dbo->DBQuote( shift @$values );
            $item =~ s/\?/$value/;
            push @logWhere, $item;
        }

        my $whereString = join( ',', @logWhere );

        # There is an extremely slight chance that this write might fail.
        # The log table is on another host...
        # If that occurs, then we need to fall back and at least write this
        # out to our log file so we have some sort of hope of recovering later.
        #
        eval {
            $self->_createChangeLogEntry(
                table_name  => $tableName,
                action      => Common::DB::Item::TableChangeLog::kActionDelete,
                where_pairs => $whereString
            );
        };

        if ($@) {

            # Log the update, then re-throw the exception.
            #
            my $serializedLogMessage = "|$tableName|" . Common::DB::Item::TableChangeLog::kActionDelete . "||$whereString|";

            Common::Log::Print("Common::DB::LogWhenChangedItem FAILURE:$serializedLogMessage");

            die($@);
        }
    }
}

sub _doInsert {
    my ( $self, $dbo, $tableName, $set, $values, $autoField ) = @_;

    $self->SUPER::_doInsert( $dbo, $tableName, $set, $values, $autoField );

    if ( $self->_logChanges() ) {
        my @logSet;
        my $item;
        foreach $item (@$set) {

            # !!! Need to make this regex more explicit.
            # like, search for '=?' and the like.
            if (   $item =~ /=\?$/
                || $item =~ /\(\?\)$/ ) {
                my $value = $dbo->DBQuote( shift @$values );
                $item =~ s/\?/$value/;
            } elsif ( $item =~ /=CURRENT_TIMESTAMP/ ) {

                # We want to replace 'CURRENT_TIMESTAMP' with the actual current timestamp.
                # This way the value we log will be reasonably close to the actual value.
                #
                my $currentTime = $dbo->DBQuote( secondsToDateTime(time) );
                $item =~ s/CURRENT_TIMESTAMP/$currentTime/;
            }
            push @logSet, $item;
        }

        # If there is an autoincrement key, we're going to need to add that to the logged data.
        #
        if ( defined $autoField ) {
            push @logSet, "$autoField=" . $self->$autoField;
        }

        my $setString = join( ',', @logSet );

        eval {
            $self->_createChangeLogEntry(
                table_name => $tableName,
                action     => Common::DB::Item::TableChangeLog::kActionInsert,
                set_pairs  => $setString,
            );
        };
        if ($@) {

            # Log the update, then re-throw the exception.
            #
            my $serializedLogMessage = "|$tableName|" . Common::DB::Item::TableChangeLog::kActionInsert . "|$setString||";

            Common::Log::Print("Common::DB::LogWhenChangedItem FAILURE:$serializedLogMessage");

            die($@);
        }

    }
}

sub _doUpdate {
    my ( $self, $dbo, $tableName, $set, $where, $values ) = @_;

    $self->SUPER::_doUpdate( $dbo, $tableName, $set, $where, $values );

    # !!! Don't like this string-based approach.
    # !!! We'll just have to re-parse it downstream, which could
    # !!! introduce bugs.
    # !!! But, if I use two tables, then updating the log is no longer
    # !!! an atomic operation, which will lead to race conditions.
    # !!! Unless I lock the tables, which is pretty drastic.
    # !!! So, I guess we'll stick with text fields.

    if ( $self->_logChanges() ) {
        my @logSet;
        my @logWhere;

        my $item;
        foreach $item (@$set) {
            if (   $item =~ /=\?$/
                || $item =~ /\(\?\)$/ ) {
                my $value = $dbo->DBQuote( shift @$values );
                $item =~ s/\?/$value/;
            } elsif ( $item =~ /=CURRENT_TIMESTAMP/ ) {

                # We want to replace 'CURRENT_TIMESTAMP' with the actual current timestamp.
                # This way the value we log will be reasonably close to the actual value.
                #
                my $currentTime = $dbo->DBQuote( secondsToDateTime(time) );
                $item =~ s/CURRENT_TIMESTAMP/$currentTime/;
            }
            push @logSet, $item;
        }

        foreach $item (@$where) {
            if (   $item =~ /=\?$/
                || $item =~ /\(\?\)$/ ) {
                my $value = $dbo->DBQuote( shift @$values );
                $item =~ s/\?/$value/;
            } elsif ( $item =~ /=CURRENT_TIMESTAMP/ ) {

                # We want to replace 'CURRENT_TIMESTAMP' with the actual current timestamp.
                # This way the value we log will be reasonably close to the actual value.
                #
                my $currentTime = $dbo->DBQuote( secondsToDateTime(time) );
                $item =~ s/CURRENT_TIMESTAMP/$currentTime/;
            }
            push @logWhere, $item;
        }

        my $setString   = join( ',', @logSet );
        my $whereString = join( ',', @logWhere );

        eval {
            $self->_createChangeLogEntry(
                table_name  => $tableName,
                action      => Common::DB::Item::TableChangeLog::kActionUpdate,
                set_pairs   => $setString,
                where_pairs => $whereString
            );
        };
        if ($@) {

            # Log the update, then re-throw the exception.
            #
            my $serializedLogMessage = "|$tableName|" . Common::DB::Item::TableChangeLog::kActionUpdate . "|$setString|$whereString|";

            Common::Log::Print("Common::DB::LogWhenChangedItem FAILURE:$serializedLogMessage");

            die($@);
        }
    }
}

sub _logChanges {
    my ($self) = @_;

    # !!! Add a client_id/client_type check in here
    #
    my $clientID = Common::RSApp::GetClientID();
    return 0 if 112 == $clientID;
    return 1;
}

1;
