package Guardian::Base;
use strict;
use warnings;

use Exporter qw(import);

our @EXPORT_OK = qw(GenerateMonitoredFiles GetFileposition DetectIPProtocolVersion FilePositions);

use Net::IP;

#
## Function to generate a hash of monitored files and their file positions.
#
## This function is responsible for creating the hash of which files should be
## monitored by guardian. In order to do this, all options from the given hash of
## main settings will be parsed and all files to monitor and their configured parsers
## get extracted, validated and stored into a temporary hash.
#
## Next step will be to cleanup files which have been monitored in the past but have been
## requested for beeing unmonitored for now. To do this, a check if the the file name is
## part of the existing hash of monitored files and if true to transfer the data into the
## new temporary hash which get returned by the function.
#
sub GenerateMonitoredFiles (\%\%) {
	# Dereference the given hash-refs and store
	# them into a new temporary hashes.
	my %mainsettings = %{ $_[0] };
	my %current_monitored_files = %{ $_[1] };

	# Private hash for storing the new monitored files.
	my %new_monitored_files = ();

	# Loop through the temporary hash which contains the main settings.
	# Search for files which should be monitored and extract the requested
	# parser. Compare if the file already was a part of the hash which contains
	# the monitored files and add them to the private new hash of monitored
	# files which will be returned.
        foreach my $config_option (keys %mainsettings) {
                # Skip option if it does not look like "Monitor_XYZ".
                next unless($config_option =~ m/^Monitor_/);

		# Splitt monitor instruction into 2 parts, to grab the
		# requested parser module.
		my ($start, $parser) = split (/_/, $config_option);

		# Convert parser name into lower case format.
		# Internally the parser module name is completely handled
		# in this way. This also prevents from any problems related
		# how the parser name has been spelled in the config file.
		$parser = lc($parser);

		# Check if the configured parser is available and valid.
		next unless(&Guardian::Parser::IsSupportedParser($parser));

                # Get the configured file for this option.
                my $file = $mainsettings{$config_option};

                # Skip the file, if it does not exist or is not read-able.
                next unless(-r "$file");

                # Check if the file not yet has been added to the hash
                # of monitored files.
                unless(exists($current_monitored_files{$file})) {
                        # Add the file, init and store the fileposition.
                        $new_monitored_files{$file} = $parser;
		} else {
			# Copy file and parser information to the new hash.
			$new_monitored_files{$file} = $current_monitored_files{$file};
		}
        }

	# Return the new_monitored_files hash.
	return %new_monitored_files;
}

#
## The FilePositions function.
#
## This function is responsible for creating and/or updating the hash which
## stores the current cursor position of the end of file (EOF) of all
## monitored files.
#
## The function requires the hash of currently monitored files and the old hash
## of the current file positions in order to work properly.
#
sub FilePositions (\%\%) {
	# Dereference the given hash-refs and store
	# them into a new temporary hashes.
	my %monitored_files = %{ $_[0] };
	my %current_file_positions = %{ $_[1] };

	# Private hash for storing the new monitored files.
	my %new_file_positions = ();

	# Loop through the hash of monitored files.
	# Compare if the file allready has been a part of the hash
	# which contains the file positions and transfer the stored
	# cursor position into the temporary hash which will be returned.
	#
	# Otherwise, call the responsible function to obtain the current
	# end of file (EOF) and store it.
	foreach my $file (keys %monitored_files) {
		# Check if the filename is allready part of the hash
		# of file positions.
		if (exists($current_file_positions{$file})) {
			# Copy file position into temporary hash.
			$new_file_positions{$file} = $current_file_positions{$file};
		} else {
			# Call function to obtain the file position.
			my $position = &GetFileposition($file);

			# Add filename and position to the temporary hash.
			$new_file_positions{$file} = $position;
		}
	}

	# Return the new_file_positions hash.
	return %new_file_positions;
}

#
## Address/Network to binary format caluculator function.
#
## This function is used to convert a given single IP address
## or network into a binary format.
#
## The used Net::IP module is not able to directly detect
## single addresses or network ranges. Only an element which may be
## a single address or a whole network can be assigned, for which a
## lot of different values can be calculated. In case the input has
## been a single address, the module will calculate the same binary
## address (intip) and last address for the network range (last_int)
## because internally it uses a /32 bit prefix for IPv4 and a /128 prefix
## on IPv6 addresses.
#
## So a single address can be detected by just comparing both calculated
## addresses if they are equal.
#
sub IPOrNet2Int($) {
	my $address = shift;

	# Assign and validate the given address, or directly return
	# nothing (False) and exit the function.
	my $ip = new Net::IP ($address) || return;

	# Convert the given address into integer format.
	my $first .= $ip->intip();

	# Calculate last address for the given network.
	my $last .= $ip->last_int();

	# Check whether the first address equals the last address.
	# If this is true, a single IP address has been passed.
	if ($first eq $last) {
		# Return the binary converted single address.
		return $first;
	}

	# If both addresses are not equal a network has been passed.
	#
	# Check if the converted first address is less than the calculated last
	# address of the network.
	elsif ($first < $last) {
		# Return the binary converted first and last address of
		# the given network.
		return $first, $last;
	}

	# If we got here, something strange happend, return nothing (False).
	else {
		return;
	}
}

#
## DetectIPProtocolVersion function.
#
## Wrapper function for determining the used protocol version (4/6)
## for a given IP address.
#
sub DetectIPProtocolVersion ($) {
	my $address = shift;

	# Call external perl module to detect the used IP protocol version.
	my $version = &Net::IP::ip_get_version($address);

	# Return the detected version.
	return $version;
}

#
## Function to get the current (EOF) cursor postion.
#
## This function is used to get the cursor position of the end of file (EOF) of
## a specified file.
#
## In order to prevent from permanently read and keep files opened, or dealing
## with huge logfiles, at initialization time of the worker processes, the file will
## be opened once and the cursor position of the end of file (EOF) get stored.
#
sub GetFileposition ($) {
	my $file = $_[0];

	# Open the file.
	open(FILE, $file) or die "Could not open $file. $!";

	# Just seek to the end of the file (EOF).
	seek(FILE, 0, 2);

	# Get and store the position.
	my $position = tell(FILE),

	# Close the file again.
	close(FILE);

	# Return the position.
	return $position;
}

#
## The SortAddressHash function.
#
# This function requires a reference to an hash containing
# IP-addresses, will sort them into a nice looking order
# and return the soreted result as an array.
#
sub SortAddressHash (\%) {
	# Dereference the given hash reference and store it
	# in a new temporary hash.
	my %addresshash = %{ $_[0] };

	# Loop through the entire hash keys.
	foreach my $address (keys(%addresshash)) {
		# Convert the address or subnet into binary format.
		my @bin_address = &IPOrNet2Int($address);

		# Only store the first result if there are multiple
		# one in case of a given subnet.
		$addresshash{$address} = $bin_address[0];
        }

	# Sort the addresshash by the binary addresses
        # of the stored addresses and save them is an array.
        my @sorted_addresses = sort { $addresshash{$a} <=> $addresshash{$b} } keys %addresshash;

        # Return the sorted address array.
        return @sorted_addresses;
}

1;
