package SL::XMLInvoice;

use strict;
use warnings;

use List::Util qw(first);
use XML::LibXML;

use SL::Locale::String qw(t8);
use SL::XMLInvoice::UBL;
use SL::XMLInvoice::CrossIndustryInvoice;
use SL::XMLInvoice::CrossIndustryDocument;

use constant RES_OK => 0;
use constant RES_XML_PARSING_FAILED => 1;
use constant RES_UNKNOWN_ROOT_NODE_TYPE => 2;

our @document_modules = qw(
  SL::XMLInvoice::CrossIndustryDocument
  SL::XMLInvoice::CrossIndustryInvoice
  SL::XMLInvoice::UBL
);

=head1 NAME

SL::XMLInvoice - Top level factory class for XML Invoice parsers.

=head1 DESCRIPTION

C<SL::XMLInvoice> is an abstraction layer allowing the application to pass any
supported XML invoice document for parsing, with C<SL::XMLInvoice> handling all
details from there: depending on its document type declaration, this class will
pick and instatiate the appropriate child class for parsing the document and
return an object exposing its data with the standardized structure outlined
below.

See L <SL::XMLInvoice::Base>
for details on the shared interface of the returned instances.

=head1 SYNOPSIS

  # $xml_data contains an XML document as flat scalar
  my $invoice_parser = SL::XMLInvoice->new($xml_data);

  # %metadata is a hash of document level metadata items
  my %metadata = %{$invoice_parser->metadata};

  # @items is an array of hashes, each representing a line
  # item on the bill
  my @items = @{$invoice_parser->items};

=cut

sub new {
  my ($class, $xml_data) = @_;
  my $self = {};

  $self->{message} = '';
  $self->{dom} = eval { XML::LibXML->load_xml(string => $xml_data) };

  if ( ! $self->{dom} ) {
    $self->{message} = t8("Parsing the XML data failed: #1", $xml_data);
    $self->{result} = RES_XML_PARSING_FAILED;
    return $self;
  }

  # Determine parser class to use
  my $type = first {
    $_->check_signature($self->{dom})
  } @document_modules;

  unless ( $type ) {
    $self->{result} = RES_UNKNOWN_ROOT_NODE_TYPE;

    my @supported = map { $_->supported } @document_modules;

    $self->{message} =  t8("Could not parse XML Invoice: unknown XML invoice type\nsupported: #1",
                           join ",\n", @supported
                        );
    return $self;
  }

  bless $self, $type;

  # Implementation sanity check for child classes: make sure they are aware of
  # the keys the hash returned by their metadata() method must contain.
  my @missing_data_keys = grep { !${$self->_data_keys}{$_} } @{ $self->data_keys };
  if ( scalar(@missing_data_keys) > 0 ) {
    die "Incomplete implementation: the following metadata keys appear to be missing from $type: " . join(", ", @missing_data_keys);
  }

  # Implementation sanity check for child classes: make sure they are aware of
  # the keys the hashes returned by their items() method must contain.
  my @missing_item_keys = ();
  foreach my $item_key ( @{$self->item_keys} ) {
    unless ( ${$self->_item_keys}{$item_key}) { push @missing_item_keys, $item_key; }
  }
  if ( scalar(@missing_item_keys) > 0 ) {
    die "Incomplete implementation: the following item keys appear to be missing from $type: " . join(", ", @missing_item_keys);
  }

  $self->parse_xml;

  # Ensure these methods are implemented in the child class
  $self->metadata;
  $self->items;

  $self->{result} = RES_OK;
  return $self;
}

1;

