# Copyright 2008-2014 Cumulus Systems Incorporated.
# All Rights Reserved.

# This module provide functions for logging messages from a Perl script. We are using custom mechanism for generating log files. Here log file will
# be created in "<base log folder>/<probe type>Probe" folder. Log file name will be: <probe_type>Probe.log like netappProbe.log. Once the size of
# current log file reaches 10 MB, we rename it to <probe_type>Probe.log.<n> and create new <probe_type>Probe.log file. There will be maximum of 6
# files exist at a time.

# Name of the Package.
package commonDefaultLogModule;

# It is used for strict compilation.
use strict;

# It helps in debugging.
use warnings;

# This is used to create folder recursively.
use File::Path qw(mkpath);

# These are used to get the current date and time.
use Time::HiRes qw(gettimeofday);
use POSIX "strftime";

# It is used to get base file name from the complete file name.
use File::Basename;

# It is used to store maximum size of a log file in bytes.
my $MAX_LOG_FILE_SIZE = 1024000000;

# This stores an instance of commDefaultLogModule class.
my $commDefaultLogModule = undef;

# --------------------------------------------------------------------------------------------------------------------------------------------------
# Sub-routines
# --------------------------------------------------------------------------------------------------------------------------------------------------
# Constructor of class commonDefaultLogModule.
#
# @param :
#   $_[0] - [In] Class name.
#   $_[1] - [In] Base log folder.
#   $_[2] - [In] Probe Type like NetApp.
#   $_[3] - [In] Probe Id "192.168.20.100".
#
# @affected global variables :
#   None
#
# @return :
#   Instance of commonDefaultLogModule class.
sub getInstance {
    # This variable stores package name.
    my $class = $_[0];

    # This variable stores base folder in which we generate logs.
    my $baseFolder = $_[1];

    # This variable stores probe type.
    my $probeType = $_[2];

    # This variable stores probe id.
    my $probeId = $_[3];

    # This variable stores final message Information.
    my $finalMessageInfo = "[".$probeType."-".$probeId."]";

    # This variable stores log file information.
    my $logFile = getLogFileName($baseFolder, $probeType);

    # Create class instance.
    $commDefaultLogModule = bless {
        probeType => $probeType,
        logInfo => $finalMessageInfo,
        logFile => $logFile,
        fileCount => 1,
    }, shift unless $commDefaultLogModule;

    return $commDefaultLogModule;
}

# When the constructor of commonDefaultLogModule get invoked, it is not necessary that the value of $probeId is available at that time. So in that case,
# caller can invoke this method to update object's "logInfo" value so that proper log message can be formed.
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Probe Id.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub setProbeId {
    # Get Probe Type.
    my $probeType = $_[0]->{probeType};

    # Update the final message information.
    my $finalMessageInfo = "[".$probeType."-".$_[1]."]";

    # Update object member variable "logInfo".
    $_[0]->{logInfo} = $finalMessageInfo;
}

# --------------------------------------------------------------------------------------------------------------------------------------------------
# Sub-routines
# --------------------------------------------------------------------------------------------------------------------------------------------------
# This function is used to log a debug message in the log file. It logs the message in the following format:
# [YYYY/MM/DD HH:MM:ss] [<ProbeType>-<ProbeId>] [<Log Source>:<Line Number>] <Debug> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub debug {
    # This variable stores the file name from where the log is coming.
    my $logSource = basename((caller(0))[1]);

    # This variable stores the line number in the file from where the log is coming.
    my $lineNumber = (caller(0))[2];

    # Create the final log message.
    my $logMessage = $_[0]->{logInfo}." [$logSource:$lineNumber] [Debug] ".$_[1];

    # Delegate function call to core log function.
    $_[0]->logMsg($logMessage);
}

# This function is used to log an info message in the log file. It logs the message in the following format:
# [YYYY/MM/DD HH:MM:ss] [<ProbeType>-<ProbeId>] [<Log Source>:<Line Number>] <Info> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub info {
    # This variable stores the file name from where the log is coming.
    my $logSource = basename((caller(0))[1]);

    # This variable stores the line number in the file from where the log is coming.
    my $lineNumber = (caller(0))[2];

    # Create the final log message.
    my $logMessage = $_[0]->{logInfo}." [$logSource:$lineNumber] [Info] ".$_[1];

    # Delegate function call to core log function.
    $_[0]->logMsg($logMessage);
}

