package Mojolicious::Plugin::Config;
use Mojo::Base 'Mojolicious::Plugin';

require File::Basename;
require File::Spec;

use constant DEBUG => $ENV{MOJO_CONFIG_DEBUG} || 0;

# "Who are you, my warranty?!"
sub load {
  my ($self, $file, $conf, $app) = @_;

  # Debug
  $app->log->debug(qq/Reading config file "$file"./);

  # Slurp UTF-8 file
  open my $handle, "<:encoding(UTF-8)", $file
    or die qq/Couldn't open config file "$file": $!/;
  my $content = do { local $/; <$handle> };

  # Process
  return $self->parse($content, $file, $conf, $app);
}

sub parse {
  my ($self, $content, $file, $conf, $app) = @_;

  # Run Perl code
  no warnings;
  die qq/Couldn't parse config file "$file": $@/
    unless my $config = eval "sub app { \$app }; $content";
  die qq/Config file "$file" did not return a hashref.\n/
    unless ref $config && ref $config eq 'HASH';

  return $config;
}

sub register {
  my ($self, $app, $conf) = @_;

  # Plugin config
  $conf ||= {};

  # File
  my $file = $conf->{file} || $ENV{MOJO_CONFIG};
  unless ($file) {

    # Basename
    $file = File::Basename::basename($ENV{MOJO_EXE} || $0);

    # Remove .pl, .p6 and .t extentions
    $file =~ s/(?:\.p(?:l|6))|\.t$//i;

    # Default extension
    $file .= '.' . ($conf->{ext} || 'conf');
  }

  # Debug
  warn "CONFIG FILE $file\n" if DEBUG;

  # Mode specific config file
  my $mode;
  if ($file =~ /^(.*)\.([^\.]+)$/) {
    $mode = join '.', $1, $app->mode, $2;

    # Debug
    warn "MODE SPECIFIC CONFIG FILE $mode\n" if DEBUG;
  }

  # Absolute path
  $file = $app->home->rel_file($file)
    unless File::Spec->file_name_is_absolute($file);
  $mode = $app->home->rel_file($mode)
    if defined $mode && !File::Spec->file_name_is_absolute($mode);

  # Read config file
  my $config = {};
  if (-e $file) { $config = $self->load($file, $conf, $app) }

  # Check for default
  else {

    # All missing
    die qq/Config file "$file" missing, maybe you need to create it?\n/
      unless $conf->{default};

    # Debug
    $app->log->debug(qq/Config file "$file" missing, using default config./);
  }

  # Merge with mode specific config file
  if (defined $mode && -e $mode) {
    $config = {%$config, %{$self->load($mode, $conf, $app)}};
  }

  # Merge
  $config = {%{$conf->{default}}, %$config} if $conf->{default};

  # Add "config" helper
  $app->helper(
    config => sub {
      my $self = shift;
      return $config unless @_;
      return $config->{$_[0]};
    }
  );

  # Default
  $app->defaults(($conf->{stash_key} || 'config') => $config);

  return $config;
}

1;
__END__

=head1 NAME

Mojolicious::Plugin::Config - Perlish Configuration Plugin

=head1 SYNOPSIS

  # myapp.conf
  {
    foo       => "bar",
    music_dir => app->home->rel_dir('music')
  };

  # Mojolicious
  my $config = $self->plugin('config');

  # Mojolicious::Lite
  my $config = plugin 'config';

  # Reads myapp.conf by default and puts the parsed version into the stash
  my $config = $self->stash('config');

  # Everything can be customized with options
  my $config = plugin config => {
    file      => '/etc/myapp.stuff',
    stash_key => 'conf'
  };

=head1 DESCRIPTION

L<Mojolicious::Plugin::Config> is a Perl-ish configuration plugin.
The application object can be accessed via the C<app> helper.
You can extend the normal config file C<myapp.conf> with C<mode> specific
ones like C<myapp.$mode.conf>.

=head1 OPTIONS

=head2 C<default>

  # Mojolicious::Lite
  plugin config => {default => {foo => 'bar'}};

Default configuration.

=head2 C<ext>

  # Mojolicious::Lite
  plugin config => {ext => 'stuff'};

File extension of config file, defaults to C<conf>.

=head2 C<file>

  # Mojolicious::Lite
  plugin config => {file => 'myapp.conf'};
  plugin config => {file => '/etc/foo.stuff'};

Configuration file, defaults to the value of C<MOJO_CONFIG> or C<myapp.conf>
in the application home directory.

=head2 C<stash_key>

  # Mojolicious::Lite
  plugin config => {stash_key => 'conf'};

Configuration stash key.

=head1 HELPERS

=head2 C<config>

  <%= config 'something' %>
  <%= config->{something} %>

Access config values.

=head1 METHODS

L<Mojolicious::Plugin::Config> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 C<load>

  $plugin->load($file, $conf, $app);

Loads config file and passes the content to C<parse>.

  sub load {
    my ($self, $file, $conf, $app) = @_;
    ...
    return $self->parse($content, $file, $conf, $app);
  }

=head2 C<parse>

  $plugin->parse($content, $file, $conf, $app);

Parse config file.

  sub parse {
    my ($self, $content, $file, $conf, $app) = @_;
    ...
    return $hash;
  }

=head2 C<register>

  $plugin->register;

Register plugin in L<Mojolicious> application.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.

=cut