#!/usr/bin/perl

# Copyright 2011 BibLibre
#
# This file is part of Koha.
#
# Koha 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 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.

=head1 DESCRIPTION

This script build OAI-PMH sets (to be used by opac/oai.pl) according to sets
and mappings defined in Koha. It reads information from oai_sets and
oai_sets_mappings, and then fill table oai_sets_biblios with built infos.

=head1 USAGE

    build_oai_sets.pl [-h] [-v] [-r] [-i] [-l LENGTH [-o OFFSET]]
        -h          Print help message;
        -v          Be verbose
        -r          Truncate table oai_sets_biblios before inserting new rows
        -i          Embed items information, mandatory if you defined mappings
                    on item fields
        -l LENGTH   Process LENGTH biblios
        -o OFFSET   If LENGTH is defined, start processing from OFFSET

=cut

use Modern::Perl;
use MARC::Record;
use MARC::File::XML;
use List::MoreUtils qw( uniq );
use Getopt::Std     qw( getopts );

use Koha::Script;
use C4::Context;
use C4::Charset   qw( StripNonXmlChars );
use C4::OAI::Sets qw(
    AddOAISetsBiblios
    CalcOAISetsBiblio
    GetOAISet
    GetOAISetBySpec
    GetOAISets
    GetOAISetsMappings
    ModOAISetsBiblios
);
use Koha::Biblio::Metadata;

my %opts;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
my $go = getopts( 'vo:l:ihr', \%opts );

if ( !$go or $opts{h} ) {
    &print_usage;
    exit;
}

my $verbose     = $opts{v};
my $offset      = $opts{o};
my $length      = $opts{l};
my $embed_items = $opts{i};
my $reset       = $opts{r};

my $dbh = C4::Context->dbh;

# Get OAI sets mappings
my $mappings = GetOAISetsMappings;

# Get all biblionumbers and marcxml
print "Retrieving biblios... " if $verbose;
my $query = qq{
    SELECT biblionumber, metadata, 0 as "deleted"
    FROM biblio_metadata
    WHERE format='marcxml'
    AND  `schema` = ?
    UNION
    SELECT biblionumber, metadata, 1 as "deleted"
    FROM deletedbiblio_metadata
    WHERE format='marcxml'
    AND  `schema` = ?
    ORDER BY biblionumber ASC
};
if ($length) {
    $query .= "LIMIT $length";
    if ($offset) {
        $query .= " OFFSET $offset";
    }
}
my $sth = $dbh->prepare($query);
$sth->execute( C4::Context->preference('marcflavour'), C4::Context->preference('marcflavour') );
my $results = $sth->fetchall_arrayref( {} );
print "done.\n" if $verbose;

# Build lists of parents sets
my $sets = GetOAISets;
my $parentsets;
foreach my $set (@$sets) {
    my $setSpec = $set->{'spec'};
    while ( $setSpec =~ /^(.+):(.+)$/ ) {
        my $parent     = $1;
        my $parent_set = GetOAISetBySpec($parent);
        if ($parent_set) {
            push @{ $parentsets->{ $set->{'id'} } }, $parent_set->{'id'};
            $setSpec = $parent;
        } else {
            last;
        }
    }
}

my $num_biblios  = scalar @$results;
my $i            = 1;
my $sets_biblios = {};
foreach my $res (@$results) {
    my $biblionumber = $res->{'biblionumber'};
    my $marcxml      = $res->{'metadata'};
    if ( $verbose and $i % 1000 == 0 ) {
        my $percent = ( $i * 100 ) / $num_biblios;
        $percent = sprintf( "%.2f", $percent );
        say "Progression: $i/$num_biblios ($percent %)";
    }

    # The following lines are copied from GetMarcBiblio
    # We don't call GetMarcBiblio to avoid a sql query to be executed each time
    $marcxml = StripNonXmlChars($marcxml);
    MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
    my $record;
    eval { $record = MARC::Record::new_from_xml( $marcxml, "UTF-8", C4::Context->preference('marcflavour') ); };
    if ($@) {
        warn "(biblio $biblionumber) Error while creating record from marcxml: $@";
        next;
    }
    if ( $embed_items && !( $res->{'deleted'} ) ) {
        $record = Koha::Biblio::Metadata->record(
            {
                record       => $record,
                embed_items  => 1,
                biblionumber => $biblionumber,
            }
        );
    }

    my @biblio_sets = CalcOAISetsBiblio( $record, $mappings );
    foreach my $set_id (@biblio_sets) {
        push @{ $sets_biblios->{$set_id} }, $biblionumber;
        foreach my $parent_set_id ( @{ $parentsets->{$set_id} } ) {
            push @{ $sets_biblios->{$parent_set_id} }, $biblionumber;
        }
    }
    $i++;
}
say "Progression: done." if $verbose;

say "Summary:";
foreach my $set_id ( keys %$sets_biblios ) {
    $sets_biblios->{$set_id} = [ uniq @{ $sets_biblios->{$set_id} } ];
    my $set     = GetOAISet($set_id);
    my $setSpec = $set->{'spec'};
    say "Set '$setSpec': " . scalar( @{ $sets_biblios->{$set_id} } ) . " biblios";
}

print "Updating database... ";
if ($reset) {
    ModOAISetsBiblios( {} );
}
AddOAISetsBiblios($sets_biblios);
print "done.\n";

sub print_usage {
    print "build_oai_sets.pl: Build OAI-PMH sets, according to mappings defined in Koha\n";
    print "Usage: build_oai_sets.pl [-h] [-v] [-i] [-l LENGTH [-o OFFSET]]\n\n";
    print "\t-h\t\tPrint this help and exit\n";
    print "\t-v\t\t" . " Be verbose\n";
    print "\t-i\t\tEmbed items information, mandatory if you defined mappings on item fields\n";
    print "\t-l LENGTH\tProcess LENGTH biblios\n";
    print "\t-o OFFSET\tIf LENGTH is defined, start processing from OFFSET\n\n";
}
