package SL::Helper::QrBillParser;

use strict;
use warnings;

use SL::Helper::QrBillFunctions qw(
  get_street_name_from_address_line
  get_building_number_from_address_line
  get_postal_code_from_address_line
  get_town_name_from_address_line
);

use Rose::Object::MakeMethods::Generic(
  scalar                  => [ qw(is_valid error raw_data) ],
  'scalar --get_set_init' => [ qw(spec) ],
);

our $VERSION = '0.01';

use constant {
  REGEX_QRTYPE               => qr{^SPC$},
  REGEX_VERSION              => qr{^0200$},
  REGEX_CODING               => qr{^1$},
  REGEX_IBAN                 => qr{^(?:CH|LI)[0-9a-zA-Z]{19}$},
  REGEX_ADDRESS_TYPE         => qr{^[KS]$},
  REGEX_NAME                 => qr{^.{1,70}$},
  REGEX_ADDRESS_LINE         => qr{^.{0,70}$},
  REGEX_POSTAL_CODE          => qr{^.{0,16}$},
  REGEX_TOWN                 => qr{^.{0,35}$},
  REGEX_COUNTRY              => qr{^[A-Za-z]{2}$},
  REGEX_AMOUNT               => qr{^(?:(?:0|[1-9][0-9]{0,8})\.[0-9]{2})?$},
  REGEX_CURRENCY             => qr{^(?:CHF|EUR)$},
  REGEX_REFERENCE_TYPE       => qr{^(?:QRR|SCOR|NON)$},
  REGEX_REFERENCE            => qr{^.{0,27}$},
  REGEX_UNSTRUCTURED_MESSAGE => qr{^.{0,140}$},
  REGEX_TRAILER              => qr{^EPD$},
  REGEX_BILL_INFORMATION     => qr{^.{0,140}$},
  REGEX_ALTERNATIVE_SCHEME_PARAMETER      => qr{^.{0,100}$},
};

sub new {
  my $class = shift;

  my $self = bless {}, $class;

  $self->init(@_);

  return $self;
}

sub init {
  my $self = shift;
  my ($qrtext) = @_;

  my @lines = split /(?:\n|\r\n)/, $qrtext;

  $self->is_valid(1);
  $self->error('');
  $self->raw_data($qrtext);

  for my $section ( @{$self->spec} ) {
    for my $field ( @{$section->{fields}} ) {
      my $value = $lines[$field->{line_number}];

      if (!test_value($value, $field->{test}, $field->{status})) {
        $self->error("Test failed: Section: '$section->{section}' Field: '$field->{name}' Value: '$value'");
        $self->is_valid(0);
        last;
      }

      $self->{$section->{section}} = {} if (!$self->{$section->{section}});
      $self->{$section->{section}}->{$field->{name}} = $value;
    }
    last if $self->error;
  }
}

sub get_creditor_field {
  my $self = shift;
  my ($structured_field, $extract_field, $extract_fn) = @_;

  if ($self->{creditor}->{address_type} eq 'S') {
    return $self->{creditor}->{$structured_field};
  }
  # extract
  my $r = $extract_fn->($self->{creditor}->{$extract_field});

  return $r // '';
}

sub get_creditor_street_name {
  return shift->get_creditor_field(
    'street_or_address_line_1',
    'street_or_address_line_1',
    \&get_street_name_from_address_line
  );
}

sub get_creditor_building_number {
  return shift->get_creditor_field(
    'building_number_or_address_line_2',
    'street_or_address_line_1',
    \&get_building_number_from_address_line
  );
}

sub get_creditor_post_code {
  return shift->get_creditor_field(
    'postal_code',
    'building_number_or_address_line_2',
    \&get_postal_code_from_address_line
  );
}

sub get_creditor_town_name {
  return shift->get_creditor_field(
    'town',
    'building_number_or_address_line_2',
    \&get_town_name_from_address_line
  );
}

