# 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.

# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package Scmbug::Daemon::Bugzilla;

@ISA = qw( Scmbug::Daemon::Bugtracker );

use strict;
use Data::Dumper;
use Scmbug::Daemon::Bugtracker;
use Scmbug::Common;
use Scmbug::VDD;

my $bugzilla_bug_status_map = {
  "ASSIGNED" => {
    name   => "ASSIGNED",
    active => 1
  },
  "REOPENED" => {
    name   => "REOPENED",
    active => 1
  }
};

sub is_version_up_to_2_16 {
  my $self = shift;

  if ( $self->{version_type} eq "up_to_2_16" ) {
    return 1;
  } else {
    return 0;
  }
}

sub is_version_up_to_2_18 {
  my $self = shift;

  if ( $self->{version_type} eq "up_to_2_18" ) {
    return 1;
  } else {
    return 0;
  }
}

sub is_version_up_to_2_20 {
  my $self = shift;

  if ( $self->{version_type} eq "up_to_2_20" ) {
    return 1;
  } else {
    return 0;
  }
}

sub is_version_up_to_2_22 {
  my $self = shift;

  if ( $self->{version_type} eq "up_to_2_22" ) {
    return 1;
  } else {
    return 0;
  }
}

# There are various ways to talk to Bugzilla. The Bugzilla API as
# provided by the Bugzilla source using the globals.pl file is used
# here.
#
# This requires local access to the bugzilla localconfig file
#
sub init_specific {
  my $self            = shift;
  my $package_name    = "";
  my $package_version = "";

  # Include the bugzilla libraries
  push @INC, $self->installation_directory();
  chdir $self->installation_directory();

  #
  # Require DBI version 1.50 on Windows. Earlier versions have
  # threading issues.
  #
  if ($PRODUCT_ON_WINDOWS) {
    $package_name    = "DBI";
    $package_version = "1.50";
    my $package_exists_retval;
    my $package_exists_version_found = "";
    ( $package_exists_retval, $package_exists_version_found ) =
      package_exists( $package_name, $package_version );
    if ( $package_exists_retval == 0 ) {
      if ( $package_exists_version_found eq "" ) {
        return ( 2,
              "Package '"
            . $package_name
            . "' not found. v$package_version is needed.\n"
            . package_installation_instructions($package_name) );
      } else {
        return ( 2,
              "Package '"
            . $package_name
            . "' v$package_version not found. v$package_exists_version_found found instead.\n"
            . package_installation_instructions($package_name) );
      }
    }
  }

  if ( $self->is_version_latest() ) {
    $package_name = "Bugzilla";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }
  } else {

    # This is the Bugzilla API file.
    $package_name = "globals.pl";
    if ( package_exists($package_name) == 0 ) {
      return ( 2,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }
    do 'globals.pl';
  }

  if ( $self->is_version_up_to_2_16()
    || $self->is_version_up_to_2_18()
    || $self->is_version_up_to_2_20() )
  {

    # This also seems to be required. Not sure why.
    $package_name = "CGI.pl";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }
  }

  if ( $self->is_version_up_to_2_20()
    || $self->is_version_up_to_2_22()
    || $self->is_version_latest() )
  {

    # In Bugzilla 2.20 AppendComment was moved to Bugzilla::Bug
    $package_name = "Bugzilla::Bug";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }

    # In Bugzilla 2.20 we also need Bugzilla::User::login_to_id
    $package_name = "Bugzilla::User";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }
  }

  if ( $self->is_version_latest() ) {
    $package_name = "Bugzilla::Product";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }

    $package_name = "Bugzilla::Version";
    if ( package_exists($package_name) == 0 ) {
      return ( 3,
            "Package '"
          . $package_name
          . "' not found in installation directory '"
          . $self->installation_directory()
          . "'.\n" );
    }
  }

  # This is direct modification to bugzilla's internal variables
  # required to configure the database connection
  $::db_host = $self->location();
  $::db_port = $self->port();
  $::db_name = $self->database();
  $::db_user = $self->username();
  $::db_pass = $self->password();

  return 0;
}

