package Common::Log;
use strict;

use lib '/app/tools/common/lib';
use Common::Assert;

use Carp;
use Apache2::Const -compile => qw(:log);
use APR::Const -compile     => qw(ENOTIME SUCCESS);

use IO::File;
use Data::Dumper;

use constant kDefaultLogLevel => 'warn';

###############################################################################
#
#  Common::Log currently has two usages, legacy and new.  The legacy method
#  provided simple static calls to print messages to STDERR.  The debug method
#  check the systems environment to determine if a message should be printed
#  or not.  However, this system only had two log levels (print or don't print)
#  and would only send log messages to STDERR.
#
#  The new implementation stores the log level and output mechanism in a
#  singleton object.  It supports all the same log levels as Apache and can
#  output messages to apache's log mechanism, a file, or STDERR.
#
###############################################################################
#
# New Logger
#
#  Usage:
#
#  The logger can be invoked using calls to log methods which miror Apache's log
#  levels.
#
#     Log->emerg( "message" )
#     Log->alert( "message" )
#     Log->crit( "message" )
#     Log->error( "message" )
#     Log->warn( "message" )
#     Log->notice( "message" )
#     Log->info( "message" )
#     Log->debug( "message" )
#
# Initialization:
#
# By default debug messages are sent to STDERR and the default log level is
# defined in the constant Common::Log::kDefaultLogLevel.  These can be overloaded.
# using a few log methods.
#
# sub init( %args ) - initialize the log object.
#   ARGS:
#       apache - the current ApacheRec object.
#       file   - file to output log messages too.
#       level  - explicitly set the log level.
#
# sub increaseLogLevel( int ) - increase the current log level by the amount specified
#   - int defaults to 1.
#
###############################################################################
#
# Legacy
#
# Static Methods:
#
#   Common::Log::Print( "message" );
#   Common::Log::Debug( "message" );
#   Common::Log::DebugMem();
#
###############################################################################

# Standardized log printing routine.
#
sub Print {
    print STDERR localtime() . " [$$, " . getppid() . "] ";
    map {
        print STDERR ref($_) ? Dumper($_) : $_;
        print STDERR "\n";
    } @_;
}

#
# Debug
#
# Provides a simple interface to emit debugging messages.
# The advantage to using this over a simple 'print STDERR' is that this
# code checks an environmental variable...

sub Debug {

    # Each httpd can service multiple vhosts, each with a different CLIENT_ID.
    # So our convention is to set the DEBUG environmental variable to the CLIENT_ID
    # in order to activate debugging for a given client.
    #
    if ( $ENV{DEV_SERVER} || ( $ENV{DEBUG} && $ENV{CLIENT_ID} && $ENV{DEBUG} == $ENV{CLIENT_ID} ) ) {
        Print(@_);
    }
}

# This version of 'Debug' will grab the current contents of /proc/$$/statm, which contains
# a snapshot of the current memory in use by the process.
# This does add a slight amount of overhead
sub DebugMem {
    if ( $ENV{DEV_SERVER} || ( $ENV{DEBUG} && $ENV{CLIENT_ID} && $ENV{DEBUG} == $ENV{CLIENT_ID} ) ) {
        if ( open STATM, "/proc/self/statm" ) {
            my $statm = <STATM>;
            close STATM;
            chomp $statm;
            substr( $_[0], 0, 0 ) = "<$statm>";
        }
        Print(@_);
    }
}

# Common::Log Singleton

