# 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::TrackingFlags::Flag::Visibility;

use base qw(Bugzilla::Object);

use 5.10.1;
use strict;
use warnings;

use Bugzilla::Error;
use Bugzilla::Product;
use Bugzilla::Component;
use Scalar::Util qw(blessed);

###############################
####    Initialization     ####
###############################

use constant DB_TABLE => 'tracking_flags_visibility';

use constant DB_COLUMNS => qw(
  id
  tracking_flag_id
  product_id
  component_id
);

use constant LIST_ORDER => 'id';

use constant UPDATE_COLUMNS => ();    # immutable

use constant VALIDATORS => {
  tracking_flag_id => \&_check_tracking_flag,
  product_id       => \&_check_product,
  component_id     => \&_check_component,
};

###############################
####      Methods          ####
###############################

sub match {
  my $class    = shift;
  my ($params) = @_;
  my $dbh      = Bugzilla->dbh;

  # Allow matching component and product by name
  # (in addition to matching by ID).
  # Borrowed from Bugzilla::Bug::match
  my %translate_fields
    = (product => 'Bugzilla::Product', component => 'Bugzilla::Component',);

  foreach my $field (keys %translate_fields) {
    my @ids;

    # Convert names to ids. We use "exists" everywhere since people can
    # legally specify "undef" to mean IS NULL
    if (exists $params->{$field}) {
      my $names   = $params->{$field};
      my $type    = $translate_fields{$field};
      my $objects = Bugzilla::Object::match($type, {name => $names});
      push(@ids, map { $_->id } @$objects);
    }

    # You can also specify ids directly as arguments to this function,
    # so include them in the list if they have been specified.
    if (exists $params->{"${field}_id"}) {
      my $current_ids = $params->{"${field}_id"};
      my @id_array = ref $current_ids ? @$current_ids : ($current_ids);
      push(@ids, @id_array);
    }

    # We do this "or" instead of a "scalar(@ids)" to handle the case
    # when people passed only invalid object names. Otherwise we'd
    # end up with a SUPER::match call with zero criteria (which dies).
    if (exists $params->{$field} or exists $params->{"${field}_id"}) {
      delete $params->{$field};
      $params->{"${field}_id"} = scalar(@ids) == 1 ? [$ids[0]] : \@ids;
    }
  }

  # If we aren't matching on the product, use the default matching code
  if (!exists $params->{product_id}) {
    return $class->SUPER::match(@_);
  }

  my @criteria = ("1=1");

  if ($params->{product_id}) {
    push(@criteria, $dbh->sql_in('product_id', $params->{'product_id'}));
    if ($params->{component_id}) {
      my $component_id = $params->{component_id};
      push(@criteria,
            "("
          . $dbh->sql_in('component_id', $params->{'component_id'})
          . " OR component_id IS NULL)");
    }
  }

  my $where = join(' AND ', @criteria);
  my $flag_ids = $dbh->selectcol_arrayref(
    "SELECT id
                                               FROM tracking_flags_visibility
                                              WHERE $where"
  );

  return Bugzilla::Extension::TrackingFlags::Flag::Visibility->new_from_list(
    $flag_ids);
}

###############################
####      Validators       ####
###############################

sub _check_tracking_flag {
  my ($invocant, $flag) = @_;
  if (blessed $flag) {
    return $flag->flag_id;
  }
  $flag
    = Bugzilla::Extension::TrackingFlags::Flag->new($flag)
    || ThrowCodeError('tracking_flags_invalid_param',
    {name => 'flag_id', value => $flag});
  return $flag->flag_id;
}

sub _check_product {
  my ($invocant, $product) = @_;
  if (blessed $product) {
    return $product->id;
  }
  $product
    = Bugzilla::Product->new($product)
    || ThrowCodeError('tracking_flags_invalid_param',
    {name => 'product_id', value => $product});
  return $product->id;
}

sub _check_component {
  my ($invocant, $component) = @_;
  return undef unless defined $component;
  if (blessed $component) {
    return $component->id;
  }
  $component
    = Bugzilla::Component->new($component)
    || ThrowCodeError('tracking_flags_invalid_param',
    {name => 'component_id', value => $component});
  return $component->id;
}

###############################
####      Accessors        ####
###############################

sub tracking_flag_id { return $_[0]->{'tracking_flag_id'}; }
sub product_id       { return $_[0]->{'product_id'}; }
sub component_id     { return $_[0]->{'component_id'}; }

sub tracking_flag {
  my ($self) = @_;
  $self->{'tracking_flag'}
    ||= Bugzilla::Extension::TrackingFlags::Flag->new($self->tracking_flag_id);
  return $self->{'tracking_flag'};
}

sub product {
  my ($self) = @_;
  $self->{'product'} ||= Bugzilla::Product->new($self->product_id);
  return $self->{'product'};
}

sub component {
  my ($self) = @_;
  return undef unless $self->component_id;
  $self->{'component'} ||= Bugzilla::Component->new($self->component_id);
  return $self->{'component'};
}

1;
