#!/usr/bin/perl -w

use strict;
use warnings;

use SReview::Config::Common;
use DBI;
use JSON;

=head1 NAME

sreview-import - Import an event schedule from an event's upstream

=head1 SYNOPSIS

sreview-import

=head1 DESCRIPTION

C<sreview-import> instantiates a C<schedule_format> schedule parser, and
makes sure that first, all events and talks in the parsed schedule exist
in the SReview database, and second, that all events and talks which
exist in the SReview database for a found event but which do I<not>
exist in the parsed schedule are marked as C<ignored>. Currently two
parsers are implemented: the C<penta> parser which parses the Pentabarf
XML format as implemented by Pentabarf itself, and the C<wafer> parser
which parses the Pentabarf XML format as implemented by the C<wafer>
event management software (which has a few differences that would
otherwise break C<sreview-import>'s idempotency).

The location of the event schedule is to be passed to the schedule
parser using the relevant option of that parser; most support the C<url>
option for a URL.

=head1 CONFIGURATION

C<sreview-import> considers the following configuration values:

=over

=cut

my $config = SReview::Config::Common::setup;

=item dbistring

The DBI string used to connect to the database. Required.

=cut

my $dbh = DBI->connect($config->get('dbistring'), '', '') or die 'Cannot connect to database!';

=item schedule_format

The format of the schedule. Required. Must be implemented as a subclass
of C<SReview::Schedule::Base>.

=cut

my $format = $config->get('schedule_format');
my $pclass = 'SReview::Schedule::' . ucfirst($format);

eval "require $pclass;" or die "Could not load schedule parser for format $format: $@\n";

=item schedule_options

Any options to be provided to the parser. Should be a hash; which keys
are supported depends on the relevant parser. See the parser's
documentation for details (e.g., L<SReview::Schedule::Penta> for the
C<penta> parser.

=cut

my $opts = $config->get('schedule_options');

my $parser = "$pclass"->new(%$opts);

$dbh->begin_work;

my $update_talk = $dbh->prepare("UPDATE talks SET room = ?, slug = ?, starttime = ?::timestamptz, endtime = ?::timestamptz, title = ?, subtitle = ?, track = ? WHERE id = ?");
my $search_room = $dbh->prepare("SELECT id FROM rooms WHERE name = ?");
my $add_room = $dbh->prepare("INSERT INTO rooms(name, altname, outputname) VALUES(?, ?, ?) RETURNING id");
my $update_room = $dbh->prepare("UPDATE rooms SET altname = ?, outputname = ? WHERE name = ?");
my $add_track = $dbh->prepare("INSERT INTO tracks(name, email, upstreamid) VALUES(?, ?, ?) RETURNING id");
my $update_track = $dbh->prepare("UPDATE track SET name = ?, email = ? WHERE upstreamid = ?");
my $add_talk = $dbh->prepare("INSERT INTO talks(room, slug, starttime, endtime, title, event, upstreamid, subtitle, track, description) VALUES(?, ?, ?::timestamptz, ?::timestamptz, ?, ?, ?, ?, ?, ?) RETURNING id");
my $set_flags = $dbh->prepare("UPDATE talks SET flags = COALESCE(flags::jsonb || ?::jsonb, ?::jsonb) WHERE id = ?");
my $search_speaker = $dbh->prepare("SELECT id FROM speakers WHERE upstreamid = ?");
my $update_speaker = $dbh->prepare("UPDATE speaker SET name = ?, email = ? WHERE upstreamid = ?");
my $add_speaker = $dbh->prepare("INSERT INTO speakers(email, name, upstreamid) VALUES(?, ?, ?) RETURNING id");
my $clear_speakers = $dbh->prepare("DELETE FROM speakers_talks WHERE talk = ?");
my $link_speaker = $dbh->prepare("INSERT INTO speakers_talks(speaker, talk) VALUES (?, ?) ON CONFLICT ON CONSTRAINT speakers_talks_pkey DO NOTHING");
my $ignore_talk = $dbh->prepare("UPDATE talks SET state='ignored' WHERE id = ? AND state <= 'waiting_for_files'");
my $unignore_talk = $dbh->prepare("UPDATE talks SET state='waiting_for_files' WHERE id = ? AND state = 'ignored'");
my $all_talks = $dbh->prepare("SELECT id, upstreamid FROM talks WHERE event = ?");
my $all_tracks = $dbh->prepare("SELECT id, upstreamid FROM tracks");

foreach my $event(@{$parser->events}) {
	my $st = $dbh->prepare("SELECT id FROM events WHERE name = ?");
	$st->execute($event->name) or die $!;
	if($st->rows > 1) {
		warn "Multiple events with name \"" . $event->name . "\", proceeding with first found one.\n";
	} elsif($st->rows == 0) {
		$st = $dbh->prepare("INSERT INTO events(name) VALUES(?) RETURNING id");
		$st->execute($event->name) or die $!;
	}
	my $row = $st->fetchrow_arrayref;
	my $eventid = $row->[0];

	my %known_talks;
	my %roomid;
	my %trackid;
	$all_talks->execute($eventid) or die $!;
	while($row = $all_talks->fetchrow_arrayref) {
		$known_talks{$row->[1]} = $row->[0];
	}
	$all_tracks->execute or die $!;
	while($row = $all_tracks->fetchrow_arrayref) {
		$trackid{$row->[1]} = $row->[0];
	}
	foreach my $talk(@{$event->talks}) {
		next if $talk->filtered;
		my $room;
		if(exists($roomid{$talk->room->name})) {
			$room = $roomid{$talk->room->name};
		} else {
			$search_room->execute($talk->room->name) or die $!;
			if($search_room->rows > 1) {
				warn "Multiple rooms with name \"" . $talk->room->name . "\", proceeding with first found one.\n";
			} elsif($search_room->rows == 0) {
				$add_room->execute($talk->room->name, $talk->room->altname, $talk->room->outputname) or die $!;
				$row = $add_room->fetchrow_arrayref;
			} else {
				$row = $search_room->fetchrow_arrayref;
				if(defined($talk->room->altname) || defined($talk->room->outputname)) {
					$update_room->execute($talk->room->altname, $talk->room->outputname, $talk->room->name);
				}
			}
			$room = $row->[0];
			$roomid{$talk->room->name} = $room;
		}
		my $track;
		if(exists($trackid{$talk->track->upstreamid})) {
			$track = $trackid{$talk->track->upstreamid};
			if(defined($talk->track->email)) {
				$update_track->execute($talk->track->name, $talk->track->email, $talk->track->upstreamid);
			}
		} else {
			$add_track->execute($talk->track->name, $talk->track->email, $talk->track->upstreamid) or die $!;
			$row = $add_track->fetchrow_arrayref;
			$track = $row->[0];
			$trackid{$talk->track->upstreamid} = $track;
		}
		my $talkid;
		if(exists($known_talks{$talk->upstreamid})) {
			$update_talk->execute($room, $talk->slug, $talk->starttime->datetime(' '), $talk->endtime->datetime(' '), $talk->title, $talk->subtitle, $track, $known_talks{$talk->upstreamid}) or die $!;
			$talkid = $known_talks{$talk->upstreamid};
			$unignore_talk->execute($known_talks{$talk->upstreamid});
			delete $known_talks{$talk->upstreamid};
		} else {
			$add_talk->execute($room, $talk->slug, $talk->starttime->datetime(' '), $talk->endtime->datetime(' '), $talk->title, $eventid, $talk->upstreamid, $talk->subtitle, $track, $talk->description) or die $!;
			$row = $add_talk->fetchrow_arrayref;
			$talkid = $row->[0];
		}
		if(defined($talk->flags)) {
			my $flags = $talk->flags;
			foreach my $flag(keys %$flags) {
				if($flags->{$flag}) {
					$flags->{$flag} = $JSON::true;
				} else {
					$flags->{$flag} = $JSON::false;
				}
			}
			$flags = encode_json($flags);
			$set_flags->execute($flags, $flags, $talkid);
		}
		$clear_speakers->execute($talkid) or die $!;
		foreach my $speaker(@{$talk->speakers}) {
			$search_speaker->execute($speaker->upstreamid) or die $!;
			if($search_speaker->rows > 1) {
				warn "Multiple speakers with upstream ID \"" . $speaker->upstreamid . "\", proceeding with first found one.\n";
			} elsif($search_speaker->rows == 0) {
				$add_speaker->execute($speaker->email, $speaker->name, $speaker->upstreamid) or die $!;
				$row = $add_speaker->fetchrow_arrayref;
			} else {
				$row = $search_speaker->fetchrow_arrayref;
				if(defined($speaker->email)) {
					$update_speaker->execute($speaker->name, $speaker->email, $speaker->upstreamid);
				}
			}
			my $speakerid = $row->[0];
			$link_speaker->execute($speakerid, $talkid) or die $!;
		}
	}
	foreach my $talkid(values %known_talks) {
		$ignore_talk->execute($talkid);
	}
}

$dbh->commit;
