package RackMan;

use Moose;
use Term::ANSIColor qw< :constants >;
use namespace::autoclean;


our $VERSION = "1.17";
our $STATUS  = 0;
$::COMMAND ||= __PACKAGE__;


has config => (
    is => "ro",
    isa => "RackMan::Config",
    default => sub {
        require RackMan::Config;
        RackMan::Config->new(-file => "/etc/rack.conf")
    },
);

has options => (
    is => "ro",
    isa => "HashRef",
);

has racktables => (
    is => "ro",
    isa => "RackTables::Schema",
    lazy => 1,
    builder => "connection",
);


#
# connection()
# ----------
sub connection {
    my $self = shift;

    # check that the connection parameters are correctly set
    my @params = $self->config->val(database => "dsn")
        or RackMan->error("[database]/dsn is missing from the config file");

    push @params, map {
        my $value = $self->config->val(database => $_);
        defined $value ? $value
        : RackMan->error("[database]/$_ is missing from the config file")
    } qw< user password >;

    require RackTables::Schema;
    return RackTables::Schema->connect(@params)
}


#
# device_by_id()
# ------------
sub device_by_id {
    my ($self, $id) = @_;

    require RackMan::Device;
    return RackMan::Device->new({
        id => $id,  rackman => $self,
        racktables => $self->racktables,
    })
}


#
# device_by_name()
# --------------
sub device_by_name {
    my ($self, $name) = @_;

    require RackMan::Device;
    return RackMan::Device->new({
        name => $name,  rackman => $self,
        racktables => $self->racktables,
    })
}


#
# device()
# ------
*device = \&device_by_name;


#
# process()
# -------
sub process {
    my ($self, $action, @args) = @_;

    my %opts;
    my @rest;

    # parse task options
    while (my $arg = shift @args) {
        if ($arg eq "as") {
            $opts{$arg} = shift @args;
        }
        else {
            push @rest, $arg;
        }
    }

    my $name = pop @rest;
    my $method = "task_$action";

    require RackMan::Tasks;

    RackMan->error("unknown action '$action'")
        unless RackMan::Tasks->meta->has_method($method);

    if ($action eq "list") {
        RackMan::Tasks->$method({ rackman => $self, stdout => 1,
            type => $name, %opts });
    }
    else {
        my $object = $self->device($name);
        $self->config->set_current_rackobject($object);
        RackMan::Tasks->meta->apply($object);
        $object->$method({ rackman => $self, stdout => 1, %opts });
    }
}


#
# get_scm()
# -------
sub get_scm {
    my ($self, $args) = @_;

    my $type = "none";
    my $caller = caller();

    if ($self->options->{scm}) {
        # global default SCM tool
        my $default = $self->config->val(general => "default_scm", "none");

        # check if the caller has its own configuration section
        if ($caller->can("CONFIG_SECTION")) {
            my $section = $caller->CONFIG_SECTION;
            $type = $self->config->val($section, "scm", $default);
        }
    }

    # clean leading and trailing spaces
    $type =~ s/^ +| +$//g;

    require RackMan::SCM;
    return RackMan::SCM->new({ type => $type, %$args,
        verbose => $self->options->{verbose}, prefix => "    x " })
}


#
# warning()
# -------
sub warning {
    my $class = shift;
    warn "$::COMMAND: ", BOLD(YELLOW("warning: ")), @_, RESET, "\n";
}


#
# error()
# -----
sub error {
    my $class = shift;
    warn "$::COMMAND: ", BOLD(RED("error: ")), @_, RESET, "\n";
    exit ($STATUS || 1);
}


#
# error_n()
# -------
sub error_n {
    print STDERR "\n";
    goto &error;
}


#
# status()
# ------
sub status {
    return $STATUS
}


#
# set_status()
# ----------
sub set_status {
    $STATUS = $_[1];
}


__PACKAGE__->meta->make_immutable;

__END__

=pod

=head1 NAME

RackMan - Main interface for accessing a RackTables database

=head1 SYNOPSIS

    use RackMan;
    use RackMan::Config;

    my $config  = RackMan::Config->new(-file => "/etc/rack.conf");
    my $rackman = RackMan->new({ config => $config });


=head1 DESCRIPTION

RackMan is a set of Perl modules for fetching information from a
L<RackTables|http://racktables.org/> database. The distribution
also includes some commands that show how to use the RackMan API.

=over 

=item *
rack is a program that generates the configuration files for the
given RackObject, and talk with the corresponding devices to set
them up accordingly.

=item *
cisco-status is a program that connects to a Cisco switch to list
the devices connected to it, with additionnal information resolved
from RackTables.

=item *
cfengine-tags is a program that generates tag files for Cfengine.

=back
    
A technical presentation of this software was made at the French
Perl Workshop 2012: L<http://maddingue.org/conferences/fpw-2012/rackman/>

Note: This software was written to perform very specific tasks.
Although it was tried to keep it generic, it certainly isn't, and
the documentation is very rough. There's a more comprehensive
tutorial (only in French for now) in pod/RackMan/Manual.fr.pod

This C<RackMan> module provides the main interface for accessing a
RackTables database.


=head1 METHODS

=head2 connection

Connect to the RackTables database using the parameters from the
configuration file.


=head2 device

Alias for C<device_by_name()>.


=head2 device_by_id

Try to find and return the RackObject with the given ID.


=head2 device_by_name

Try to find and return the RackObject with the given name.


=head2 get_scm

Return a RackMan::SCM object corresponding to the tool selected by the
configuration file and command line options.


=head2 process

Process and execute an action as given from the command line.

Arguments are expected to be:

=over

=item *

the action name

=item *

optional action options, as a plain hash

=item *

RackObject name

=back


=head2 error

Class method to print an error and exit.


=head2 error_n

Class method to print an error, preceded by a newline, and exit.


=head2 set_status

Class method to set the return status of the program.


=head2 status

Class method to get the return status of the program.


=head2 warning

Class method to print a warning.


=head1 AUTHOR

Sebastien Aperghis-Tramoni (sebastien@aperghis.net)

=cut

