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

package Common::Script;

use strict;

use Date::Format;
use File::Basename;
use Sys::Hostname;
use Carp;
use IO::Scalar;
use Date::Format;
use Proc::ProcessTable;
use Getopt::Long;
use Data::Dumper;

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

$| = 1;

sub new {
    my ( $class, %args ) = @_;
    my $self = bless {}, $class;

    return $self->_init(%args);
}

sub _init {
    my ( $self, %args ) = @_;
    my $stdout;
    my $stderr;
    my $io;

    $self->{_debug}     = $args{debug};
    $self->{_verbose}   = $args{verbose};
    $self->{_quiet}     = $args{quiet};
    $self->{_client_id} = $args{client_id};

    if ( $self->{_verbose} ) {
        $self->{_implicitDebug} = 1;
    }

    if ( $self->{_debug} || ( $self->{_verbose} && $self->{_verbose} >= 3 ) ) {
        $ENV{DEV_SERVER} = 1;
    }

    $self->_setup_log();

    return $self;
}

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

    $self->_parse_options();

    if ( defined( $self->{_list} ) ) {
        $self->list_clients( $self->{_list} );
    } elsif ( $self->{_help} ) {
        $self->usage();
    } elsif ( $self->{_jobify} ) {
        $self->jobify();
    } else {
        $self->run();
    }
}

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

    $self->run_process();
}

sub print {
    my $self = shift;

    return unless (@_);

    # prepend a timestamp

    # Send the message to STDOUT
    print join( "\n", @_ ) . "\n" unless ( $self->{_quiet} );
}

sub log {
    my $self = shift;

    return unless (@_);

    # prepend a timestamp

    # Send the message to STDOUT
    print localtime() . " " . join( "\n", @_ ) . "\n" unless ( $self->{_quiet} );
}

sub debug {
    my $self = shift;

    return unless (@_);

    print STDERR localtime() . " " . join( "\n", @_ ) . "\n" if ( $self->{_debug} );
}

sub run_process {
    my $self = shift;
    my @list = $self->_get_client_list();

    $self->debug("  Client list: @list");

    foreach (@list) {
        $self->debug(" - Processing Client: $_");
        $self->_process_client($_);
    }
}

sub _process {
    my ($self) = @_;
    assert( 0, 'override this' );
}

sub _process_client {
    my $self      = shift;
    my $client_id = shift;

    assert($self);
    $self->error("Client ID must be greater than 0") unless ( $client_id > 0 );

    my $singleton = new Common::RSApp( clientID => $client_id );
    die("Invalid client id '$client_id'")
      unless ( $singleton && Common::RSDB::ClientIDToDBName($client_id) );

    die("Invalid client type") unless ( $self->_isValidClientType($client_id) );

    $self->_process();

    $singleton = undef;
}

sub _get_client_list {
    my $self = shift;
    my @list;
    my $host = Common::RSApp::GetRealHostname();

    $self->debug("Running on $host");

    assert($self);
    assert($host);

    my $singleton = new Common::RSApp( clientID => 0 );

    return ( $self->{_client_id} ) if ( defined( $self->{_client_id} ) );

    # If this is being run on a production web box
    if (Common::RSApp::IsProductionWebServer) {
        return sort { $a <=> $b } $self->_getWebProductionClientIDs();
    }

    # If this is being run on another production app box
    elsif (Common::RSApp::IsProductionServer) {
        return sort { $a <=> $b } $self->_getProductionClientIDs();
    }

    # Running on some other host, run of all install DBs on localhost
    else {
        return sort { $a <=> $b } $self->_getLocalClientIDs();
    }
}

sub _getWebProductionClientIDs {
    return Common::RSDB::GetClientIDs( Common::RSApp::GetRealHostname() );
}

sub _getProductionClientIDs {
    return Common::RSDB::GetAllClientIDs();
}

sub _getLocalClientIDs {
    return Common::RSDB::GetLocalClientIDs();
}

