package NOLookup::Brreg::DataLookup;

use warnings;
use strict;
use base qw(Class::Accessor::Chained); ## Provides a new() method
use POSIX qw(locale_h);
use NOLookup::Brreg::Entry;
use WWW::Mechanize;
use JSON;
use URI::Encode qw(uri_encode);
use Data::Dumper;
$Data::Dumper::Indent=1;

my $BRREG_TIMEOUT = 60; # secs (default is 180 secs but we want shorter time).

my $MAX_PAGES = 10; # max pages to fetch if no max is specified

my $BRREG     = "https://data.brreg.no/enhetsregisteret/api";
my $BRREG_UPD = "https://data.brreg.no/enhetsregisteret/api/oppdateringer";

my @module_methods = qw /

    lookup_orgno
    lookup_orgname
    lookup_reg_dates
    lookup_update_dates

    orgno_ok

    status
    error
    warning

    data
    size
    total_size
    next_page

    raw_json_decoded
   /;

# The new method and also the accessor methods
__PACKAGE__->mk_accessors( 
    @module_methods 
);



######## L o o k u p

sub lookup_orgno {
    my ($self, $orgno, $sok_underenheter) = @_;

    my $ENHETER="enheter";
    
    if ($sok_underenheter) {
        $ENHETER = "under" . $ENHETER;
    }
    
    # validate the organizaton number
    unless (orgno_ok($self, $orgno)) {
        return $self;
    }
    
    ##
    # Use the orgno as key to brreg.
    # Match is only found if number does exist.
    $self->size(0);
    $self->_lookup_org_entries("$BRREG/$ENHETER/$orgno");

}

sub lookup_orgname {
    my ($self, $orgname, $max_no_pages, $sok_underenheter) = @_;
    
    die "mandatory parameter 'orgname' not specified" unless ($orgname);

    my $ENHETER="enheter";
    
    if ($sok_underenheter) {
        $ENHETER = "under" . $ENHETER;
    }
    
    my $max_pg_to_fetch = $max_no_pages || $MAX_PAGES;
    
    # Use the orgname as filter in search to brreg
    # Note a limitation in the brreg API service that 
    # only names starting with $orgname is supported,
    # ref. the 'startswith' filter in the URL.
    # We should've liked a 'contains' instead. 

    my $onm_e = uri_encode($orgname, {encode_reserved => 1});

    my $BR = "$BRREG/$ENHETER/?size=100&navn=$onm_e";

    # First page is 0
    my $pcnt = 0;
    $self->size(0);
    $self->next_page(1); # force the first lookup

    while ($self->next_page && $pcnt < $max_pg_to_fetch) {
        #print STDERR "Page count is: $pcnt, fetching next page...\n";
        $self->_lookup_org_entries("$BR&page=$pcnt");
        ++$pcnt;
    }
}

sub lookup_reg_dates {
    my ($self, $from_date, $to_date, $max_no_pages, $sok_underenheter) = @_;

    my $max_pg_to_fetch = $max_no_pages || $MAX_PAGES;

    my $ENHETER="enheter";
    
    if ($sok_underenheter) {
        $ENHETER = "under" . $ENHETER;
    }
    
    # Use the from / to dates as filter for lookup on 
    # registration dates
    my $rdateF = "fraRegistreringsdatoEnhetsregisteret";
    my $rdateT = "tilRegistreringsdatoEnhetsregisteret";
    my $dateFilter;
    if ($from_date && !$to_date) {
        $dateFilter = "$rdateF=$from_date";
    } elsif (!$from_date && $to_date) {
        $dateFilter = "$rdateT=$to_date";
    } else {
        $dateFilter = "$rdateF=$from_date&$rdateT=$to_date";
    }
    
    my $BR = "$BRREG/$ENHETER/?size=100&$dateFilter";
      
    # First page is 0
    my $pcnt = 0;
    $self->size(0);
    $self->next_page(1); # force the first lookup

    # Fetch max 5 pages
    while ($self->next_page && $pcnt < $max_pg_to_fetch) {
        #print STDERR "Page count is: $pcnt, fetching next page...\n";
        $self->_lookup_org_entries("$BR&page=$pcnt");
        ++$pcnt;
    }
}


sub lookup_update_dates {
    my ($self, $from_date, $update_id, $max_no_pages, $sok_underenheter) = @_;

    my $max_pg_to_fetch = $max_no_pages || $MAX_PAGES;

    my $ENHETER="enheter";
    
    if ($sok_underenheter) {
        $ENHETER = "under" . $ENHETER;
    }
    
    # Use the from / to dates as filter for lookup on 
    # update date
    my $updFilter;
    $from_date .= "T00:00:00.000Z";
          
    if ($from_date && !$update_id) {
        $updFilter = "dato=$from_date";
    } elsif (!$from_date && $update_id) {
        $updFilter = "oppdateringsid=$update_id";
    } else {
        $updFilter = "dato=$from_date&oppdateringsid=$update_id";
    }
    
    my $BR = "$BRREG_UPD/$ENHETER/?size=100&$updFilter";
      
    # First page is 0
    my $pcnt = 0;
    $self->size(0);
    $self->next_page(1); # force the first lookup

    # Fetch max 5 pages
    while ($self->next_page && $pcnt < $max_pg_to_fetch) {
        #print STDERR "Page count is: $pcnt, fetching next page...\n";
        $self->_lookup_org_entries("$BR&page=$pcnt");
        ++$pcnt;
    }
}


