###############################################################################
# Codestriker: Copyright (c) 2001, 2002 David Sitsky.  All rights reserved.
# sits@users.sourceforge.net
#
# This program is free software; you can redistribute it and modify it under
# the terms of the GPL.

# Action object for handling the submission of a new comment.

package Codestriker::Action::SubmitNewComment;

use strict;

use HTML::Entities ();
use Codestriker::Model::Comment;
use Codestriker::Model::File;
use Codestriker::Model::Topic;

# If the input is valid, create the appropriate topic into the database.
sub process($$$) {
    my ($type, $http_input, $http_response) = @_;

    # Obtain a new URL builder object.
    my $query = $http_response->get_query();
    my $url_builder = Codestriker::Http::UrlBuilder->new($query);

    # Check that the appropriate fields have been filled in.
    my $topicid = $http_input->get('topic');
    my $line = $http_input->get('line');
    my $fn = $http_input->get('fn');
    my $new = $http_input->get('new');
    my $comments = $http_input->get('comments');
    my $email = $http_input->get('email');
    my $cc = $http_input->get('comment_cc');
    my $mode = $http_input->get('mode');
    my $anchor = $http_input->get('a');
    my $format = $http_input->get('format');

    # Check that the fields have been filled appropriately.
    if ($comments eq "" || !defined $comments) {
        $http_response->error("No comments were entered");
    }
    if ($email eq "" || !defined $email) {
        $http_response->error("No email address was entered");
    }

    # Retrieve the comment metric values.
    my @metrics = ();
    foreach my $comment_state_metric (@{$Codestriker::comment_state_metrics}) {
        my $name = "comment_state_metric_" . $comment_state_metric->{name};
        my $metric = {};
        $metric->{name} = $comment_state_metric->{name};
        $metric->{value} = $http_input->get($name);
        if (defined $metric->{value} && $metric->{value} eq 'Select Value') {
            $http_response->error("Metric value for $metric->{name} unspecified");
        }

        # Handles the case where a comment is sent via email - metrics should not change.
        push @metrics, $metric if defined $metric->{value} && $metric->{value} ne '';
    }

    # Retrieve the appropriate topic details.
    my $topic = Codestriker::Model::Topic->new($topicid);

    # Don't accept any new comments if the topic state is read only.
    if (Codestriker::topic_readonly($topic->{topic_state})) {
        $http_response->error("Topic state is read only");
    }

    # Fire the topic listener to indicate that the user has viewed the topic.
    Codestriker::TopicListeners::Manager::topic_viewed($email, $topic);

    # Create the comment in the database.
    my $comment = Codestriker::Model::Comment->new();
    $comment->create($topicid, $line, $fn, $new,
                     $email, $comments, \@metrics);
    $comment->{cc} = $cc;

    # Tell the listener classes that a comment has just been created.
    my $listener_response =
      Codestriker::TopicListeners::Manager::comment_create($topic, $comment);

    if (defined $format && $format eq "xml") {
        my $response = $listener_response ne '' ? $listener_response : 'OK';

        print $query->header(-content_type=>'text/xml');
        print "<?xml version=\"1.0\" encoding=\"UTF-8\" " .
          "standalone=\"yes\"?>\n";
        print "<response><method>submitnewcomment</method>" .
          "<result>" . HTML::Entities::encode($response) .
            "</result></response>\n";
    } else {
        $http_response->error($listener_response) if $listener_response ne '';
        # Display a simple screen indicating that the comment has been
        # registered.  Clicking the Close button simply dismisses the
        # edit popup.  Leaving it up will ensure the next editing
        # topic will be handled quickly, as the overhead of bringing
        # up a new window is removed.
        my $reload = $query->param('submit') eq 'Submit+Refresh' ? 1 : 0;
        $http_response->generate_header(topic=>$topic,
                                        topic_title=>"Comment Submitted: " .
                                        "$topic->{title}",
                                        email=>$email,
                                        repository=>$Codestriker::repository_name_map->{$topic->{repository}},
                                        load_anchor=>$anchor,
                                        reload=>$reload, cache=>0);

        my $view_topic_url = $url_builder->view_url(topicid => $topicid, mode => $mode, fview => $fn,
                                                    filenumber => $fn, new => $new, line => $line,
                                                    projectid => $topic->{project_id});
        my $view_comments_url = $url_builder->view_comments_url(topicid => $topic->{topicid},
                                                                projectid => $topic->{project_id});

        my $vars = {};
        $vars->{'view_topic_url'} = $view_topic_url;
        $vars->{'view_comments_url'} = $view_comments_url;
        $vars->{'comment'} = $comment->{data};

        my $template = Codestriker::Http::Template->new("submitnewcomment");
        $template->process($vars);

        $http_response->generate_footer();
    }
}

