###################################################################
# Copyright (C) 2011 RoyaltyShare, Inc.  All Rights Reserved
###################################################################

package Common::Daemon;

use strict;

use Carp;
use File::Basename;
use Proc::Daemon;
use File::Pid;

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

use base 'Common::Script';

sub logfile   { confess "Must be overloaded" }
sub pidfile   { confess "Must be overloaded" }
sub sleepTime { 5 }

my $PID;
$SIG{INT}  = \&SIGTRAP;
$SIG{KILL} = \&SIGTRAP;

sub _init {
    my $self = shift;

    $self->SUPER::_init(@_);

    $self->{_app} = new Common::RSApp( clientID => 0 );

    return $self;
}

sub start {
    my $class = shift;
    my $self = ref($class) ? $class : $class->new(@_);

    $self->_parse_options();

    if ( $self->{_help} ) {
        $self->usage();
    } elsif ( $self->{_kill} ) {
        $self->kill();
    } else {
        $self->run();
    }
}

sub run {
    my $self = shift;

    my $name = basename $0;

    if ( my $pid = $self->_isRunning() ) {
        die "$name is already running. PID: $pid\n";
    }

    if ( $self->param('foreground') ) {
        Log->warn("Starting $name in foreground");
        $self->_startForeground();
    } else {
        $self->_startDaemon();
    }

}

sub _startForeground {
    my $self = shift;

    my $name = basename $0;

    my $pid = $$;

    $PID = new File::Pid( { file => $self->pidfile } );
    $PID->write || die "Failed to write PID file: " . $self->pidfile . "\n";

    Log->warn("Starting $name in foreground");
    $self->_processLoop();
}

sub _startDaemon {
    my $self   = shift;
    my $daemon = Proc::Daemon->new();

    my $name = basename $0;

    open STDOUT, '>>' . $self->logfile or die "CANNOT OPEN " . $self->logfile . " : $!\n";
    open STDERR, '>>' . $self->logfile or die "CANNOT OPEN " . $self->logfile . " : $!\n";

    my $pid = $daemon->Init;

    unless ($pid) {

        open STDOUT, '>>' . $self->logfile or die;
        open STDERR, '>>' . $self->logfile or die;

        $PID = new File::Pid( { file => $self->pidfile } );
        $PID->write || die "Failed to write PID file: " . $self->pidfile . "\n";

        Log->warn("Starting $name daemon");
        $self->_processLoop();
    }
}

sub SIGTRAP {
    my $sig = shift;

    my $name = basename $0;

    Log->info("signal trapped: $sig");

    $PID->remove() if ($PID);

    Log->warn("Stopping $name");
    exit 1;
}

sub _processLoop {
    my $self = shift;

    assert( $self->sleepTime(), "Sleep time not defined!" );

    while (1) {
        $self->_heartbeat();
        $self->_process();
        sleep $self->sleepTime();
    }
}

sub _heartbeat {
    Log->info("_heartbeat not overloaded.");
}

sub kill {
    my $self = shift;
    my $pid  = $self->_isRunning();

    if ($pid) {
        kill 'INT', $pid || die "Couldn't kill daemon: $!";
        unlink $self->pidfile if ( -e $self->pidfile );
    } else {
        die "Daemon not currently running\n";
    }
}

sub _isRunning {
    my $self = shift;
    my $process;

    return unless ( -e $self->pidfile );

    open( INFILE, $self->pidfile ) || die "Could not read PID file '" . $self->pidfile . "' : $!\n";
    Log->debug( "Looking for PID in file " . $self->pidfile );

    my $pid = <INFILE>;
    chomp $pid;

    if ($pid) {
        Log->info("PID for daemon is $pid");
        Log->debug("Checking to see if $pid actually exists");

        my $table = new Proc::ProcessTable();
        foreach ( @{ $table->table } ) {
            $process = $_ if ( $_->pid == $pid );
        }

        # Return undef unless we actually find a process with that ID
        unless ($process) {
            Log->warn("Process $pid doesn't actually exist");
            unlink $self->pidfile || die "Could not remove PID file: $!";
            return;
        }
    } else {
        Log->info("No PID found");
    }
    return $pid;
}

sub _common_list { qw( debug verbose help ) }

sub _common_options {
    {
        foreground => {
            short       => 'f',
            description => 'Run in foreground, do not daemonize.',
        },
        kill => {
            short       => 'k',
            description => 'Kill current running daemon.',
        },
        verbose => {
            short       => 'v+',
            description => 'Increase display verbosity.',
        },
        debug => {
            short       => 'd',
            description => 'Set log level to debug',
        },
        help => {
            short       => 'h',
            description => 'display this screen'
        },
    };
}

1;