sub orgno_ok {
    my ($self, $orgno) = @_;

    # remove any white spaces
    $orgno =~ s/\s+//g;

    if ($orgno eq "000000000") {
        $self->status("Invalid orgno: $orgno");
        $self->error(1);
        return 0;
    }
    unless ($orgno =~ m/\d{9}/) {
        $self->status("Organization number $orgno must be 9 digits");
        $self->error(1);
        return 0;
    }    
    return 1;
}

#####
# The lookup itself, internal method only.

sub _lookup_org_entries {
    my ($self, $URL) = @_;

    #print STDERR "URL: $URL\n";
    
    my $mech = WWW::Mechanize->new(
        timeout => $BRREG_TIMEOUT,
        autocheck => 0
        );
    
    my $resp = $mech->get($URL);

    unless ($mech->success) {
        $self->status($resp->status_line);
        $self->error(1);
        $self->next_page(undef);
        return $self;
    }

    my $json = decode_json($mech->text);

    if ($json) {
        $self->raw_json_decoded($json);
        
        # Map the json data structure to 
        # internal entry objects
        my $eo = NOLookup::Brreg::Entry->new;
        my $entries = $eo->map_json_entries($json);

        #print STDERR "Entries: ", Dumper $entries;
        
        # Collect any accumulated problems
        $self->status($eo->status) if ($eo->status);
        $self->warning($eo->warning) if ($eo->warning);
        $self->error($eo->error) if ($eo->error);

        # Save the found data
        if (@$entries) {
            push @{$self->{data}}, @$entries;
            $self->size($self->size + scalar @$entries);

            if ($eo->next_page) {
                # more pages are available.
                $self->next_page($eo->next_page);
            } else {
                # No more pages, all results are returned,
                # and we know the total_size (=totalElementssize)
                # Only if total_size is set, the user can assume
                # that all data has been fetched.
                $self->total_size($self->size);
                $self->next_page(undef);
            }
        } else {
            $self->next_page(undef);
        }
    }

    #print STDERR Dumper $self;
    
    return $self;
}

=pod

=encoding ISO-8859-1

=head1 NAME

NOLookup::Brreg::DataLookup - Lookup Brreg basic organization data from
the JSON formatted service offered by the Brreg data API via
https://data.brreg.no/enhetsregisteret/oppslag/enheter
https://data.brreg.no/enhetsregisteret/api/docs/index.html

(Brreg is a short name for 'Brønnysundregistrene, the Norwegian
Central Organization Registry).

=head1 DESCRIPTION

Use WWW::Mechanize and JSON to lookup data from the JSON API at brreg.
Use NOLookup::Brreg::Entry module to map the resulting json data
structure to a NOLookup::Brreg::Entry objects.

The timeout period $BRREG_TIMEOUT is used to trap and return the call
if Brreg lookup is hanging for some reason.

=head1 METHODS

The module provides the below methods.

=head3 lookup_orgno()

Lookup based on a complete organization number.

Returns an array of one single NOLookup::Brreg::Entry object in the data 
when a match is found.

=head3 lookup_orgname()

Lookup based on a complete or part of an organization name.

Returns 0 entries if none found.

Returns an array of NOLookup::Brreg::Entry objects in the data 
when matches are found.

A maximum of $max_no_pages pages are fetched, each of 100 entries.

=head3 lookup_reg_dates()

Lookup based on registration dates.
A from_date and/or a to_date can be specified.

The lookup will find registrations performed after midnight starting
the from date, and until midnight at the end of the to date.

Returns 0 entries if none found.

Returns an array of NOLookup::Brreg::Entry objects in the data 
when matches are found.

A maximum of $max_no_pages pages are fetched, each of 100 entries.


=head3 lookup_update_dates()

Lookup based on update date and update id.

The lookup will find updated organizations performed after midnight
starting on the from date. The minimum update id can also be set.

Returns 0 entries if none found.

Returns an array of NOLookup::Brreg::Entry objects in the data 
when matches are found.

A maximum of $max_no_pages pages are fetched, each of 100 entries.


=head3 orgno_ok()

Check if org. number basic syntax is OK, i.e. 9 digits.
Note that Brreg may reject the number due to stricter 
requirements, like checksum etc.

=head3 raw_json_decoded()

The raw JSON data structure, as returned by the JSON service,
and then decoded to a perl data structure with decode_json().

=head3 error()

Set if an error has occured. 
On errors, the lookup terminates, with more info 
in the status().

=head3 warning()

Set if a warning has occured.
On warnings, the lookup continues, but warnings
are accumulated in the status().

=head3 status()

Further description of error/warning situations.

=head3 data()

Found data, an array of NOLookup::Brreg::Entry data objects.

See doc. for NOLookup::Brreg::Entry for details.

=head3 size()

The number of objects in data().

=head3 total_size()

The total number of objects in data().

Only set if all data has been fetched.

Only if total_size is set, the user can assume
that all data has been fetched.

=head3 next_page()

If more pages (of 100 elements) can be fetched,
this method gives the URL to that page.

=head1 SUPPORT

For now, support questions should be sent to:

E<lt>(nospam)info(at)norid.noE<gt>

Please also see the README file in the distribution.

=head1 SEE ALSO

https://www.brreg.no
https://data.brreg.no/enhetsregisteret/oppslag/enheter

=head1 AUTHOR

Trond Haugen, E<lt>(nospam)info(at)norid.noE<gt>

=head1 COPYRIGHT

Copyright (c) 2017- Trond Haugen <(nospam)info(at)norid.no>.
All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

1;
