#!/usr/bin/perl
# Main OpenTrafficShaper program
# Copyright (C) 2007-2013, AllWorldIT
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
use warnings;


# Set the dirs we look for library files in
use lib('/usr/local/lib/opentrafficshaper-1.0','/usr/lib/opentrafficshaper-1.0',
		'/usr/lib64/opentrafficshaper-1.0','opentrafficshaper','awitpt', 'lib');

# Enable assertions
BEGIN {
	package POE::Kernel;
	use constant ASSERT_DEFAULT => 1;
}

# System stuff we need
use Config::IniFiles;
use Getopt::Long;
use POE;
use POSIX qw(setsid);
use Time::HiRes qw(time);

# Our own stuff
use opentrafficshaper::version;
use opentrafficshaper::logger;
use opentrafficshaper::plugins qw( plugin_register );


# Main config
my $globals;
# We just create the logger first, its only using STDERR here
my $logger = new opentrafficshaper::logger;


#
# MAIN
#
parseCfgCmdLine();

# Check if we must use a log file instead
if (defined($globals->{'config'}->{'log_file'})) {
	$logger->open($globals->{'config'}->{'log_file'});
}
$logger->setLevel($globals->{'config'}->{'log_level'});
# Check if we need to go background
if (defined($globals->{'config'}->{'background'})) {
	daemonize();
}

displayBanner();
init();
start();
$logger->log(LOG_NOTICE,"[MAIN] Entering RUNNING state");
POE::Kernel->run();
exit;




# Function to display banner
sub displayBanner
{
	$logger->log(LOG_NOTICE,"[MAIN] OpenTrafficShaper v".VERSION." - Copyright (c) 2007-2013, AllWorldIT");
}


