The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package GSM::SMS::Config;
use strict;
use vars qw( $REVISION $VERSION @EXPORT );

use base qw( Exporter );
@EXPORT = qw( &setup &generate_config );

use Carp;
use Log::Agent;
use ExtUtils::MakeMaker qw( prompt );
use Config;
use File::Path;
use File::Spec;

$VERSION = "0.161";
$REVISION = '$Revision: 1.6 $';

=head1 NAME

GSM::SMS::Config - Implements a simple .ini style config.

=head1 DESCRIPTION

Implements a simple configuration format. Used mainly for the transports 
config file.

The configuration format is defined as follows

  ^#         := comment
  ^[.+]$     := start block
  ^.+=.+$    := var, value pair
  
The structure allows attribute (configuration) access as follows

  $_preferences->{$blockname}->{$var}=$value
  $blockname = ( 'default', <blocknames> }

=head1 METHODS

=over 4

=item B<new> - The constructor

  my $cfg = GSM::SMS::Config->new(
               -file => $config_file, # Optional otherwise take default config
			   -check => 1            # Optional, does a sanity check
			);

=cut

my $Config_defaults = {};
if ( $^O =~ /^MSWin/ ) {
	$Config_defaults->{'logdir'} = "C:\\gsmsms\\log";
	$Config_defaults->{'spool'} = "C:\\gsmsms\\spool";
	$Config_defaults->{'port'} = 'COM1';
	$Config_defaults->{'filetransport'} = "C:\\gsmsms\\filetransport";
} else {
	$Config_defaults->{'logdir'} = "/var/log/gsmsms";
	$Config_defaults->{'spool'} = "/var/spool/gsmsms";
	$Config_defaults->{'port'} = '/dev/ttyS0';
	$Config_defaults->{'filetransport'} = "/tmp/filetransport";
}

sub new {
	my ($proto, %arg) = @_;

	my $class = ref($proto) || $proto;

	my $self = {
			_config_file => $arg{-file},
			_check => $arg{-check}
	};

	bless $self, $class;

	$self->read_config( $self->{_config_file}, $self->{_check} );

	return $self;
}

=item B<setup> - run the setup script

=cut

sub setup {
	my $config = _config_wizard();

	if ($config) {
		require File::Spec;
		my $config_file = File::Spec->catfile(
			$Config{'installsitelib'}, "GSM", "SMS", "Config", "Default.pm"
		);		
		open OUT, ">$config_file" or die "$!: $config_file";
		print OUT $config;
		close OUT;
		print "Config saved.\n";
	}
}

=item B<save_default> - save this configuration as the default

=cut

sub save_default {
	my ($self) = @_;

}

=item B<read_config> - read a configuration file

=cut

sub read_config {
	my ($self, $filename, $check) = @_;
	my $config = {};
	
	# prepare default config
	my $hook = {};
	$config->{'default'} = [];
	push(@{$config->{'default'}}, $hook);
	
	# open config file
	local(*F);
	
	if ( $filename ) {
		
		logdbg "debug", "Reading config from a specific file ($filename)";
		
		open F, $filename or do 
							 { 
								logcroak "Could not open config file $filename ($!)"; 
								return undef 
							 };

		while (<F>) {
			chomp;					# loose trailing newline
			s/#.*//;				# loose comments
			s/^\s+//;				# loose leading white
			s/\s+$//;				# loose trailing white;
			next unless length;		# did we loose everything?
			
			# recon block or var/value pair ...
			if ( /\[(.+?)\]/ ) {
				$hook =  {} ;
				$config->{$1} = [];
				push( @{$config->{$1}}, $hook );
			} else {
				my ($var, $value) = split(/\s*=\s*/, $_, 2);
				$hook->{$var} = $value;
			}
		}
		close F if $filename;

	} else {

		logdbg "debug", "Getting default configuration.";
	
		require GSM::SMS::Config::Default;
		$config = $GSM::SMS::Config::Default::Config;
	}
	$self->{_config} = $config;

	return undef unless $check && $self->is_sane();

	return $config;
}

