#-- perl --

package Lm;

use strict;
use warnings;

use Lm::Command;
use Lm::CommandList;
use Lm::Config;
use Lm::Defines;
use Lm::DB;
use Lm::Host;
use Lm::Poll;
use Lm::Sensor;
use Lm::Util;

use Carp;

use Data::Dumper;

use vars qw($AUTOLOAD);

=head1 NAME

Lm : Lm Host Health Monitoring main interface module

=head1 SYNOPSIS

    use Lm;

    my $lm = new Lm;    # start up Lm session
    
    $lm->finish;        # close Lm session

Since all of the functionallity is mostly held in the Lm::* modules, the 
point of having this module is to insulate the programmer from all of the 
internals of the database... 

Any program utilizing the database and the lm package should go directly thru
methods provided in this module, and not circumvent them by going to the Lm::db
module directly.

Changes:

Added the Lm::Config usage... this should eliminate all the crud in the Lm::Defines file... instead allow it all to be configured via the config file :)  --ajkunen
Lm::Defines now only contains non-configurable constants (error codes, etc)

=over 4

=cut

=head1 CONSTRUCTOR / DESTRUCTOR

=item my $lm = new Lm;

Creates a new Lm session

=cut

sub new
{
    my ($class, %opts) = @_;

    my $ref;
    
    # load the config file
    my $cfile = "/s/lm-1.2/common/etc/lm.xml";
    $cfile = $opts{config} if $opts{config};
    my $conf = new Lm::Config($cfile) or return;
    $ref->{conf} = $conf;

    # create db connections
    $ref->{db} = new Lm::DB(config => $conf) or return;

    # bless and return
    my $ret = bless $ref, $class;
    return $ret;
}