sub init_spec {
  [
    {
      section => 'header',
      fields  => [
        {
          name        => 'qrtype',
          line_number => 0,
          test        => REGEX_QRTYPE,
          status      => 'M'
        },
        {
          name        => 'version',
          line_number => 1,
          test        => REGEX_VERSION,
          status      => 'M'
        },
        {
          name        => 'coding',
          line_number => 2,
          test        => REGEX_CODING,
          status      => 'M'
        }
      ]
    },
    {
      section => 'creditor_information',
      fields  => [
        {
          name        => 'iban',
          line_number => 3,
          test        => REGEX_IBAN,
          status      => 'M'
        }
      ]
    },
    {
      section => 'creditor',
      fields  => [
        {
          name        => 'address_type',
          line_number => 4,
          test        => REGEX_ADDRESS_TYPE,
          status      => 'M',
        },
        {
          name        => 'name',
          line_number => 5,
          test        => REGEX_NAME,
          status      => 'M',
        },
        {
          name        => 'street_or_address_line_1',
          line_number => 6,
          test        => REGEX_ADDRESS_LINE,
          status      => 'O'
        },
        {
          name        => 'building_number_or_address_line_2',
          line_number => 7,
          test        => REGEX_ADDRESS_LINE,
          status      => 'O'
        },
        {
          name        => 'postal_code',
          line_number => 8,
          test        => REGEX_POSTAL_CODE,
          status      => 'D'
        },
        {
          name        => 'town',
          line_number => 9,
          test        => REGEX_TOWN,
          status      => 'D'
        },
        {
          name        => 'country',
          line_number => 10,
          test        => REGEX_COUNTRY,
          status      => 'M'
        }
      ]
    },
    {
      section => 'ultimate_creditor',
      fields  => [
        {
          name        => 'address_type',
          line_number => 11,
          test        => REGEX_ADDRESS_TYPE,
          status      => 'X'
        },
        {
          name        => 'name',
          line_number => 12,
          test        => REGEX_NAME,
          status      => 'X'
        },
        {
          name        => 'street_or_address_line_1',
          line_number => 13,
          test        => REGEX_ADDRESS_LINE,
          status      => 'X'
        },
        {
          name        => 'building_number_or_address_line_2',
          line_number => 14,
          test        => REGEX_ADDRESS_LINE,
          status      => 'X'
        },
        {
          name        => 'postal_code',
          line_number => 15,
          test        => REGEX_POSTAL_CODE,
          status      => 'X'
        },
        {
          name        => 'town',
          line_number => 16,
          test        => REGEX_TOWN,
          status      => 'X'
        },
        {
          name        => 'country',
          line_number => 17,
          test        => REGEX_COUNTRY,
          status      => 'X'
        }
      ]
    },
    {
      section => 'payment_amount_information',
      fields  => [
        {
          name        => 'amount',
          line_number => 18,
          test        => REGEX_AMOUNT,
          status      => 'O'
        },
        {
          name        => 'currency',
          line_number => 19,
          test        => REGEX_CURRENCY,
          status      => 'M'
        }
      ]
    },
    {
      section => 'ultimate_debtor',
      fields  => [
        {
          name        => 'address_type',
          line_number => 20,
          test        => REGEX_ADDRESS_TYPE,
          status      => 'D'
        },
        {
          name        => 'name',
          line_number => 21,
          test        => REGEX_NAME,
          status      => 'D'
        },
        {
          name        => 'street_or_address_line_1',
          line_number => 22,
          test        => REGEX_ADDRESS_LINE,
          status      => 'O'
        },
        {
          name        => 'building_number_or_address_line_2',
          line_number => 23,
          test        => REGEX_ADDRESS_LINE,
          status      => 'O'
        },
        {
          name        => 'postal_code',
          line_number => 24,
          test        => REGEX_POSTAL_CODE,
          status      => 'D'
        },
        {
          name        => 'town',
          line_number => 25,
          test        => REGEX_TOWN,
          status      => 'D'
        },
        {
          name        => 'country',
          line_number => 26,
          test        => REGEX_COUNTRY,
          status      => 'D'
        }
      ]
    },
    {
      section => 'payment_reference',
      fields  => [
        {
          name        => 'reference_type',
          line_number => 27,
          test        => REGEX_REFERENCE_TYPE,
          status      => 'M'
        },
        {
          name        => 'reference',
          line_number => 28,
          test        => REGEX_REFERENCE,
          status      => 'D'
        }
      ]
    },
    {
      section => 'additional_information',
      fields  => [
        {
          name        => 'unstructured_message',
          line_number => 29,
          test        => REGEX_UNSTRUCTURED_MESSAGE,
          status      => 'O'
        },
        {
          name        => 'trailer',
          line_number => 30,
          test        => REGEX_TRAILER,
          status      => 'M'
        },
        {
          name        => 'bill_information',
          line_number => 31,
          test        => REGEX_BILL_INFORMATION,
          status      => 'A'
        }
      ]
    },
    {
      section => 'alternative_scheme',
      fields  => [
        {
          name        => 'alternative_scheme_parameter1',
          line_number => 32,
          test        => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
          status      => 'A'
        },
        {
          name        => 'alternative_scheme_parameter2',
          line_number => 33,
          test        => REGEX_ALTERNATIVE_SCHEME_PARAMETER,
          status      => 'A'
        }
      ]
    }
  ];
}