sub execute_post_tag_cleanup {
  my $self = shift;

  if ( $self->is_version_latest() ) {

    # obsolete?
  } else {

    # If Bugzilla is installed locally to the same machine the daemon
    # is installed, we can do more
    if ( $self->installed_locally() ) {

      # Force Bugzilla to regenerate the version cache file. In this
      # way, the version changes as a result of tagging are
      # reflected in the web interface immediately.
      GenerateVersionTable();
    }
  }
}

# Bugzilla 2.20 has a bug in AppendComment. It stores the comment with
# zero time stamp, if it is not given as a parameter.
sub bugzilla_2_20_fixed_AppendComment ($$$;$$$) {
  my ( $bugid, $whoid, $comment, $isprivate, $timestamp, $work_time ) = @_;
  $work_time ||= 0;
  my $dbh = Bugzilla->dbh;

  ValidateTime( $work_time, "work_time" ) if $work_time;
  trick_taint($work_time);

  # Use the date/time we were given if possible (allowing calling code
  # to synchronize the comment's timestamp with those of other records).
  if ( !defined($timestamp) ) {
    $timestamp = $dbh->selectrow_array("SELECT NOW()");
  }

  $comment =~ s/\r\n/\n/g;    # Handle Windows-style line endings.
  $comment =~ s/\r/\n/g;      # Handle Mac-style line endings.

  if ( $comment =~ /^\s*$/ ) {    # Nothin' but whitespace
    return;
  }

  # Comments are always safe, because we always display their raw contents,
  # and we use them in a placeholder below.
  trick_taint($comment);
  my $privacyval = $isprivate ? 1 : 0;
  $dbh->do(
    q{INSERT INTO longdescs
                      (bug_id, who, bug_when, thetext, isprivate, work_time)
               VALUES (?,?,?,?,?,?)}, undef,
    ( $bugid, $whoid, $timestamp, $comment, $privacyval, $work_time )
  );
  $dbh->do( "UPDATE bugs SET delta_ts = ? WHERE bug_id = ?", undef, ( $timestamp, $bugid ) );
}

# -----------------------------------------------------------------------------
# These functions are required by the Bugtracker interface
# -----------------------------------------------------------------------------

sub set_version_type {
  my $self    = shift;
  my $version = $self->version();

  if ( $version =~ m/(\d+)\.(\d+)\.(\d+)/ ) {
    my $major = $1;
    my $minor = $2;
    my $patch = $3;
    if ( $major == 2 ) {
      if ( $minor >= 14 && $minor <= 16 ) {
        $self->{version_type} = "up_to_2_16";
      } elsif ( $minor == 18 ) {
        $self->{version_type} = "up_to_2_18";
      } elsif ( $minor == 20 ) {
        $self->{version_type} = "up_to_2_20";
      } elsif ( $minor == 22 ) {
        $self->{version_type} = "up_to_2_22";
      } else {
        $self->{version_type} = $BUGTRACKER_VERSION_NOT_SUPPORTED;
      }
    } elsif ( $major == 3 && $minor == 0 ) {
      $self->{version_type} = $BUGTRACKER_VERSION_LATEST;
    } else {
      $self->{version_type} = $BUGTRACKER_VERSION_NOT_SUPPORTED;
    }
  } else {
    $self->{version_type} = $BUGTRACKER_VERSION_WRONG_FORMAT;
  }
}

# -----------------------------------------------------------------------------
# These functions are required by the Integration interface
# -----------------------------------------------------------------------------

# Connects to the bug-tracker database
sub integration_connect_database {
  my $self = shift;

  if ( $self->is_version_up_to_2_16() ) {
    ConnectToDatabase();
  }
}

# Disconnects from the bug-tracker database
sub integration_disconnect_database {
  my $self = shift;
}