sub list_clients {
    my $self = shift;
    my $host = shift;
    my $key;
    foreach $key ( sort { $a <=> $b } keys(%Common::RSDB::CLIENT_DB) ) {
        printf( "%-20s %-10s ID: %s\n", $Common::RSDB::CLIENT_DB{$key}->{db_name}, "($Common::RSDB::CLIENT_DB{$key}->{host_id})", $key )
          if ( !defined($host) || $Common::RSDB::CLIENT_DB{$key}->{db_name} =~ /$host/i );
    }
    exit 1;
}

sub _parse_parameter_option {
    my $self = shift;
    my $parameter = shift || return '';

    if ( $parameter =~ /^[:=][sdif]$/ ) {
        return $parameter;
    } elsif ( $parameter =~ /^[sdif]$/ ) {
        return "=$parameter";
    } else {
        die "invalid parameter field '$parameter'.  Must be s, d, i, or f";
    }
}

sub _parse_options {
    my $self = shift;
    my %opts;

    my $options = $self->_get_options();

    foreach my $key ( keys(%$options) ) {
        my $name = "$key";
        my $storage;
        $name .= "|$options->{$key}->{short}" if ( $options->{$key}->{short} );
        $name .= $self->_parse_parameter_option( $options->{$key}->{parameter} );
        $opts{$name} = \$storage;
    }

    Getopt::Long::Configure("bundling");
    GetOptions(%opts) || $self->error();

    my $missing = $self->_set_options( \%opts );

    $self->_setup_log();

    $self->error("Required parameter: $missing") if ($missing);
}

sub _setup_log {
    my $self    = shift;
    my $debug   = $self->param('debug');
    my $verbose = $self->param('verbose');

    if ( $debug && !$self->{_implicitDebug} ) {
        Log->init( level => 'debug' );
    }

    if ($verbose) {
        Log->increaseLogLevel($verbose) if ($verbose);
    }
}

sub _set_options {
    my $self   = shift;
    my $values = shift;

    my $opts = $self->_get_options() || {};
    my $variable;

    Common::Log::Debug("Setting command line options");

    foreach my $key ( keys %$values ) {
        my ($name) = $key;
        $name =~ s/[|:=].*//;

        my $value = ${ $values->{$key} };
        $value = $opts->{$name}->{default} unless ( defined($value) );

        if ( defined($value) ) {
            $self->{ '_' . $name } = $value;
        }
    }

    # Check for required fields (unless we are displaying help or the client list)
    unless ( defined( $self->{_help} ) || defined( $self->{_list} ) ) {
        foreach my $key ( keys %$opts ) {
            if ( $opts->{$key}->{required} && !defined( $self->{ '_' . $key } ) ) {
                $self->error("$key is a required parameter");
            }
        }
    }

    if ( $self->{_verbose} ) {
        $self->{_implicitDebug} = 1;
    }

    if ( $self->{_debug} || ( $self->{_verbose} && $self->{_verbose} >= 3 ) ) {
        $ENV{DEV_SERVER} = 1;
    }

    return undef;
}

sub _options { }

sub _get_options {
    my $self   = shift;
    my $opts   = $self->_options() || {};
    my $common = $self->_common_options() || {};
    my $result;

    foreach my $key ( keys %$common ) {
        $result->{$key} = $common->{$key};
    }

    foreach my $key ( keys %$opts ) {
        $result->{$key} = $opts->{$key};
    }

    return $result;
}

sub _get_option_string {
    my $self = shift;
    my $result;

    my $opts = $self->_get_options();

    foreach my $key ( keys %$opts ) {
        $result .= "$key";
        $result .= ":" if ( $opts->{$key}->{parameter} );
    }

    return $result;
}