### helper

sub test_value {
  my ($value, $test, $status) = @_;

  # mandatory fields must have a content
  return 0 if $status eq 'M' && length $value <= 0;

  # optional fields can be empty
  return 1 if $status eq 'O' && length $value == 0;

  # dependent fields can be empty
  return 1 if $status eq 'D' && length $value == 0;

  # "do not fill" fields cannot have a content
  if ($status eq 'X') {
    return 1 if ($value eq '');
    return 0;
  }

  # additional fields can be undefined
  if ($status eq 'A') {
    return 1 if !defined($value);
    return 0 if $value !~ $test;
    return 1;
  }

  return 0 if !defined($value);
  return 0 if $value !~ $test;
  return 1;
}

1;

__END__

=pod

=encoding utf8

=head1 NAME

SL::Helper::QrBillParser - Helper for parsing QR bill data

=head1 SYNOPSIS

  use SL::Helper::QrBillParser;

  my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});

  my $valid = $qr_obj->is_valid;
  my $error_message = $qr_obj->error;
  my $qrtext = $qr_obj->raw_data;

  # data for remittance information
  my $reference = $qr_obj->{payment_reference}->{reference};
  my $unstructured_message = $qr_obj->{additional_information}->{unstructured_message}

  # set currency and amount
  my $currency = $qr_obj->{payment_amount_information}->{currency};
  my $amount = $qr_obj->{payment_amount_information}->{amount}

  # set creditor name and address from qr data
  my $creditor_name = $qr_obj->{creditor}->{name};
  my $creditor_street_name = $qr_obj->get_creditor_street_name;
  my $creditor_building_number = $qr_obj->get_creditor_building_number;
  my $creditor_postal_code = $qr_obj->get_creditor_post_code;
  my $creditor_town_name = $qr_obj->get_creditor_town_name;
  my $creditor_country = $qr_obj->{creditor}->{country}

  # set creditor iban
  my $creditor_iban = $qr_obj->{creditor_information}->{iban};

=head1 DESCRIPTION

This is simple helper to parse swiss qr bill data from a string into an object.

Some methods are provided to easily retrieve the creditor address data.

=head1 FUNCTIONS

=over 4

=item C<new>

  my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});

Creates a new object from the qr bill data string.

=item C<is_valid>

  my $valid = $qr_obj->is_valid;

Returns true if the qr bill data is valid.

=item C<error>

  my $error_message = $qr_obj->error;

Returns the error message if the qr bill data is invalid.

=item C<raw_data>

  my $qrtext = $qr_obj->raw_data;

Returns the raw qr bill data string.

=item C<get_creditor_street_name>

  my $creditor_street_name = $qr_obj->get_creditor_street_name;

Returns the creditor street name.

=item C<get_creditor_building_number>

  my $creditor_building_number = $qr_obj->get_creditor_building_number;

Returns the creditor building number.

=item C<get_creditor_post_code>

  my $creditor_postal_code = $qr_obj->get_creditor_post_code;

Returns the creditor postal code.

=item C<get_creditor_town_name>

  my $creditor_town_name = $qr_obj->get_creditor_town_name;

Returns the creditor town name.

=back

=head1 TESTS

Tests for functions see t/helper/qrbill_parser.t.

Run: C<t/test.pl t/helper/qrbill_parser.t>

=head1 LIMITATIONS

Basic validation is performed based on the status code and regular expressions.
However complete checks of dependent fields would require more elaborate logic.

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Cem Aydin E<lt>cem.aydin@revamp-it.chE<gt>
Steven Schubiger E<lt>stsc@refcnt.orgE<gt>

=cut