# This function is used to log a warning message in the log file. It logs the message in the following format:
# [YYYY/MM/DD HH:MM:ss] [<ProbeType>-<ProbeId>] [<Log Source>:<Line Number>] <Warn> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub warn {
    # This variable stores the file name from where the log is coming.
    my $logSource = basename((caller(0))[1]);

    # This variable stores the line number in the file from where the log is coming.
    my $lineNumber = (caller(0))[2];

    # Create the final log message.
    my $logMessage = $_[0]->{logInfo}." [$logSource:$lineNumber] [Warn] ".$_[1];

    # Delegate function call to core log function.
    $_[0]->logMsg($logMessage);
}

# This function is used to log an error message in the log file. It logs the message in the following format:
# [YYYY/MM/DD HH:MM:ss] [<ProbeType>-<ProbeId>] [<Log Source>:<Line Number>] <Error> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub error {
    # This variable stores the file name from where the log is coming.
    my $logSource = basename((caller(0))[1]);

    # This variable stores the line number in the file from where the log is coming.
    my $lineNumber = (caller(0))[2];

   # Create the final log message.
    my $logMessage = $_[0]->{logInfo}." [$logSource:$lineNumber] [Error] ".$_[1];

    # Delegate function call to core log function.
    $_[0]->logMsg($logMessage);
}

# This function is used to log a fatal message in the log file. It logs the message in the following format:
# [YYYY-MM-DD HH:MM:ss,SSS] [<ProbeType>-<ProbeId>] [<Log Source>:<Line Number>] <Fatal> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub fatal {
    # This variable stores the file name from where the log is coming.
    my $logSource = basename((caller(0))[1]);

    # This variable stores the line number in the file from where the log is coming.
    my $lineNumber = (caller(0))[2];

    # Create the final log message.
    my $logMessage = $_[0]->{logInfo}." [$logSource:$lineNumber] [Fatal] ".$_[1];

    # Delegate function call to core log function.
    $_[0]->logMsg($logMessage);
}

# This function is used to log a message in the log file. It logs the message in the following format:
# [YYYY/MM/DD HH:MM:ss] [<ProbeType>-<ProbeId>] [<Log Source>] <Log Type> <Log message>
#
# @param :
#   $_[0] - [In] Object Reference.
#   $_[1] - [In] Log Message.
#
# @affected global variables :
#   None
#
# @return :
#   Nothing
sub logMsg {
    # Get the name of the log file.
    my $logFile = $_[0]->{logFile};

    # Get the log message to be dumped in the log file.
    my $logMessage = $_[1];

    # Get the current date and time.
    my ($seconds, $microseconds) = gettimeofday;
    my $currentTime = strftime("%Y/%m/%d %H:%M:%S", localtime($seconds));

    if (-e $logFile) {
        # Get size of the log file.
        my $fileSize = -s $logFile;

        # If size of the log file exceeds 10 MB then rename the original and create a new file.
        if ($fileSize > $MAX_LOG_FILE_SIZE) {
            my $fileCount = $_[0]->{fileCount};

            # If file count reaches 6 then delete the oldest log file that is $logfile.5.
            if (6 == $fileCount) {
                # Delete logFile.5
                unlink($logFile.".".5);

                $fileCount = $fileCount - 1;
            }

            # Rename the other files that is if log file name is $logfile.4, it will now be renamed to $logfile.5 and so on.
            my $newFileNumber = $fileCount;
            while ($newFileNumber > 1) {
                my $currentFileNumber = $newFileNumber - 1;

                rename($logFile.".".$currentFileNumber, $logFile.".".$newFileNumber);
                $newFileNumber--;
            }

            # Rename current main log file to logFile.1.
            rename($logFile, $logFile.".".1);

            # Update fileCount object member variable.
            $_[0]->{fileCount} = $fileCount + 1;
        }
    }

    # Open the log file.
    open(my $LOG_HANDLE, ">>", $_[0]->{logFile}) or die "Not able to open the file: [$logFile].";

    # Construct the final log message.
    my $finalMsg = "[".$currentTime."]".$logMessage."\n";

    # Dump the final log message in log file.
    print $LOG_HANDLE $finalMsg;

    close($LOG_HANDLE);
}

# This function is used to return the log file name.
#
# @param :
#   None
#
# @affected global variables :
#   None
#
# @return :
#   Log File Name.
sub getLogFileName {
    my $baseFolder = $_[0];

    my $probeType = lc($_[1]);

    # Set Probe log folder path.
    my $logFolder = $baseFolder."/".$probeType."Probe";

    # If log folder does not exist then create it.
    if (! (-e $logFolder))
    {
        mkpath($logFolder);
    }

    # Set the name of log file.
    my $logFile = $logFolder."/".$probeType."Probe.log";

    return $logFile;
}

# The file must return true ("1") as the last statement to indicate successful execution of any initialization code. So it's customary to end such a
# file with 1 unless we are sure that we will return true otherwise. But it's better just to put the 1;, in case we add more statements.
1;