sub param {
    my $self = shift;
    my $name = shift;
    my $opts = $self->_get_options;

    assert($name);

    if ( grep( /^$name$/, keys %$opts ) ) {
        my $field = "_" . $name;
        return $self->{$field};
    } else {
        die "Unknown option name '$name'";
    }
}

sub error {
    shift->usage(@_);
}

sub usage {
    my $self  = shift;
    my $error = shift;

    my $options;
    my $detailed;
    my $prog = basename $0;

    my $opts = $self->_get_options() || {};
    my @order;

    # Add everything except the options we want added last
    foreach my $key ( keys %$opts ) {
        push( @order, $key )
          unless ( $key =~ /^(help|list|debug|verbose)$/ );
    }
    push( @order, $self->_common_list );

    foreach my $key (@order) {
        my $o = "--$key";
        if ( $opts->{$key}->{parameter} ) {
            $o .= ' value';
        }

        unless ( $opts->{$key}->{required} ) {
            $o = "[$o]";
        }

        $options .= " $o";

        die("parameter required _options '$key' missing description variable")
          unless ( $opts->{$key}->{description} );

        my $short = $opts->{$key}->{short} ? "[-$opts->{$key}->{short}] " : "";
        $short =~ s/\+// if ($short);

        $detailed .= "\t--$key $short- $opts->{$key}->{description}";
        $detailed .= " (default: $opts->{$key}->{default})" if ( $opts->{$key}->{default} );
        $detailed .= "\n";
    }

    print STDERR "$error\n" if ($error);
    print STDERR "USAGE: $prog$options \n$detailed";
    exit 1;
}

sub _isValidClientType { 1 }

sub _common_list { qw( debug verbose list help ) }

sub _common_options {
    my $self    = shift;
    my $options = {
        verbose => {
            short       => 'v+',
            description => 'Increase display verbosity.',
        },
        debug => {
            short       => 'D',
            description => 'Set log level to debug',
        },
        client_id => {
            short       => 'c',
            description => 'Limit to client id',
            parameter   => 'i'
        },
        list => {
            short       => 'l',
            description => 'List ALL clients (host pattern is optional)',
            parameter   => ':s',
        },
        help => {
            short       => 'h',
            description => 'display this screen'
        },
    };

    $options->{jobify} = { description => 'Enqueue a job instead of running' }
      if ( $self->_jobClass() );

    return $options;
}

sub _jobClass { }

sub jobify {
    my $self = shift;
    my @list = $self->_get_client_list();

    $self->debug("  Client list: @list");

    foreach (@list) {
        $self->debug(" - Processing Client: $_");
        $self->_jobify_client($_);
    }
}

sub _jobify_client {
    my $self      = shift;
    my $client_id = shift;

    assert($self);
    $self->error("Client ID must be greater than 0") unless ( $client_id > 0 );

    my $singleton = new Common::RSApp( clientID => $client_id );

    #    die( "Invalid client id '$client_id'")
    #	    unless( $singleton && Common::RSDB::ClientIDToDBName($client_id) );

    #	die( "Invalid client type" ) unless( $self->_isValidClientType($client_id) );

    Log->notice("Creating job for Client ID $client_id");

    my $job = $self->_getJobObject();
    $job->enqueue();
}

sub _getJobObject {
    my $self = shift;

    unless ( $self->_jobClass() ) {
        die "_jobClass must be overloaded to use jobify";
    }

    my %args = $self->_jobObjectArgs();
    Log->info( "Creating " . $self->_jobClass . " with params: " . Dumper \%args );

    my $pkg = $self->_jobClass();

    eval "require " . $pkg;
    die $@ if ($@);

    my $job = $pkg->new(%args);

    return $job;
}

sub _jobObjectArgs {
    my $self    = shift;
    my $options = $self->_get_options();
    my %args;

    foreach my $key ( keys %$options ) {
        $args{$key} = $self->param($key) if ( defined( $self->param($key) ) && $key ne 'jobify' );
    }

    return ( options => $options, args => \%args );
}

1;
