# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::ZPushNotify;

use 5.10.1;
use strict;
use warnings;

use base qw(Bugzilla::Extension);
our $VERSION = '1';

use Bugzilla;

#
# insert into the notifications table
#

sub _notify {
  my ($bug_id, $delta_ts) = @_;

  # because the push_notify table is hot, we defer updating it until the
  # request has completed.  this ensures we are outside the scope of any
  # transaction blocks.

  my $stash = Bugzilla->request_cache->{ZPushNotify_stash} ||= [];
  push @$stash, {bug_id => $bug_id, delta_ts => $delta_ts};
}

sub request_cleanup {
  my $stash = Bugzilla->request_cache->{ZPushNotify_stash} || return;

  my $dbh = Bugzilla->dbh;
  foreach my $rh (@$stash) {

    # using REPLACE INTO or INSERT .. ON DUPLICATE KEY UPDATE results in a
    # lock on the bugs table due to the FK.  this way is more verbose but
    # only locks the push_notify table.
    $dbh->bz_start_transaction();
    my ($id) = $dbh->selectrow_array("SELECT id FROM push_notify WHERE bug_id=?",
      undef, $rh->{bug_id});
    if ($id) {
      $dbh->do("UPDATE push_notify SET delta_ts=? WHERE id=?",
        undef, $rh->{delta_ts}, $id);
    }
    else {
      $dbh->do("INSERT INTO push_notify (bug_id, delta_ts) VALUES (?, ?)",
        undef, $rh->{bug_id}, $rh->{delta_ts});
    }
    $dbh->bz_commit_transaction();
  }
}

#
# object hooks
#

sub object_end_of_create {
  my ($self, $args) = @_;
  my $object = $args->{object};
  return unless Bugzilla->params->{enable_simple_push};
  return unless $object->isa('Bugzilla::Flag');
  _notify($object->bug->id, $object->creation_date);
}

sub flag_updated {
  my ($self, $args) = @_;
  my $flag      = $args->{flag};
  my $timestamp = $args->{timestamp};
  my $changes   = $args->{changes};
  return unless Bugzilla->params->{enable_simple_push};
  return unless scalar(keys %$changes);
  _notify($flag->bug->id, $timestamp);
}

sub flag_deleted {
  my ($self, $args) = @_;
  my $flag      = $args->{flag};
  my $timestamp = $args->{timestamp};
  return unless Bugzilla->params->{enable_simple_push};
  _notify($flag->bug->id, $timestamp);
}

sub attachment_end_of_update {
  my ($self, $args) = @_;
  return unless Bugzilla->params->{enable_simple_push};
  return unless scalar keys %{$args->{changes}};
  return unless my $object = $args->{object};
  _notify($object->bug->id, $object->modification_time);
}

sub object_before_delete {
  my ($self, $args) = @_;
  return unless Bugzilla->params->{enable_simple_push};
  return unless my $object = $args->{object};
  if ($object->isa('Bugzilla::Attachment')) {
    my $timestamp = Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
    _notify($object->bug->id, $timestamp);
  }
}

sub bug_end_of_update_delta_ts {
  my ($self, $args) = @_;
  return unless Bugzilla->params->{enable_simple_push};
  _notify($args->{bug_id}, $args->{timestamp});
}

sub bug_end_of_create {
  my ($self, $args) = @_;
  return unless Bugzilla->params->{enable_simple_push};
  _notify($args->{bug}->id, $args->{timestamp});
}

#
# schema / param
#

sub db_schema_abstract_schema {
  my ($self, $args) = @_;
  $args->{'schema'}->{'push_notify'} = {
    FIELDS => [
      id     => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1,},
      bug_id => {
        TYPE       => 'INT3',
        NOTNULL    => 1,
        REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'},
      },
      delta_ts => {TYPE => 'DATETIME', NOTNULL => 1,},
    ],
    INDEXES => [push_notify_idx => {FIELDS => ['bug_id'], TYPE => 'UNIQUE',},],
  };
}

sub config_modify_panels {
  my ($self, $args) = @_;
  push @{$args->{panels}->{advanced}->{params}},
    {name => 'enable_simple_push', type => 'b', default => 0,};
}

__PACKAGE__->NAME;