my %DB_AUTOLOAD = 
(


    # HOST Commands
    'addHost'               => {    query => 'ADD_HOST',
                                    method => 'pbDo',
                                    args => [qw(host board os poll report)] },

    'getHost'               => {    query => 'GET_HOST',
                                    method => 'pbHashSingle',
                                    args => [qw(host)] },

    'getAllHosts'           => {    query => 'GET_ALL_HOSTS',
                                    method => 'pbHashRef',
                                    'keys' => ['host'],
                                    args => [ ] },

    'retireHost'            => {    query => 'RETIRE_HOST',
                                    method => 'pbDo',
                                    args => [qw(host)] },

    'retireDevice'          => {    query => 'RETIRE_DEVICE',
                                    method => 'pbDo',
                                    args => [qw(host device)] },

    'getBaselineHosts'      => {    query => 'GET_BASELINE_HOSTS',
                                    method => 'pbList',
                                    'keys' => ['host'],
                                    args => [ ] },

    'getHostsByBoard'       => {    query => 'GET_HOSTS_BY_BOARD',
                                    method => 'pbList',
                                    'keys' => ['host'],
                                    args => [qw(board os)] },
                    


    # HOST CONFIG Commands
    'getHostConfig'         => {    query => 'GET_HOST_CONFIG',
                                    method => 'pbHashRefValue',
                                    'keys' => ['attrib'], 
                                    valkey => 'value',
                                    args => [ qw(host)] },

    'getAllHostConfigs'     => {    query => 'GET_ALL_HOST_CONFIGS',
                                    method => 'pbHashRefValue',
                                    'keys' => [qw(host attrib)],
                                    valkey => 'value',
                                    args => [ ] },

    'setHostAttrib'         => {    query => ['UPDATE_HOST_ATTRIB',
                                              'INSERT_HOST_ATTRIB'],
                                    method => 'pbUpdateInsert',
                                    args => [ qw(host attrib value) ] },


    
    # SENSOR Commands

    'getHostSensors'        => {    query => 'GET_HOST_SENSORS',
                                    method => 'pbHashRef',
                                    'keys' => [qw(sensor)],
                                    args => [ qw(host) ] },

    'getSensor'             => {    query => 'GET_SENSOR',
                                    method => 'pbHashSingle',
                                    args => [ qw(host sensor) ] },
                                    
    'getAllSensors'         => {    query => 'GET_ALL_SENSORS',
                                    method => 'pbHashRef',
                                    'keys' => [qw(host sensor)],
                                    args => [ ] },

    'addSensor'             => {    query => 'ADD_SENSOR',
                                    method => 'pbDo',
                                    args => [qw(host sensor datatype class)],
                                    subs => {
                                            baseline => sub
                                                {
                                                    my ($this) = @_;
                                                    return (24*60*60* $this->{conf}->get('/Baseline/BasePeriod'));
                                                }
                                        } },
                                        
                                    
                                    

    # SENSOR CONFIG Commands

    'getSensorConfig'       => {    query => 'GET_SENSOR_CONFIG',
                                    method => 'pbHashRefValue',
                                    'keys' => [qw(attrib)],
                                    valkey => 'value',
                                    args => [ qw(host sensor) ] },

    'getAllSensorConfigs'   => {    query => 'GET_ALL_SENSOR_CONFIGS',
                                    method => 'pbHashRefValue',
                                    'keys' => [qw(host sensor attrib)],
                                    valkey => 'value',
                                    args => [ ] },

    'getHostSensorConfigs'  => {    query => 'GET_HOST_SENSOR_CONFIGS',
                                    method => 'pbHashRefValue',
                                    'keys' => [qw(sensor attrib)],
                                    valkey => 'value',
                                    args => [qw(host) ] },

    'setSensorAttrib'       => {    query => [ 'UPDATE_SENSOR_ATTRIB',
                                               'INSERT_SENSOR_ATTRIB'] ,
                                    method => 'pbUpdateInsert',
                                    args => [qw(host sensor attrib value)] },

    # EXEC_QUEUE Commands
    
    'getReadyCommands'      => {    query => 'GET_READY_COMMANDS',
                                    method => 'pbHashList',
                                    args => [ ] },

    'getPendingCommands'    => {    query => 'GET_PENDING_COMMANDS',
                                    method => 'pbHashList',
                                    args => [ ] },

    'getActiveCommands'     => {    query => 'GET_ACTIVE_COMMANDS',
                                    method => 'pbHashList',
                                    args => [ ] },

    'getCompletedCommands'  => {    query => 'GET_COMPLETED_COMMANDS',
                                    method => 'pbHashList',
                                    args => [ ] },

    'getCompletedCommandsLimit'  => {    query => 'GET_COMPLETED_COMMANDS_LIMIT',
                                    method => 'pbHashList',
                                    args => [qw(limit)] },

    'getCommand'            => {    query => 'GET_COMMAND',
                                    method => 'pbHashSingle',
                                    args => [qw(cid)] },
    
    'beginCommand'          => {    query => 'MARK_COMMAND_STARTED',
                                    method => 'pbDo',
                                    args => [qw(cid)] },

    'endCommand'            => {    query => 'MARK_COMMAND_COMPLETED',
                                    method => 'pbDo',
                                    args => [qw(cid result)] },

    'failActiveCommands'    => {    query => 'FAIL_ACTIVE_COMMANDS',
                                    method => 'pbDo',
                                    args => [ ] },

    '_reserveCommandID'     => {    query => 'RESERVE_COMMAND_ID',
                                    method => 'pbHashSingle',
                                    args => [ ] },
                                
    'issueCommand'        => {    query => 'INSERT_COMMAND',
                                    method => 'pbDo',
                                    'return' => 'cid',
                                    subs => {
                                        cid =>  
                                            sub {
                                                my ($this) = @_;
                                                my $cid = $this->db__reserveCommandID();
                                                
                                                die $this->getError() if $this->isError();

                                                return $cid->{nextval};
                                            },
                                        time_unquoted =>
                                            sub {
                                                my ($this, @args) = @_;
                                                my $t = "null";
                                                $t = "'$args[2]'" if $args[2];
                                                return $t;
                                            }
                                    },
                                    args => [ qw(command args time_unquoted) ] },

    # Board dependency stuff
    'getBoardSensorDep'     =>  {   query => 'GET_BOARD_SENSORDEP',
                                    method => 'pbHashList',
                                    args => [qw(board os)] },

    
);


