The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Bot::Cobalt::Lang;
our $VERSION = '0.016002';

use 5.12.1;
use strictures 1;

use Carp;
use Moo;

use Bot::Cobalt::Common qw/:types/;
use Bot::Cobalt::Serializer;

use File::Spec;
use Try::Tiny;

## Configurable:
has 'lang_dir' => (
  lazy => 1,

  is  => 'ro',
  isa => Str,
  
  predicate => 'has_lang_dir',
  writer    => '_set_lang_dir',
);

has 'lang' => (
  required => 1,
  
  is  => 'rwp',
  isa => Str,
);

has 'absolute_path' => (
  lazy => 1,

  is  => 'ro',  
  isa => Str,

  predicate => 'has_absolute_path',
  writer    => '_set_absolute_path',
);

has 'use_core' => (
  is  => 'rwp',
  isa => Bool,
  
  default => sub { 0 },
);

has 'use_core_only' => (
  is  => 'rwp',
  isa => Bool,
  
  default => sub { 0 },

  trigger => sub {
    my ($self, $val) = @_;

    $self->_set_use_core(1) if $val;
  },
);

## Public:
has 'rpls' => (
  lazy => 1,
  
  is  => 'rwp',
  isa => HashRef,
  
  builder => '_build_rpls_hash',
);

has 'spec' => (
  is  => 'rwp',
  isa => Int,

  default => sub { 0 },
);

## Private:
has '_full_lang_path' => (
  lazy => 1,
  
  is  => 'ro',
  isa => Str,
  
  builder => '_build_full_lang_path',
);

has '_core_set' => (
  lazy => 1,
  
  is  => 'ro',
  isa => HashRef,
  
  builder => '_build_core_set',
);


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

  unless ( $self->use_core_only ) {
    die "Need either a lang_dir or an absolute path"
      unless $self->has_absolute_path or $self->has_lang_dir;
  }

  ## Load/validate rpls() at construction time.
  $self->rpls;
}

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

  return $self->absolute_path if $self->has_absolute_path;
  
  my $file_path = $self->lang . ".yml" ;

  File::Spec->catfile(
    File::Spec->splitdir($self->lang_dir),
    $file_path
  )
}

sub _build_core_set {
  my ($self) = @_;
  
  my $core_set_yaml =
    $Bot::Cobalt::Lang::BUILTIN_SET 
     //= do { local $/; <DATA> } ;
  
  my $serializer = Bot::Cobalt::Serializer->new;
  
  $serializer->thaw( $core_set_yaml )
}

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

  my $rpl_hash;

  ## Core (built-in) load; shallow copy:
  $rpl_hash = \%{ $self->_core_set->{RPL} }
    if $self->use_core;

  if ( $self->use_core_only ) {
    $self->_set_spec( $self->_core_set->{SPEC} );
    return $rpl_hash
  }

  my $serializer = Bot::Cobalt::Serializer->new;
  
  my $croakable;
  
  my $loaded_set = try {
    $serializer->readfile( $self->_full_lang_path )
  } catch {
    ## croak() by default.
    ## If this is a core set load, return empty hash.
    if ( !$self->use_core ) {
      $croakable = "readfile() failure for ". $self->lang().
        "(". $self->_full_lang_path(). "): ".
        $_ ;
      0
    } else {
      carp "Language load failure for ".$self->lang.": $_\n";
      { RPL => {} }
    }
  } or croak $croakable;

  if ( $self->use_core ) {
    my $rev_for_loaded  = $loaded_set->{SPEC}      // 0;
    my $rev_for_builtin = $self->_core_set->{SPEC} // 0;

    if ($rev_for_builtin > $rev_for_loaded) {
      warn("Appear to be loading a core language set, but the internal",
        " set has a higher SPEC number than the loaded set",
        " ($rev_for_builtin > $rev_for_loaded).\n",
        " You may want to update language sets.\n",
      );
    }

  }
  
  my $loaded_rpl_hash = $loaded_set->{RPL};

  confess "Language set loaded but no RPL hash found"
    unless ref $loaded_rpl_hash eq 'HASH';

  $self->_set_spec( $loaded_set->{SPEC} );
  
  @{$rpl_hash}{ keys(%$loaded_rpl_hash) }
    = @{$loaded_set->{RPL}}{ keys(%$loaded_rpl_hash) } ;

  $rpl_hash
}

### this module will have a langset appended at build-time
1;
__DATA__