# Enters an SCM check-in comment, originating from the bug-tracking
# username of the SCM user, against a bug
#
# PARAMETERS:
# $1 - Bug id
# $2 - Bug-tracker username of SCM user that will enter a comment
# $3 - Comment to be entered
#
# RETURNS:
# - 0 on success
# - 1,<a string describing the error> on failure
sub integration_add_comment {
  my $self = shift;
  my ( $bugid, $username, $comment ) = (@_);

  if ( $self->is_version_up_to_2_20() ) {

    # Bugzilla 2.20 does not have AppendComment in globals.pl It
    # was moved to Bugzilla::Bug. The original AppendComment takes
    # the user ID instead of the user name, so we need to convert
    # it here
    my $userid = Bugzilla::User::login_to_id($username);

    if ( $userid > 0 ) {
      bugzilla_2_20_fixed_AppendComment( $bugid, $userid, $comment );
      return 0;
    } else {

      # This should never happen. Each user should have a
      # corresponding userid in the database schema
      return 1,
"Login '$username' could not be converted to an id in Bugzilla. Is username mapping setup correctly in daemon.conf ?\n";
    }
  } elsif ( $self->is_version_up_to_2_22() || $self->is_version_latest() ) {

    # AppendComment was fixed in Bugzilla 2.22. Should use the
    # native version
    my $userid = Bugzilla::User::login_to_id($username);

    if ( $userid > 0 ) {
      Bugzilla::Bug::AppendComment( $bugid, $userid, $comment );
      return 0;
    } else {

      # This should never happen. Each user should have a
      # corresponding userid in the database schema
      return 1,
"Login '$username' could not be converted to an id in Bugzilla. Is username mapping setup correctly in daemon.conf ?\n";
    }
  } else {
    AppendComment( $bugid, $username, $comment );
    return 0;
  }
}

# Given a bug id, returns the name of the product to which the bug
# belongs
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_product_name {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my $bug = new Bugzilla::Bug($bugid);
    return $bug->product();
  }

  my $bug_product_name;
  my $sql;

  if ( $self->is_version_up_to_2_16() ) {
    $sql = "SELECT product FROM bugs WHERE bug_id = $bugid";
  } elsif ( $self->is_version_up_to_2_18()
    || $self->is_version_up_to_2_20()
    || $self->is_version_up_to_2_22() )
  {
    $sql =
"SELECT products.name FROM products, bugs WHERE bugs.bug_id = $bugid and bugs.product_id = products.id";
  }

  PushGlobalSQLState();
  SendSQL($sql);
  $bug_product_name = FetchOneColumn();
  PopGlobalSQLState();

  return $bug_product_name;
}

# Given a bug id, returns the bug-tracking username of the user to
# which the bug is assigned
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_owner {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my $bug = new Bugzilla::Bug($bugid);
    return $bug->assigned_to();
  }

  my $bug_owner;
  PushGlobalSQLState();
  SendSQL(
"SELECT profiles.login_name FROM profiles, bugs WHERE bugs.bug_id = $bugid AND bugs.assigned_to = profiles.userid"
  );
  $bug_owner = FetchOneColumn();
  PopGlobalSQLState();

  return $bug_owner;
}

# Given a bug id, returns the bug-tracking email address of the user to
# which that reported the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_reporter_email {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my $bug = new Bugzilla::Bug($bugid);
    return $bug->assigned_to();
  }

  my $bug_reporter_email;
  my $sql =
"SELECT profiles.login_name FROM profiles, bugs WHERE bugs.bug_id = $bugid AND bugs.reporter = profiles.userid";

  PushGlobalSQLState();
  SendSQL($sql);
  $bug_reporter_email = FetchOneColumn();
  PopGlobalSQLState();

  return $bug_reporter_email;
}

# Given a username, returns the user's email address
#
# PARAMETERS:
# $1 - Username
sub integration_get_user_email {
  my $self     = shift;
  my $username = shift;

  return $username;
}