sub AUTOLOAD
{
    my ($this, @args) = @_;
    my $type = ref($this) || die "$this is not an object!";

    # grab the name
    my $name = $AUTOLOAD;
    $name =~ s/.*://;
   
    # switchboard
    if($name =~ /^db_(\w+)$/ && $DB_AUTOLOAD{$1})
    {
        $this->_runDB_AUTOLOAD($1, @args);
    }
    else
    {
        confess "Command $AUTOLOAD does not exist!\n";
    }
}

sub DESTROY
{
    my ($this) = @_;
    # make sure the db gets disconnected
    $this->{db}->disconnect() if($this->{db}->{dbh});
}

sub _runDB_AUTOLOAD
{
    my ($this, $name, @args) = @_;
    my $type = ref($this) || die "$this is not an object!";

    # grab the subroutine information
    my $h = $DB_AUTOLOAD{$name};
    return if(!$h);
    my $method = $h->{method};
    my $query = $h->{query};

    # run the query
    my $ret;
    eval 
    {
        # munge the arguments into the hash
        my @tmp = @args;
        my %opts = map { $_ => shift @tmp } @{$h->{args}};

        # see if we need to exec a subroutine first
        if($h->{subs})
        {
            foreach my $key (keys %{$h->{subs}})
            {
                $opts{$key} = &{$h->{subs}->{$key}}($this, @args);
            }
        }

        # Okay, run the query
        if($h->{'keys'})
        {
            if($h->{'valkey'})
            {
                $ret = $this->{db}->$method($query, $h->{'keys'}, $h->{'valkey'}, %opts);
            }
            else
            {
                $ret = $this->{db}->$method($query, $h->{'keys'}, %opts);
            }
        }
        else
        {
            $ret = $this->{db}->$method($query, %opts);
        }

        # check for a return value
        if($h->{'return'})
        {
            $ret = $opts{$h->{'return'}};
        }
    };
    if($@)
    {
        $this->setError("Lm->$name failed: $@");
        return;        
    }
    return $ret;
}

=item $lm->finish();

Closes the Lm session (closes db connections, etc)

=cut

sub finish
{
    my ($this) = @_;

    $this->{db}->disconnect;
}

=item $lm->begin();

Begins a new DB transaction

=cut

sub begin
{
    my ($this) = @_;
    $this->{db}->begin();
}

=item $lm->rollback();

Rolls-back an open DB transaction.

=cut 

sub rollback
{
    my ($this) = @_;
    $this->{db}->rollback();
    
}

=item $lm->commit();

Commits an open DB transaction.

=cut

sub commit
{
    my ($this) = @_;
    $this->{db}->commit();
}

=head1 ERROR HANDLING

=item $lm->setError($error_message);

Sets the current error message string.  This should only be used internally in the Lm module to report DB errors, etc.

=cut

sub setError
{
     my($this, $errmsg) = @_;

     $this->{ERROR} =  $errmsg;
}

=item $lm->getError();

Returns and clears the current error message. If no message exists, returns undef.

=cut

sub getError
{
    my($this) = @_;
    my $err = $this->{ERROR};
    $this->{ERROR} = undef;
    return $err;
}

=item $lm->isError();

Returns true if there is currently a stored error. Doesn't clear the error message

=cut

sub isError
{
    my ($this) = @_;
    if(defined $this->{ERROR}){return 1;}
    return;
}


