# Copyright © Emilio González Montaña
# Licence: Attribution & no derivatives
#   * Attribution to the plugin web page URL should be done if you want to use it.
#     https://redmine.ociotec.com/projects/redmine-plugin-scrum
#   * No derivatives of this plugin (or partial) are allowed.
# Take a look to licence.txt file at plugin root folder for further details.

class ScrumController < ApplicationController

  menu_item :product_backlog, :except => [:stats]
  menu_item :overview, :only => [:stats]

  before_action :find_issue,
                :only => [:change_story_points, :change_remaining_story_points,
                          :change_pending_effort,
                          :change_assigned_to, :new_time_entry,
                          :create_time_entry, :edit_task, :update_task,
                          :change_pending_efforts]
  before_action :find_sprint,
                :only => [:new_pbi, :create_pbi,
                          :move_not_closed_pbis_to_last_sprint]
  before_action :find_pbi,
                :only => [:new_task, :create_task, :edit_pbi, :update_pbi,
                          :move_pbi, :move_to_last_sprint,
                          :move_to_product_backlog]
  before_action :find_project_by_project_id,
                :only => [:stats]

  before_action :authorize,
                :except => [:new_pbi, :create_pbi, :new_task, :create_task,
                            :move_to_last_sprint,
                            :move_not_closed_pbis_to_last_sprint,
                            :move_to_product_backlog,
                            :new_time_entry, :create_time_entry]
  before_action :authorize_add_issues,
                :only => [:new_pbi, :create_pbi, :new_task, :create_task]
  before_action :authorize_edit_issues,
                :only => [:move_to_last_sprint,
                          :move_not_closed_pbis_to_last_sprint,
                          :move_to_product_backlog]
  before_action :authorize_log_time,
                :only => [:new_time_entry, :create_time_entry]

  helper :custom_fields
  helper :projects
  helper :scrum
  helper :timelog

  def change_story_points
    begin
      @issue.story_points = params[:value]
      status = 200
    rescue
      status = 503
    end
    render :body => nil, :status => status
  end

  def change_remaining_story_points
    @issue.remaining_story_points = params[:value]
    render :body => nil, :status => status
  end

  def change_pending_effort
    @issue.pending_effort = params[:value]
    render :body => nil, :status => 200
  end

  def change_pending_efforts
    params['pending_efforts'].each_pair do |id, value|
      pending_effort = PendingEffort.find(id)
      raise "Invalid pending effort ID #{id}" if pending_effort.nil?
      raise "Pending effort ID #{id} is not owned by this issue" if pending_effort.issue_id != @issue.id
      if value.blank?
        pending_effort.delete
      else
        pending_effort.effort = value.to_f
        pending_effort.save!
      end
    end
    redirect_to issue_path(@issue)
  end

  def change_assigned_to
    @issue.init_journal(User.current)
    @issue.assigned_to = params[:value].blank? ? nil : User.find(params[:value].to_i)
    @issue.save!
    render_task(@project, @issue, params)
  end

  def new_time_entry
    @pbi_status_id = params[:pbi_status_id]
    @other_pbi_status_ids = params[:other_pbi_status_ids]
    @task_id = params[:task_id]
    respond_to do |format|
      format.js
    end
  end

  def create_time_entry
    begin
      time_entry = TimeEntry.new(params.require(:time_entry).permit(:hours, :spent_on, :comments, :activity_id, :user_id))
      time_entry.project_id = @project.id
      time_entry.issue_id = @issue.id
      time_entry.user_id = params[:time_entry][:user_id]
      call_hook(:controller_timelog_edit_before_save, {:params => params, :time_entry => time_entry})
      time_entry.save!
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js
    end
  end

  def new_pbi
    @pbi = Issue.new
    @pbi.project = @project
    @pbi.tracker = @project.trackers.find(params[:tracker_id])
    @pbi.status = @pbi.default_status
    @pbi.author = User.current
    @pbi.sprint = @sprint
    @top = true unless params[:top].nil? or (params[:top] == 'false')
    respond_to do |format|
      format.html
      format.js
    end
  end

  def create_pbi
    begin
      @continue = !(params[:create_and_continue].nil?)
      @top = !(params[:top].nil?)
      @pbi = Issue.new
      if params[:issue][:project_id]
        @pbi.project_id = params[:issue][:project_id]
      else
        @pbi.project = @project
      end
      @pbi.author = User.current
      @pbi.tracker_id = params[:issue][:tracker_id]
      @pbi.set_on_top if @top
      @pbi.sprint = @sprint
      update_attributes(@pbi, params)
      @pbi.save!
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js
    end
  end

  def edit_pbi
    respond_to do |format|
      format.js
    end
  end

  def update_pbi
    begin
      @pbi.init_journal(User.current, params[:issue][:notes])
      update_attributes(@pbi, params)
      @pbi.save!
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js
    end
  end

  def move_pbi
    begin
      @position = params[:position]
      case params[:position]
        when 'top', 'bottom'
          @pbi.move_pbi_to(@position)
        when 'before'
          @other_pbi = params[:before_other_pbi]
          @pbi.move_pbi_to(@position, @other_pbi)
        when 'after'
          @other_pbi = params[:after_other_pbi]
          @pbi.move_pbi_to(@position, @other_pbi)
        else
          raise "Invalid position: #{@position.inspect}"
      end
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
  end

  def move_to_last_sprint
    begin
      raise "The project hasn't defined any Sprint yet" unless @project.last_sprint
      @previous_sprint = @pbi.sprint
      move_issue_to_sprint(@pbi, @project.last_sprint)
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js
    end
  end

  def move_not_closed_pbis_to_last_sprint
    begin
      last_sprint = @project.last_sprint
      raise "The project hasn't defined any Sprint yet" unless last_sprint
      not_closed_pbis = @sprint.not_closed_pbis
      if not_closed_pbis.empty?
        flash[:notice] = l(:label_nothing_to_move)
      else
        not_closed_pbis_links = []
        not_closed_pbis.each do |pbi|
          link = view_context.link_to_issue(pbi,
                                            :project => pbi.project != @project,
                                            :tracker => true)
          not_closed_pbis_links << link
          move_issue_to_sprint(pbi, last_sprint)
        end
        flash[:notice] = l(:label_pbis_moved,
                           :pbis => not_closed_pbis_links.join(', '))
      end
    rescue Exception => exception
      logger.error("Exception: #{exception.inspect}")
      flash[:error] = exception
    end
    redirect_to sprint_path(@sprint)
  end

  def move_to_product_backlog
    begin
      product_backlog = @project.product_backlogs.find(params[:id])
      move_issue_to_sprint(@pbi, product_backlog)
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js
    end
  end

  def new_task
    @task = Issue.new
    @task.project = @pbi.project
    @task.tracker = Tracker.find(params[:tracker_id])
    @task.status = @task.default_status
    @task.parent = @pbi
    @task.author = User.current
    @task.sprint = @sprint
    if Scrum::Setting.inherit_pbi_attributes
      @task.inherit_from_issue(@pbi)
    end
    respond_to do |format|
      format.html
      format.js
    end
  rescue Exception => e
    logger.error("Exception: #{e.inspect}")
    render_404
  end

  def create_task
    begin
      @continue = !(params[:create_and_continue].nil?)
      @task = Issue.new
      if params[:issue][:project_id]
        @task.project_id = params[:issue][:project_id]
      else
        @task.project = @pbi.project
      end
      @task.parent_issue_id = @pbi.id
      @task.author = User.current
      @task.sprint = @sprint
      @task.tracker_id = params[:issue][:tracker_id]
      update_attributes(@task, params)
      @task.save!
      @task.pending_effort = params[:issue][:pending_effort]
    rescue Exception => @exception
    end
    respond_to do |format|
      format.js
    end
  end

  def edit_task
    respond_to do |format|
      format.js
    end
  end

  def update_task
    begin
      @issue.init_journal(User.current, params[:issue][:notes])
      @old_status = @issue.status
      update_attributes(@issue, params)
      @issue.save!
      @issue.pending_effort = params[:issue][:pending_effort]
    rescue Exception => @exception
      logger.error("Exception: #{@exception.inspect}")
    end
    respond_to do |format|
      format.js do
        render "scrum/update_issue"
      end
    end
  end

  def stats
    if User.current.allowed_to?(:view_time_entries, @project)
      cond = @project.project_condition(Setting.display_subprojects_issues?)
      @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
    end

    @closed_story_points_per_sprint = @project.closed_story_points_per_sprint
    @closed_story_points_per_sprint_chart = {:id => 'closed_story_points_per_sprint', :height => 400}

    @hours_per_story_point = @project.hours_per_story_point
    @hours_per_story_point_chart = {:id => 'hours_per_story_point', :height => 400}

    @sps_by_pbi_category, @sps_by_pbi_category_total = @project.sps_by_category
    @sps_by_pbi_type, @sps_by_pbi_type_total = @project.sps_by_pbi_type
    @effort_by_activity, @effort_by_activity_total = @project.effort_by_activity
  end