=item B<is_sane> - check if a configuration complies with some rules

=cut

sub is_sane {
	my ($self) = @_;

	my $config = $self->{_config};

	# we need a spool_dir for the transports ...
	unless (defined $self->get_value( undef, 'spooldir' ))
	{
		logcroak "insane config: 'spooldir' is mandatory in config file";
		return undef;
	}

	# we need a router object for the transports
	unless (defined $self->get_value( undef, 'router' ))
	{
		logcroak "insane config: 'router' is mandatory in config file";
		return undef;
	}

	# we also need to know here we want the logfiles ... although this can be
	# application specific
	unless (defined $self->get_value( undef, 'log' ))
	{
		logcroak "insane config: 'log' is mandatory in config file";
		return undef;
	}
	
	# we need at least one defined transport ...
	if (keys(%{$config}) <= 1)
	{ 
		logcroak "insane config: We need at least one defined transport";
		return undef;
	}

	return 1;
}

=item B<get_section_names> - Get an array of all the section names

=cut

sub get_section_names {
	my ($self) = @_;

	return keys %{$self->{_config}};
}


=item B<get_config> - get a specific config file section

  $config->get_config( 'default' );
  $config->get_config( 'Serial01' );

=cut

sub get_config {
	my ($self, $name) = @_;
	
	return ${$self->{_config}->{$name}}[0];
}

=item B<get_value> - get the config value for that section

  $value = $config->get_value($section, $name);

=cut

sub get_value {
	my ($self, $section, $name) = @_;

	$section = $section || 'default';

	return ${$self->{_config}->{$section}}[0]->{$name};
}

=item B<generate_config> - Generate a boilerplate config file

  perl -MGSM::SMS::Config -egenerate_config


This method prints out a boilerplate config file starting from the settings
in the default configuration.

Use this as a starting point to generate the configuration files for the examples.

=cut

sub generate_config {
	my $cfg = GSM::SMS::Config->new;		

print <<"EOT";
#### GSM::SMS configuration file
#
# Generated by GSM::SMS::Config ($REVISION)
#

EOT

	# default first
	my $default = $cfg->get_config( 'default' );
	foreach my $key (keys %{$default}) {
		print $key . " = " . $default->{$key} . "\n";
	}

	foreach my $section ($cfg->get_section_names) {
		if ( $section ne 'default' ) {
			print "\n[$section]\n";
			my $section_cfg = $cfg->get_config( $section );
			foreach my $key (keys %{$section_cfg}) {
				print "\t" . $key . " = " . $section_cfg->{$key} . "\n";
			}
		}
	}
}

=item B<_config_wizard> - The actual question asking mind boggling configurator

This method implements a console based configuration script for the package.
It will generate a site-wide config file that will be the default when instantiating a L<GSM::SMS::NBS> class.

=cut

sub _config_wizard {
	my $config = '';

	print <<EOT;
   
     ____ ____  __  __     ____  __  __ ____
    / ___/ ___||  \\/  |_ _/ ___||  \\/  / ___|
   | |  _\\___ \\| |\\/| (_|_)___ \\| |\\/| \\___ \\
   | |_| |___) | |  | |_ _ ___) | |  | |___) |
    \\____|____/|_|  |_(_|_)____/|_|  |_|____/

        Perl Modules For Smart Messaging


Welcome to the GSM::SMS package! Thanks for using it!

This configuration script gives you the possibility to configure the
default settings of the system. You can override these settings by
providing a configuration file when using the package.