# Given a bug id, returns the email addresses of the users monitoring
# the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_monitors_email_list {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my @bug_monitors_email_list;

    my $bug = new Bugzilla::Bug($bugid);
    my (@cc) = $bug->cc();
    if (@cc) {
      foreach my $c (@cc) {
        foreach my $bug_monitor_email (@$c) {
          push @bug_monitors_email_list, $bug_monitor_email;
        }
      }
    }

    return @bug_monitors_email_list;
  }

  my $sql =
"SELECT profiles.login_name from profiles, cc WHERE cc.bug_id= $bugid AND cc.who = profiles.userid";
  my @bug_monitors_email_list;

  PushGlobalSQLState();
  SendSQL($sql);
  while ( MoreSQLData() ) {
    my $bug_monitor_email;

    $bug_monitor_email = FetchOneColumn();
    push @bug_monitors_email_list, $bug_monitor_email;
  }
  PopGlobalSQLState();

  return @bug_monitors_email_list;
}

# Given a project_name, returns the email addresses of all the
# managers of the project.
#
# For Bugzilla, this is not applicable
#
# PARAMETERS:
# $1 - Project name
sub integration_get_managers_email_list {
  my $self         = shift;
  my $project_name = shift;

  return undef;
}

# Given a bug id, returns the status of the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_status {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my $bug = new Bugzilla::Bug($bugid);
    return $bug->bug_status();
  }

  my $bug_status;

  PushGlobalSQLState();
  SendSQL( "SELECT bugs.bug_status FROM bugs WHERE bugs.bug_id=" . $bugid );
  $bug_status = FetchOneColumn();
  PopGlobalSQLState();

  return $bug_status;
}

# Given a bug id, returns 1 if the bug is in a state considered
# active, or 0 otherwise. Active bug state examples would be
# "assigned" and "reopened". A bug can accept checkins in this
# state. However, "new" and "confirmed" are not active since in this
# state the bug has not been assigned to a developer. "resolved" is
# obviously not active.
#
# PARAMETERS:
# $1 - Bug id
sub integration_bug_in_active_state {
  my $self       = shift;
  my $bugid      = shift;
  my $bug_status = $self->integration_get_bug_status($bugid);

  # Verify that the bug is in the assigned or reopened state
  if ( $bugzilla_bug_status_map->{$bug_status}->{active} == 1 ) {
    return ( 1, $bug_status );
  } else {
    return ( 0, $bug_status );
  }
}

# Returns a string that reports the list of active states in the
# bug-tracker
sub integration_active_states_list {
  my $self = shift;

  return $self->active_states_list($bugzilla_bug_status_map);
}

# Given a bug id, returns the subject/summary line of the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_subject {
  my $self  = shift;
  my $bugid = shift;

  if ( $self->is_version_latest() ) {

    my $bug = new Bugzilla::Bug($bugid);
    return $bug->short_desc();
  }

  my $bug_subject;

  PushGlobalSQLState();
  SendSQL( "SELECT bugs.short_desc FROM bugs WHERE bugs.bug_id=" . $bugid );
  $bug_subject = FetchOneColumn();
  PopGlobalSQLState();

  return $bug_subject;
}

# Given a product name and a tag name, adds the tag in the list of
# available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
# $3 - Description of the tag
sub integration_add_tag {
  my $self = shift;
  my ( $product_name, $tag_name, $description ) = (@_);

  if ( $self->is_version_latest() ) {

    my $product = new Bugzilla::Product( { name => $product_name } );
    my $version = Bugzilla::Version::create( $tag_name, $product );
    return 0;

  }

  my $sql;

  if ( $self->is_version_up_to_2_16() ) {
    $sql =
        "INSERT INTO versions(value,program) VALUES("
      . SqlQuote($tag_name) . ", "
      . SqlQuote($product_name) . ")";

    PushGlobalSQLState();
    SendSQL($sql);
    PopGlobalSQLState();
  } elsif ( $self->is_version_up_to_2_18()
    || $self->is_version_up_to_2_20()
    || $self->is_version_up_to_2_22() )
  {

    # First, find the product id of our product
    my $product_id;
    $sql = "SELECT id FROM products WHERE name = " . SqlQuote($product_name);

    PushGlobalSQLState();
    SendSQL($sql);
    $product_id = FetchOneColumn();
    PopGlobalSQLState();

    $sql =
        "INSERT INTO versions(value,product_id) VALUES("
      . SqlQuote($tag_name) . ", "
      . $product_id . ")";
    PushGlobalSQLState();
    SendSQL($sql);
    PopGlobalSQLState();

  }

  $self->execute_post_tag_cleanup();

  return 0;
}