# Function to parse our config and commandline
sub parseCfgCmdLine
{

	# Set defaults
	my $cfg;
	$cfg->{'config_file'} = "/etc/opentrafficshaper.conf";

	$cfg->{'background'} = "yes";
	$cfg->{'pid_file'} = "/var/run/opentrafficshaper/opentrafficshaperd.pid";
	$cfg->{'log_level'} = 2;
	$cfg->{'log_file'} = "/var/log/opentrafficshaper/opentrafficshaperd.log";

	# Parse command line params
	my $cmdline;
	%{$cmdline} = ();
	GetOptions(
			\%{$cmdline},
			"help",
			"config:s",
			"debug",
			"fg",
	) or die "Error parsing commandline arguments";

	# Check for some args
	if ($cmdline->{'help'}) {
		displayHelp();
		exit 0;
	}
	if (defined($cmdline->{'config'}) && $cmdline->{'config'} ne "") {
		$cfg->{'config_file'} = $cmdline->{'config'};
	}

	# Check config file exists
	if (! -f $cfg->{'config_file'}) {
		die("No configuration file '".$cfg->{'config_file'}."' found!");
	}

	# Use config file, ignore case
	tie my %inifile, 'Config::IniFiles', (
			-file => $cfg->{'config_file'},
			-nocase => 1
	) or die "Failed to open config file '".$cfg->{'config_file'}."': $!";
	my $inifileHandle = tied( %inifile );
	# Copy config
	my %config = %inifile;

	# Pull in params for the server
	my @server_params = (
			'log_level','log_file',
			'pid_file',
			'background',
	);
	foreach my $param (@server_params) {
		$cfg->{$param} = $config{'system'}{$param} if (defined($config{'system'}{$param}));
	}

	# Override
	if ($cmdline->{'debug'}) {
		$cfg->{'log_level'} = 4;
		$cfg->{'debug'} = 1;
	}

	# If we set on commandline for foreground, keep in foreground
	if ($cmdline->{'fg'} || (defined($config{'system'}{'background'}) && $config{'system'}{'background'} eq "no" )) {
		$cfg->{'background'} = undef;
		$cfg->{'log_file'} = undef;
		$cfg->{'pid_file'} = undef;
	}

	#
	# System plugins
	#
	if (ref($config{'plugins'}{'load'}) eq "ARRAY") {
		foreach my $plugin (@{$config{'plugins'}{'load'}}) {
			$plugin =~ s/\s+//g;
	 		# Skip comments
	 		next if ($plugin =~ /^#/);
			push(@{$cfg->{'plugin_list'}},$plugin);
		}
	} elsif (defined($config{'plugins'}{'load'})) {
		my @pluginList = split(/\s+/,$config{'plugins'}{'load'});
		foreach my $plugin (@pluginList) {
			# Skip comments
			next if ($plugin =~ /^#/);
			push(@{$cfg->{'plugin_list'}},$plugin);
		}
	}

	# We may have config file groups we want to remember for other plugins
	foreach my $group ($inifileHandle->Groups()) {
		# Loop with group members
		foreach my $member ($inifileHandle->GroupMembers($group)) {
			# Chop off group name and just get the member
			my $cleanMember = substr($member,length($group)+1);
			# Link the config...
			$config{$group}->{$cleanMember} = $config{$member};
		}
	}

	$globals->{'file.config'} = \%config;
	$globals->{'config'} = $cfg;
}


# Display help
sub displayHelp {
	displayBanner();

	print(STDERR<<EOF);

Usage: $0 [args]
    --config=<file>        Configuration file
    --debug                Put into debug mode
    --fg                   Don't go into background

EOF
}


# Initialize things we need
sub init
{
	# Certain things we need
	$globals->{'users'} = { };
	$globals->{'logger'} = $logger;
	$globals->{'version'} = VERSION;

	$logger->log(LOG_NOTICE,"[MAIN] Entering INITIALIZATION state");

	# Setup master session
	POE::Session->create(
		inline_states => {
			'_start' => \&main_session_start,
			'_stop' => \&main_session_stop,
			'main_SIGHUP' => \&main_signal_SIGHUP,
			'main_SIGINT' => \&main_signal_SIGINT,
		},
	);

	$logger->log(LOG_INFO,"[MAIN] Initializing plugins...");
	# We need to set the plugins global variable
	opentrafficshaper::plugins::init($globals);

	# Core configuration manager
	plugin_register("configmanager",1);

	# Load plugins
	foreach my $pluginName (@{$globals->{'config'}->{'plugin_list'}}) {
		plugin_register($pluginName,0);
	}

	$logger->log(LOG_INFO,"[MAIN] Plugins initialized.");
}

# Function to start things up
sub start
{
	$logger->log(LOG_NOTICE,"[MAIN] Entering STARTING state");

	# Loop with plugins and call the start function for those that exist
	foreach my $pluginName (keys %{$globals->{'plugins'}})
	{
		my $plugin = $globals->{'plugins'}->{$pluginName};

		# Load the function up
		my $callStart = $plugin->{'Start'};
		if (defined($callStart)) {
			$callStart->();
		}
	}
}



# Become daemon
sub daemonize {
	chdir '/'
			or die "Can't chdir to /: $!";

	open STDIN, '/dev/null'
			or die "Can't read /dev/null: $!";

	open STDOUT, '> /dev/null'
			or die "Can't open stdout log: $!";

	defined(my $pid = fork) 
			or die "Can't fork: $!";

	exit if $pid;

	# Write out our PID if we have a file to do it
	if (defined($globals->{'config'}->{'pid_file'})) {
		if (open(FH,"> ".$globals->{'config'}->{'pid_file'})) {
			print(FH $$);
			close(FH);
		} else {
			$logger->log(LOG_WARN,"[MAIN] Unable to write PID to '".$globals->{'config'}->{'pid_file'}."': $!");
		}
	}

	setsid
			or die "Can't start a new session: $!";

	open STDERR, '> /dev/null'
			or die "Can't open stderr log: $!";
}



# Function to fire up our main session
sub main_session_start
{
	my $kernel = $_[KERNEL];

	$kernel->alias_set('main');

	# Register signal handlers
	$kernel->sig('HUP', 'main_SIGHUP');
	$kernel->sig('INT', 'main_SIGINT');
}


# Function to dispose of anything as a final stage to shutting down
sub main_session_stop
{
	$logger->log(LOG_DEBUG,"[MAIN] Shutdown");
}


# Function to handle SIGHUP
sub main_signal_SIGHUP
{
	my ($kernel,$signal_name) = @_[KERNEL,ARG0];

	$logger->log(LOG_NOTICE,"[MAIN] Got SIGHUP");

	$kernel->sig_handled();
}


# Function to handle SIGINT
sub main_signal_SIGINT
{
	my ($kernel, $signal_name) = @_[KERNEL, ARG0];

	$logger->log(LOG_NOTICE,"[MAIN] Got SIGINT, shutting down...");

	$kernel->signal($kernel,'handle_SIGINT');
}


# vim: ts=4