EOT

	# Check for Device::SerialPort or Win32::SerialPort
	if ( $^O =~ /^MSWin/ ) {
		unless( eval "require Win32::SerialPort" ) {
			print "You don't have Win32::SerialPort installed!\nPlease install if you want to use a GSM modem\n\n";
		}
	} else {
		unless( eval "require Device::SerialPort" ) {
			print "You don't have Device::SerialPort installed!\nPlease install if you want to use a GSM modem\n\n";
		}
	}

	my $in;
	$in = prompt('Do you want to configurate the package? (y|n)', 'n');
	return if $in =~ /[nN]/;
	print "Let's ask some questions ...\n\n";

	# Configurate system wide settings

	# 1. logfiles
	my $path_default = $Config_defaults->{'logdir'};
	my $logdir = prompt("Where do you want the logfile(s)?", $path_default);
	_create_directory( $logdir ) unless (stat($logdir));

	# 2. Spool directory
	my $spool_default = $Config_defaults->{'spool'};
	my $spooldir = prompt("Where do you wish to keep the spool directory?", $spool_default);
	_create_directory( $spooldir ) unless (stat($spooldir));

	# 3. Test GSM number
	$in = prompt( "Mobile phone number to receive the tests on (leave empty for no sending)" );
	my $testgsm = $in;

	# create config file - generic part
	$config .= <<EOT;
package GSM::SMS::Config::Default;

\$Config = {	
	'default' => [
					{
					'router' => 'Simple',
					'spooldir' => '$spooldir',
					'log' => '$logdir',
					'testmsisdn' => '$testgsm'
					}
				 ],
EOT
	
	# 3. Transports
	print "\nWe're going to configure the transports\n\n";

	# 3.1 Serial
	$in = prompt( "Do you have a serial transport? (y/n)", "n");
	if ( $in =~ /y/i ) {
		$config .= _config_transport_serial();
	}

	# 3.2 NovelSoft
	print "\n";
	$in = prompt( "Do you have a NovelSoft account? (y/n)", "n" );
	if ( $in =~ /y/i ) {
		$config .= _config_transport_novelsoft();
	}

	# 3.3 MCube
	print "\n";
	$in = prompt( "Do you have an MCube account? (y/n)", "n" );
	if ( $in =~ /y/i ) {
		$config .= _config_transport_mcube();
	}

	# 3.4 File
	print "\n";
	$in = prompt( "Do you want the file test transport activated? (y/n)", "y" );
	if ( $in =~ /y/i ) {
		$config .= _config_transport_file();
	}

	$config .= <<EOT;

	};
1;
EOT

	return $config;
}

=item B<_config_transport_serial> - Gather config parameters for the serial transport

=cut

sub _config_transport_serial {
		my $config = '';
		my ($in, $name, $port, $csca, $pincode, $baud, $originator, $memory, $acl );
		do {
			do {
				$name = prompt( "What's the name?", "serial01" );
				$port = prompt( "What's the port?", $Config_defaults->{'port'} );
				$csca = prompt( "What's the CSCA?", "+32475161616" );
				$pincode = prompt( "What's the pincode?", "0000" );
				$baud = prompt( "What's the baudrate?", "9600" );
				$originator = prompt( "What's the originator?", "GSM::SMS" );
				$memory = prompt( "How big is the SMS memory?", "10" );
				$acl = prompt( "What's the access control list regex?", ".*" );

				print <<EOT;
Serial transport summary
------------------------

name:           $name
port:           $port
csca:           $csca
pincode:        $pincode
baudrate:       $baud
originator:     $originator
memory:         $memory
acl:            $acl

EOT
				$in = prompt( 'Is this correct? (y/n)', 'y' );
			} while ( $in =~ /n/i );
	
			$config .= <<EOT;

'$name' => [
			{
			'type' => 'Serial',	
			'name' => '$name',
			'pin_code' => '$pincode',
			'csca' => '$csca',
			'serial_port' => '$port',
			'baud_rate' => '$baud',
			'originator' => '$originator',
			'match' =>	'$acl',
			'memorylimit' => '$memory'
			}
		   ],
EOT

			print "Serial $name saved\n\n";
			$in = prompt( 'Do you want to configure another serial transport? (y/n)', 'n');
		} while ( $in =~ /y/i );

	return $config;
}

=item B<_config_transport_novelsoft> - Gather NovelSoft config info