private

  def render_task(project, task, params)
    render :partial => "post_its/sprint_board/task",
           :status => 200,
           :locals => {:project => project,
                       :task => task,
                       :pbi_status_id => params[:pbi_status_id],
                       :other_pbi_status_ids => params[:other_pbi_status_ids].split(","),
                       :task_id => params[:task_id],
                       :read_only => false}
  end

  def find_sprint
    @sprint = Sprint.find(params[:sprint_id])
    @project = @sprint.project
  rescue
    logger.error("Sprint #{params[:sprint_id]} not found")
    render_404
  end

  def find_pbi
    @pbi = Issue.find(params[:pbi_id])
    @sprint = @pbi.sprint
    @project = @sprint.project
  rescue
    logger.error("PBI #{params[:pbi_id]} not found")
    render_404
  end

  def authorize_action_on_current_project(action)
    if User.current.allowed_to?(action, @project)
      return true
    else
      render_403
      return false
    end
  end

  def authorize_add_issues
    authorize_action_on_current_project(:add_issues)
  end

  def authorize_log_time
    authorize_action_on_current_project(:log_time)
  end

  def authorize_edit_issues
    authorize_action_on_current_project(:edit_issues)
  end

  def update_attributes(issue, params)
    issue.status_id = params[:issue][:status_id] unless params[:issue][:status_id].nil?
    raise 'New status is not allowed' unless issue.new_statuses_allowed_to.include?(issue.status)
    issue.project_id = params[:issue][:project_id] unless params[:issue][:project_id].nil?
    issue.assigned_to_id = params[:issue][:assigned_to_id] unless params[:issue][:assigned_to_id].nil?
    issue.subject = params[:issue][:subject] unless params[:issue][:subject].nil?
    issue.priority_id = params[:issue][:priority_id] unless params[:issue][:priority_id].nil?
    issue.estimated_hours = params[:issue][:estimated_hours].gsub(',', '.') if issue.safe_attribute?(:estimated_hours) and (!(params[:issue][:estimated_hours].nil?))
    issue.done_ratio = params[:issue][:done_ratio] unless params[:issue][:done_ratio].nil?
    issue.description = params[:issue][:description] unless params[:issue][:description].nil?
    issue.category_id = params[:issue][:category_id] if issue.safe_attribute?(:category_id) and (!(params[:issue][:category_id].nil?))
    issue.fixed_version_id = params[:issue][:fixed_version_id] if issue.safe_attribute?(:fixed_version_id) and (!(params[:issue][:fixed_version_id].nil?))
    issue.start_date = params[:issue][:start_date] if issue.safe_attribute?(:start_date) and (!(params[:issue][:start_date].nil?))
    issue.due_date = params[:issue][:due_date] if issue.safe_attribute?(:due_date) and (!(params[:issue][:due_date].nil?))
    issue.custom_field_values = params[:issue][:custom_field_values] unless params[:issue][:custom_field_values].nil?
  end

  def move_issue_to_sprint(issue, sprint)
    issue.init_journal(User.current)
    issue.sprint = sprint
    issue.save!
    issue.children.each do |child|
      unless child.closed?
        move_issue_to_sprint(child, sprint)
      end
    end
  end

end