# Given a product name and a tag name, checks if the tag exists in the
# list of available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
sub integration_tag_exists {
  my $self = shift;
  my ( $product_name, $tag_name ) = (@_);

  if ( $self->is_version_latest() ) {

    my $product = new Bugzilla::Product( { name => $product_name } );
    my $versions = $product->versions();
    if ($versions) {
      foreach (@$versions) {
        if ( $_->name eq $tag_name ) {
          return 1;
        }
      }
    }
    return 0;
  }

  my $tag_status;
  my $sql;

  if ( $self->is_version_up_to_2_16() ) {
    $sql =
        "SELECT versions.value FROM versions WHERE versions.program="
      . SqlQuote($product_name)
      . " AND versions.value="
      . SqlQuote($tag_name);
  } elsif ( $self->is_version_up_to_2_18()
    || $self->is_version_up_to_2_20()
    || $self->is_version_up_to_2_22() )
  {
    $sql =
        "SELECT versions.value FROM versions,products WHERE versions.value="
      . SqlQuote($tag_name)
      . " AND versions.product_id = products.id AND products.name="
      . SqlQuote($product_name);
  }

  PushGlobalSQLState();
  SendSQL($sql);
  $tag_status = FetchOneColumn();
  if ( !defined($tag_status) ) {
    $tag_status = "";
  }
  PopGlobalSQLState();

  if ( $tag_status eq $tag_name ) {

    # The tag was found
    return 1;
  } else {

    # There was no such tag
    return 0;
  }
}

# Given a product name and a tag name, deletes the tag from the list
# of available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
sub integration_delete_tag {
  my $self = shift;
  my ( $product_name, $tag_name ) = (@_);

  if ( $self->is_version_latest() ) {

    my $product = new Bugzilla::Product( { name => $product_name } );
    my $version = new Bugzilla::Version( $product->id, $tag_name );
    $version->remove_from_db;

    return 0;

  }

  my $sql;

  if ( $self->is_version_up_to_2_16() ) {
    $sql =
        "DELETE FROM versions WHERE value="
      . SqlQuote($tag_name)
      . " AND program="
      . SqlQuote($product_name);
  } elsif ( $self->is_version_up_to_2_18()
    || $self->is_version_up_to_2_20()
    || $self->is_version_up_to_2_22() )
  {

    # First, find the product id of our product
    my $product_id;
    $sql = "SELECT id FROM products WHERE name = " . SqlQuote($product_name);

    PushGlobalSQLState();
    SendSQL($sql);
    $product_id = FetchOneColumn();
    PopGlobalSQLState();

    $sql =
      "DELETE FROM versions WHERE value=" . SqlQuote($tag_name) . " AND product_id=" . $product_id;
  }

  PushGlobalSQLState();
  SendSQL($sql);
  PopGlobalSQLState();

  $self->execute_post_tag_cleanup();

  return 0;
}