{
    my $LOGGER;

    my %log_levels = (
        emerg  => Apache2::Const::LOG_EMERG,
        alert  => Apache2::Const::LOG_ALERT,
        crit   => Apache2::Const::LOG_CRIT,
        error  => Apache2::Const::LOG_ERR,
        warn   => Apache2::Const::LOG_WARNING,
        notice => Apache2::Const::LOG_NOTICE,
        info   => Apache2::Const::LOG_INFO,
        debug  => Apache2::Const::LOG_DEBUG
    );

    sub new {
        my $class = shift || carp("No object type passed");
        my %args  = @_;
        my $self  = bless {}, $class;

        $self->{_logLevel} = kDefaultLogLevel unless ( $self->{_logLevel} );

        # Grab the apache server reference
        $self->{_apache} = $args{'apache'};

        # Attempt to open the apache log
        $self->openApacheLog();

        # Open the use log file if specified.
        $self->setLogFile( $args{'file'} );

        # Set the user defined log level
        if ( defined( $args{'level'} ) ) {
            $self->setLogLevel( $args{'level'} );
        }

        return $self;
    }

    sub init {
        my $self = shift;
        my %args = @_;

        $self->LOGGER->{_apache} = $args{apache};

        $self->LOGGER->openApacheLog()             if ( $args{apache} );
        $self->LOGGER->setLogFile( $args{file} )   if ( $args{file} );
        $self->LOGGER->setLogLevel( $args{level} ) if ( $args{level} );
    }

    sub LOGGER {
        my $self = shift;
        return $LOGGER if ($LOGGER);

        $LOGGER = new Common::Log();
        return $LOGGER;
    }

    sub setLogFile {
        my $self = shift || croak("No reference passed");
        my $logfile = shift;

        $self->{_logfile} = $logfile;

        if ($logfile) {
            $self->{_apache}    = undef;
            $self->{_apacheLog} = undef;
        }

        $self->openLogFile();
    }

    sub setLogLevel {
        my $self = shift || croak("No reference passed");
        my $loglevel = shift;

        if ( defined($loglevel) ) {
            if ( $self->isValidLogLevel($loglevel) ) {
                $self->LOGGER->{_logLevel} = $loglevel;
            } else {
                carp("Invalid loglevel: $loglevel, setting to default\n");
            }
        }
    }

    sub increaseLogLevel {
        my $self   = shift || croak("No reference passed");
        my $amount = shift || 1;

        my $current      = $self->LOGGER->{_logLevel} || kDefaultLogLevel;
        my $currentIndex = $log_levels{$current};
        my $maxIndex     = $log_levels{debug};
        my $minIndex     = $log_levels{emerg};

        $currentIndex += $amount;
        $currentIndex = $maxIndex if ( $currentIndex > $maxIndex );
        $currentIndex = $minIndex if ( $currentIndex < $minIndex );

        my $level;

        foreach my $l ( keys %log_levels ) {
            if ( $log_levels{$l} == $currentIndex ) {
                $level = $l;
                last;
            }
        }

        die "No level" unless ($level);

        $self->LOGGER->{_logLevel} = $level;
    }

    sub openLogFile {
        my $self = shift || croak("No reference passed");

        if ( defined( $self->{_logfile} ) && $self->{_logfile} ne "/dev/stderr" ) {
            $self->{_logfileHandle} = new IO::File(">>$self->{_logfile}") || die "Failed: $!";
        } else {
            $self->{_logfileHandle} = \*STDERR;
        }
    }

    sub openApacheLog {
        my $self = shift || croak("No reference passed");

        return undef unless ( defined( $self->{_apache} ) );

        $self->{_logfileHandle} = undef;
        $self->{_logfile}       = undef;

        my $r = $self->{_apache};
        $self->{_apacheLog} = $r->log() if ( defined($r) );
    }

    sub writeLog {
        my ( $self, $level, @message ) = @_;
        assert($level);

        #       my $self = shift;
        #       my $level = shift || die;
        #       my $arg = shift || die;

        #       my @message = split( "\n", $arg );

        my $fh = $self->LOGGER->{_logfileHandle} || \*STDERR;

        my $current_log_level = $self->LOGGER->{_logLevel};

        foreach (@message) {
            if ( defined( $self->LOGGER->{_apache} ) ) {
                my ( $package, $filename, $line ) = caller(1);
                my $r = $self->LOGGER->{_apache};

                unless ( $log_levels{$level} ) {
                    $level = 'debug';
                    $self->LOGGER->error("Loglevel: '$level' is not valid'");
                }

                $r->log_rerror( $package, $line, $log_levels{$level}, APR::Const::SUCCESS, $_ )
                  if ( $log_levels{$level} );
            }

            if ( defined($fh) ) {
                if ( $log_levels{$level} <= $log_levels{$current_log_level} ) {
                    print $fh localtime() . " [$$, " . getppid() . "] ($level) " . ( ref($_) ? Dumper($_) : $_ ) . "\n";
                }
            }
        }
    }

    sub emerg  { shift->writeLog( "emerg",  @_ ); }
    sub alert  { shift->writeLog( "alert",  @_ ); }
    sub crit   { shift->writeLog( "crit",   @_ ); }
    sub error  { shift->writeLog( "error",  @_ ); }
    sub warn   { shift->writeLog( "warn",   @_ ); }
    sub notice { shift->writeLog( "notice", @_ ); }
    sub info   { shift->writeLog( "info",   @_ ); }
    sub debug  { shift->writeLog( "debug",  @_ ); }

    sub isValidLogLevel {
        my $self      = shift || croak("No reference passed");
        my $log_level = shift || return;

        my @loglevels = keys(%log_levels);

        return ( "@loglevels" =~ /\b$log_level\b/ );
    }
}

package Log;
use base qw( Common::Log );

1;
###
1;    # Play nicely.
###