=cut

sub _config_transport_novelsoft {
	my $config = '';
	my ($in, $user, $password, $proxy, $acl, $originator);
	do {
		$user = prompt( "What's your account name?" );
		$password = prompt( "What's your account password?" );
		$proxy = prompt( "Give url of http proxy, if any." );
		$originator = prompt( "What's the originator?", "GSM::SMS" );
		$acl = prompt( "What's the access control list regex?", ".*" );

		print <<EOT;
Novelsoft summary
-----------------

user:         $user
password:     $password
proxy:        $proxy
originator:   $originator
acl:          $acl
		
EOT

		$in = prompt( 'Is this correct? (y/n)', 'y' );
	} while ( $in =~ /n/i );
	$config .= <<EOT;
'NovelSoft' => [
				{
				'type' => 'NovelSoft',
				'name' => 'NovelSoft',
				'proxy' =>	'$proxy',
				'userid' => '$user',
				'password' => '$password',
				'originator' => '$originator',
				'smsserver' =>	'http://clients.sms-wap.com:80/cgi/csend.cgi',
				'backupsmsserver' => 'http://clients.sms-wap.com:80/cgi/csend.cgi',
				'match' =>	'$acl'
				}
			   ],

EOT
	return $config;
}

=item B<_config_transport_mcube> - Gather MCube specific config params

=cut

sub _config_transport_mcube {
	my $config = '';
	my ($in, $user, $password, $proxy, $acl, $originator);
	do {
		$user = prompt( "What's your account name?" );
		$password = prompt( "What's your account password?" );
		$proxy = prompt( "Give url of http proxy, if any." );
		$originator = prompt( "What's the originator?", "GSM::SMS" );
		$acl = prompt( "What's the access control list regex?", ".*" );

		print <<EOT;
MCube summary
-------------

user:         $user
password:     $password
proxy:        $proxy
originator:   $originator
acl:          $acl
		
EOT

		$in = prompt( 'Is this correct? (y/n)', 'y' );
	} while ( $in =~ /n/i );
	$config .= <<EOT;

'MCube' => [
			{
			'type' => 'MCube',
			'name' => 'MCube',
			'proxy' =>	'$proxy',
			'userid' => '$user',
			'password' =>	'$password',
			'originator' => '$originator',
			'smsserver' =>	'http://www.m3.be/scripts/httpgate1.cfm',
			'match' =>	'$acl'
			}
		   ],
EOT

	return $config;
}

=item B<_config_transport_file> - Configure the file transport

=cut

sub _config_transport_file {
	my $config = '';
	my ($in, $acl, $originator, $directory);

	do {
		$directory = prompt("Directory to put the files", 
								$Config_defaults->{'filetransport'});
		_create_directory( $directory ) unless (stat($directory));

		$originator = prompt( "What's the originator?", "GSM::SMS" );
		$acl = prompt( "What's the access control list regex?", "^555" );

		print <<EOT;
File transport summary
----------------------

out directory: $directory
originator:    $originator
acl:           $acl
		
EOT

		$in = prompt( 'Is this correct? (y/n)', 'y' );
	} while ( $in =~ /n/i );
	$config .= <<EOT;

'File' => [
			{
			'type' => 'File',
			'name' => 'File',
			'out_directory' => '$directory',
			'originator' => '$originator',
			'match' =>	'$acl'
			}
		  ],
EOT

	return $config;
}

=item B<_create_directory> - Creates a directory

This method will ask you if you want to create a directory, and creates it.

=cut

sub _create_directory {
	my ($dir) = @_;
	
	print "The directory <$dir> does not exist.\n";
	my $yn;
	do {
		$yn = prompt( "Do you want to create it? (y/n)", 'y');
	} while ( $yn !~ /[nNyY]/ );
	mkpath( $dir, 1, 0777) if ( $yn =~ /y/i );	
}

1; 

=head1 AUTHOR

Johan Van den Brande <johan@vandenbrande.com>

=cut