# Runs a VDD Query
#
# PARAMETERS:
# $1 - Name of product for which a VDD will be generated
# $2 - Previous version name of the product
# $3 - Newer version name of the product
# $4 - Date the previous version name was applied in the SCM system for the product
# $5 - Date the newer version name was applied in the SCM system for the product
#
# RETURNS:
# - A Scmbug::VDD object
sub integration_get_vdd {
  my $self = shift;
  my ( $product_name, $from_version, $to_version, $from_label_date, $to_label_date ) = (@_);
  my $vdd = Scmbug::VDD->new();
  my $sql = "SELECT profiles.login_name AS author_username,
                      profiles.realname AS author_name,
                      profiles.login_name AS author_email,
                      longdescs.bug_id AS bug_id,
                      bugs.short_desc AS summary,
                      bugs.bug_status AS status,
                      bugs.resolution AS resolution,
                      bugs.bug_severity AS severity,
                      bugs.priority AS priority,
                      date_format(longdescs.bug_when,'%Y-%m-%d %H:%i') AS date,
                      longdescs.thetext AS content
               FROM   longdescs, profiles, bugs
               WHERE  bug_when < '$to_label_date'
               AND    bug_when > '$from_label_date'
               AND    longdescs.who = profiles.userid
               AND    longdescs.bug_id = bugs.bug_id";

  PushGlobalSQLState();
  SendSQL($sql);
  my $comment_counter = 1;
  while ( MoreSQLData() ) {
    my %comment;
    my $bugid;
    (
      $comment{'author_username'}, $comment{'author_name'}, $comment{'author_email'},
      $bugid,                      $comment{'summary'},     $comment{'status'},
      $comment{'resolution'},      $comment{'severity'},    $comment{'priority'},
      $comment{'date'},            $comment{'content'}
      )
      = FetchSQLData();

    $comment{'author_username'} .= Param('emailsuffix');
    $comment{'author_name'} = $comment{'author_name'} || $comment{'author_username'};

    # The resolution can be blank in the database
    if ( !defined( $comment{'resolution'} ) ) {
      $comment{'resolution'} = "";
    }
    my $comment_counter_text;
    $comment_counter_text = "inaccurate_" . $comment_counter;

    $vdd->add_bug_comment(
      'all',                $bugid,                      $comment{'summary'},
      $comment{'status'},   $comment{'resolution'},      $comment{'severity'},
      $comment{'priority'}, $comment_counter_text,       $comment{'content'},
      $comment{'date'},     $comment{'author_username'}, $comment{'author_name'},
      $comment{'author_email'}
    );

    #
    # Categorize the bug even further
    #

    # If the bug is new or unconfirmed add it in the new list as
    # well
    if ( $comment{'status'} eq "NEW"
      || $comment{'status'} eq "UNCONFIRMED" )
    {
      $vdd->add_bug_comment(
        'new',                $bugid,                      $comment{'summary'},
        $comment{'status'},   $comment{'resolution'},      $comment{'severity'},
        $comment{'priority'}, $comment_counter_text,       $comment{'content'},
        $comment{'date'},     $comment{'author_username'}, $comment{'author_name'},
        $comment{'author_email'}
      );
    } elsif ( $comment{'status'} eq "RESOLVED"
      || $comment{'status'} eq "VERIFIED"
      || $comment{'status'} eq "CLOSED" )
    {

      # If the bug is resolved or verified or closed add it in
      # closed list as well
      $vdd->add_bug_comment(
        'closed',             $bugid,                      $comment{'summary'},
        $comment{'status'},   $comment{'resolution'},      $comment{'severity'},
        $comment{'priority'}, $comment_counter_text,       $comment{'content'},
        $comment{'date'},     $comment{'author_username'}, $comment{'author_name'},
        $comment{'author_email'}
      );

      # Also check if the bug was closed with no SCM activity
      if ( !$comment{'content'} =~ m/$PRODUCT_AFFECTED_FILES_TOKEN/ ) {
        $vdd->add_bug_comment(
          'closed_untouched',   $bugid,                      $comment{'summary'},
          $comment{'status'},   $comment{'resolution'},      $comment{'severity'},
          $comment{'priority'}, $comment_counter_text,       $comment{'content'},
          $comment{'date'},     $comment{'author_username'}, $comment{'author_name'},
          $comment{'author_email'}
        );
      }

    } else {

      # The bug must have been worked on but is not yet finished
      $vdd->add_bug_comment(
        'inprogress',         $bugid,                      $comment{'summary'},
        $comment{'status'},   $comment{'resolution'},      $comment{'severity'},
        $comment{'priority'}, $comment_counter_text,       $comment{'content'},
        $comment{'date'},     $comment{'author_username'}, $comment{'author_name'},
        $comment{'author_email'}
      );
    }
    $comment_counter++;
  }
  PopGlobalSQLState();

  return $vdd;
}