# Given a topic and topic line number, try to determine the line
# number of the new file it corresponds to.  For topic lines which
# were made against '+' lines or unchanged lines, this will give an
# accurate result.  For other situations, the number returned will be
# approximate.  The results are returned in $filename_ref,
# $linenumber_ref and $accurate_ref references.  This is a deprecated method
# which is only used for data migration purposes (within checksetup.pl and
# import.pl).
sub _get_file_linenumber ($$$$$$$$)
  {
      my ($type, $topic, $topic_linenumber, $filenumber_ref,
          $filename_ref, $linenumber_ref, $accurate_ref, $new_ref) = @_;

      # Find the appropriate file that $topic_linenumber refers to.
      my (@filename, @revision, @offset, @binary);
      Codestriker::Model::File->get_filetable($topic, \@filename, \@revision,
                                              \@offset, \@binary);
      # No filetable.
      return 0 if ($#filename == -1);

      my $diff_limit = -1;
      my $index;
      for ($index = 0; $index <= $#filename; $index++) {
          last if ($offset[$index] > $topic_linenumber);
      }

      # Check if the comment was made against a diff header.
      if ($index <= $#offset) {
          my $diff_header_size;
          if ($revision[$index] eq $Codestriker::ADDED_REVISION ||
              $revision[$index] eq $Codestriker::REMOVED_REVISION) {
              # Added or removed file.
              $diff_header_size = 6;
          } elsif ($revision[$index] eq $Codestriker::PATCH_REVISION) {
              # Patch file
              $diff_header_size = 3;
          } else {
              # Normal CVS diff header.
              $diff_header_size = 7;
          }

          if ( ($topic_linenumber >=
                $offset[$index] - $diff_header_size) &&
               ($topic_linenumber <= $offset[$index]) ) {
              $$filenumber_ref = $index;
              $$filename_ref = $filename[$index];
              $$linenumber_ref = 1;
              $$accurate_ref = 0;
              $$new_ref = 0;
              return 1;
          }
      }
      $index--;

      # Couldn't find a matching linenumber.
      if ($index < 0 || $index > $#filename) {
          $$filenumber_ref = -1;
          $$filename_ref = "";
          return 1;
      }

      # Retrieve the diff text corresponding to this file.
      my ($tmp_offset, $tmp_revision, $diff_text);
      Codestriker::Model::File->get($topic, $index, \$tmp_offset,
                                    \$tmp_revision, \$diff_text);

      # Go through the patch file until we reach the topic linenumber of
      # interest.
      my $new = 0;
      my $accurate_line = 0;
      my $oldfile_linenumber = 0;
      my $newfile_linenumber = 0;
      my $current_topic_linenumber;
      my @lines = split /\n/, $diff_text;
      for (my $i = 0, $current_topic_linenumber = $offset[$index];
           $i <= $#lines && $current_topic_linenumber <= $topic_linenumber;
           $i++, $current_topic_linenumber++) {
          $_ = $lines[$i];
          if (/^\@\@ \-(\d+),\d+ \+(\d+),\d+ \@\@.*$/o) {
              # Matching diff header, record what the current linenumber is now
              # in the new file.
              $oldfile_linenumber = $1 - 1;
              $newfile_linenumber = $2 - 1;
              $accurate_line = 0;
              $new = 0;
          } elsif (/^\s.*$/o) {
              # A line with no change.
              $oldfile_linenumber++;
              $newfile_linenumber++;
              $accurate_line = 1;
              $new = 1;
          } elsif (/^\+.*$/o) {
              # A line corresponding to the new file.
              $newfile_linenumber++;
              $accurate_line = 1;
              $new = 1;
          } elsif (/^\-.*$/o) {
              # A line corresponding to the old file.
              $oldfile_linenumber++;
              $accurate_line = 0;
              $new = 0;
          }
      }

      if ($current_topic_linenumber >= $topic_linenumber) {
          # The topic linenumber was found.
          $$filenumber_ref = $index;
          $$filename_ref = $filename[$index];
          $$linenumber_ref = $new ? $newfile_linenumber : $oldfile_linenumber;
          $$accurate_ref = $accurate_line;
          $$new_ref = $new;
      } else {
          # The topic linenumber was not found.
          $$filenumber_ref = -1;
          $$filename_ref = "";
      }
      return 1;
  }

1;