# Takes a polling result (Lm::Service::Server) object and
# add host and sensor entries for it
sub addHostFromPoll
{
    my ($this, $hostname, $host, $polltype) = @_;

    # add host entry
    $this->db_addHost($hostname, $host->board, $host->os, $polltype, "Nagios");
   
    # add sensors
    foreach my $sensor ($host->listSensors)
    {
        my $ref = $host->sensor($sensor);
        next unless defined $ref->{value};
        my $obj = $this->addSensor($hostname, $sensor, $ref->{datatype}, $ref->{class});

        # set all the attributes
        foreach my $a ($obj->validAttribs)
        {
            next unless defined $ref->{$a};
            $obj->$a($ref->{$a});
        }
    }

    # return the host object
    return $this->getHost($hostname);
}


sub addSensor
{
    my ($this, $host, $sensor, $datatype, $class) = @_;

    # add sensor to database
    $this->db_addSensor($host, $sensor, $datatype, $class) or return;
    
    # now return the Lm::Sensor object 
    return $this->getSensor($host, $sensor);
}

sub getHost
{
    my ($this, $host) = @_;
    
    # create the host object and return
    return new Lm::Host($this, $host);
}

sub getBaselineHosts
{
    my ($this) = @_;
    my %hosts = map { $_ => $this->getHost($_) } @{$this->db_getBaselineHosts()};
    return \%hosts;
}

sub getSensor
{
    my ($this, $host, $sensor) = @_;

    # create a new sensor object and return
    return new Lm::Sensor($this, $host, $sensor);
}

sub getHostSensors
{
    my ($this, $host) = @_;

    # lookup all of the sensors for this host
    my $sensors = $this->db_getHostSensors($host);
    my $configs = $this->db_getHostSensorConfigs($host);

    # create a list of sensor object
    my $objs;
    foreach my $sensor (keys %$sensors)
    {
        $objs->{$sensor} = new_from_ref Lm::Sensor($this, $sensors->{$sensor}, $configs->{$sensor});
    }

    # done
    return $objs;
}

sub getAllHosts
{
    my ($this, %opts) = @_;

    # lookup all of the stuff
    my $hrec = $this->db_getAllHosts();
    my $hconf = $this->db_getAllHostConfigs();

    my $srec;
    my $sconf;
    if(!$opts{NoSensors})
    {
        $srec = $this->db_getAllSensors();
        $sconf = $this->db_getAllSensorConfigs();
    }

    # build all of our host objects
    my %hosts;
    foreach my $host (keys %$hrec)
    {
        # create all of the sensor objects
        my %sensors;
        if($opts{NoSensors})
        {
            foreach my $sensor (keys %{$srec->{$host}})
            {
                $sensors{$sensor} = new_from_ref Lm::Sensor(
                                    $this,
                                    $srec->{$host}->{$sensor},
                                    $sconf->{$host}->{$sensor},
                                    undef);
            }
        }
        
        # finally create the host object
        $hosts{$host} = new_from_ref Lm::Host($this, $hrec->{$host}, $hconf->{$host}, \%sensors);
    }

    # return the host objects hashref
    return \%hosts;
}



###
# create a polling object and return
### 
sub initPoll
{
    my ($this) = @_;
    
    # create the polling object
    my $p = new Lm::Poll($this->{conf});
    
    return $p;
}

#
# Creates a new command
#
sub issueCommand
{
    my ($this, $command, $args, $when) = @_;
    
    # create the command
    $when = to_pg($when) if defined $when;
    my $cid = $this->db_issueCommand($command, $args, $when)
        or return;

    # return the command object
    return $this->getCommand($cid);
}

#
# Returns a command object
#
sub getCommand
{
    my ($this, $cid) = @_;
    return new Lm::Command($this, $cid);
}

#
# Returns pending commands in order of execution
#
# Type: active | pending | failed
sub getCommandList
{
    my ($this, $type, $limit) = @_;

    # return a new CommandList object
    return new Lm::CommandList($this, $type, $limit);
}


#
# Returns the config object
#
sub config
{
    return $_[0]->{conf};
}

##############################################################################
# END
##############################################################################
=back

=head1 AUTHOR

Adam J Kunen <ajkunen@cs.wisc.edu>

=cut

# done
1;