# Runs a get bugs query
#
# PARAMETERS:
# $1 - Name of product in which bugs will be queried
# $2 - List of bug ids that should be queried
#
# RETURNS:
# - A Scmbug::Bug_Information object
# - An integer ret_val that if set to 1 indicates that an error
#   occured.
# - A text message that should be reported to the client if an error
#   occurred.
sub integration_get_bugs {
  my $self = shift;
  my ( $product_name, $ids ) = (@_);
  my $mergelist   = Scmbug::MergeList->new();
  my $ret_val     = 0;
  my $ret_message = "";

  my $sql = "SELECT profiles.login_name AS author_username,
                      profiles.realname AS author_name,
                      profiles.login_name AS author_email,
                      longdescs.bug_id AS bug_id,
                      bugs.short_desc AS summary,
                      bugs.bug_status AS status,
                      bugs.resolution AS resolution,
                      bugs.bug_severity AS severity,
                      bugs.priority AS priority,
                      date_format(longdescs.bug_when,'%Y-%m-%d %H:%i') AS date,
                      longdescs.thetext AS content
               FROM   longdescs, profiles, bugs
               WHERE  longdescs.who = profiles.userid
               AND    longdescs.bug_id = bugs.bug_id
               AND    (";
  my $id_counter = 0;
  foreach my $id ( @{$ids} ) {
    if ( $id_counter >= 1 ) {
      $sql .= "\n               OR     ";
    }
    $sql .= "bugs.bug_id = $id";
    $id_counter++;
  }
  $sql .= ");";

  PushGlobalSQLState();
  SendSQL($sql);
  my $comment_counter = 1;
  while ( MoreSQLData() ) {
    my %comment;
    my $bugid;
    (
      $comment{'author_username'}, $comment{'author_name'}, $comment{'author_email'},
      $bugid,                      $comment{'summary'},     $comment{'status'},
      $comment{'resolution'},      $comment{'severity'},    $comment{'priority'},
      $comment{'date'},            $comment{'content'}
      )
      = FetchSQLData();

    $comment{'author_username'} .= Param('emailsuffix');
    $comment{'author_name'} = $comment{'author_name'} || $comment{'author_username'};

    # The resolution can be blank in the database
    if ( !defined( $comment{'resolution'} ) ) {
      $comment{'resolution'} = "";
    }
    my $comment_counter_text;
    $comment_counter_text = "inaccurate_" . $comment_counter;

    my (
      $is_an_integration_comment, $affected_files_description, $extracted_comment,
      $extracted_ret_val,         $extracted_ret_message
      )
      = $self->extract_affected_files( $comment{content}, $comment{bug_id}, $comment_counter_text );

    if ( $is_an_integration_comment == 1 ) {

      if ( $extracted_ret_val == 0 ) {
        $mergelist->add_bug_comment(
          $bugid,                      $comment{'summary'},     $comment{'status'},
          $comment{'resolution'},      $comment{'severity'},    $comment{'priority'},
          $comment_counter_text,       $extracted_comment,      $comment{'date'},
          $comment{'author_username'}, $comment{'author_name'}, $comment{'author_email'},
          $affected_files_description
        );
      } else {

        # There was an error message detected. Should report it.
        $ret_val = 1;
        $ret_message .= $extracted_ret_message;
      }
    } else {

      # This is just a user comment. Ignore it.
    }
    $comment_counter++;
  }
  PopGlobalSQLState();
  close SOMEFILE;

  return $mergelist, $ret_val, $ret_message;
}

1;
