package OpenFrame::Config;

use strict;
use warnings::register;

use FileHandle;
use Scalar::Util;
use Data::Denter;
use Fcntl qw ( :flock );

our $VERSION = '1.00';

## where to look and order in which to look for config files
my $CONFIGFILES = [qw( ./.openframe.conf /etc/openframe.conf )];
my $CONFIG      = {};

=head1 NAME

OpenFrame::Config - Simple OpenFrame configuration

=head1 SYNOPSIS

  use OpenFrame::Config;
  my $config = OpenFrame::Config->new();
  my $value = $config->getKey('fred');
  $config->setKey('rainy', 'yes');

=head1 DESCRIPTION

This module is a simple configuration interview for OpenFrame. All
OpenFrame configuration will use this module.

[Insert more docs here]

=head1 METHODS

=head2 new

This is the constructor.

  my $config = OpenFrame::Config->new();

=cut

##
## constructor/fileloader
##
sub new {
  my $class = shift;
  my $file = shift;

  my $self;

  if (!$file) {
    foreach my $possibility (@$CONFIGFILES) {
      if ( -e $possibility ) {
	$file = $possibility;
	last;
      }
    }
  }

  if ($CONFIG->{$file}) {
    if ($CONFIG->{$file}->{_isMod}) {
      $CONFIG->{$file}->writeConfig;
    }
    $self = $CONFIG->{$file};
  } else {
    if (!$file) {
      if (warnings::enabled()  || $OpenFrame::DEBUG) {
	warnings::warn(sprintf("no configuration file found at %s", join(' ', @$CONFIGFILES)));
      }
      $self = {};
    } else {
      my $cfh = FileHandle->new( "<$file" );
      if ($cfh) {
	flock($cfh, LOCK_EX);
	local $/ = undef;
	$self = Undent( <$cfh> );
	$self->{_source} = $file;
	flock($cfh, LOCK_UN);
	$cfh->close();
      } else {
	warnings::warn("could not open config file $file ($!)") if (warnings::enabled || $OpenFrame::DEBUG);
	$self = {};
      }
    }

    $CONFIG->{$file} = $self;
  }


  bless $self, $class;

  if (!$self->isKey( 'VERSION' )) {
    $self->setKey( 'VERSION', $VERSION );
  }

  return $self;
}


=head2 writeConfig

The configuration file will be automatically written out when the
object goes out of scope. However, this can be forced by using the
writeConfig method:

  $config->writeConfig();

=cut

sub writeConfig {
  my $self = shift;
  return if (!$self->{_isMod});
  delete $self->{_isMod};
  my $file = -w $self->{_source} ? $self->{_source} : $CONFIGFILES->[0];
  if (exists $self->{_source}) {
    delete $self->{_source};
  }
  my $fh = FileHandle->new( ">$file" );
  if ( $fh ) {
    flock($fh, LOCK_EX);
    $fh->print( Denter( $self ) );
    flock($fh, LOCK_UN);
    $fh->close();
    $self->{_source} = $file;
    return 1;
  } else {
    warnings::warn("could not write config to $file ($!)") if (warnings::enabled || $OpenFrame::DEBUG);
    return undef;
  }
}


=head2 setKey

The setKey() method adds additional information to the
configuration. It takes a key and a value:

  $config->setKey('rainy', 'yes');

=cut

sub setKey {
  my $self = shift;
  my $key  = shift;
  my $val  = shift;
  $self->{_isMod} = 1;
  $self->{$key} = $val;
}


=head2 isKey

The isKey() method returns whether a key is part of the configuration,
much like the exists() function for Perl hashes. It takes a key:

  my $is_it_rainy = $config->isKey('rainy');

=cut

sub isKey {
  my $self = shift;
  return 1 if exists $self->{$_[0]};
}


=head2 getKey

The getKey() method returns information from the configuration. It
takes a key:

  my $value = $config->getKey('rainy');

=cut

sub getKey {
  my $self = shift;

  my $is = Scalar::Util::reftype($self->{$_[0]});

  warnings::warn("[config] value is a $is") if (warnings::enabled || $OpenFrame::DEBUG);

  if (!$is) {
    return $self->{$_[0]}
  } elsif( $is eq 'HASH') {
    my %hash = %{$self->{$_[0]}};
    return {%hash};
  } elsif ($is eq 'ARRAY') {
    my @array = @{$self->{$_[0]}};
    return [@array];
  }
}


=head2 deleteKey

The deleteKey() method deletes information from the configuration. It
takes a key:

  $config->deleteKey('rainy');

=cut

sub deleteKey {
  my $self = shift;
  my $key  = shift;
  $self->{_isMod} = 1;
  delete $self->{$key};
}


=head2 sourceFile

The sourceFile() method returns the filename that the configuration is
saved in.

  my $filename = $config->sourceFile();

=cut

sub sourceFile {
  my $self = shift;
  return $self->{_source};
}

sub DESTROY {
  my $self = shift;
  $self->writeConfig();
}

1;

=head1 AUTHOR

James A. Duncan <jduncan@fotango.com>,
Leon Brocard <leon@fotango.com>

=head1 COPYRIGHT

Copyright (C) 2001, Fotango Ltd.

This module is free software; you can redistribute it or modify it
under the same terms as Perl itself.

=cut
