# Budget is a meta class that is used to calculate summary data
# for all the deliverables on a project.  Think of it akin to:
#  has_many :deliverables
#  belongs_to :project
#
class Budget
  
  attr_reader :project
  
  def initialize(project_id)
    @project = Project.find(project_id)
  end

  # Gets the next due date from the deliverables
  def next_due_date
    del = self.deliverables
    return nil unless del.size > 0

    dates = del.collect(&:due).delete_if { |d| d.blank?}
    
    return dates.sort[0]
  end
  
  # Gets the last due date of the deliverables
  def final_due_date
    del = self.deliverables
    return nil unless del.size > 0

    dates = del.collect(&:due).delete_if { |d| d.blank?}
    
    return dates.sort[-1]    
  end
  
  # Deliverables assigned to this +Budget+
  def deliverables
    return Deliverable.find_all_by_project_id(@project.id)
  end
  
  # Total budget all of the deliverables
  def budget
    return self.deliverables.collect(&:budget).delete_if { |d| d.blank?}.inject { |sum, n| sum + n} || 0.0
  end

  # Total labor budget all of the deliverables
  def labor_budget
    return self.deliverables.collect(&:labor_budget).delete_if { |d| d.blank?}.inject { |sum, n| sum + n} || 0.0
  end

  # Amount of the budget spent.  Expressed as as a percentage whole number
  def budget_ratio
    budget = self.budget # cache result
    if budget > 0.0
      return ((self.spent / budget) * 100).round 
    else
      self.progress
    end
  end
  
  # Total amount spent for all the deliverables
  def spent
    self.deliverables.collect(&:spent).delete_if { |d| d.blank?}.inject { |sum, n| sum + n } || 0.0
  end
  
  # Amount of budget left on the deliverables
  def left
    return self.budget - self.spent
  end

  # Amount of labor budget left on the deliverables
  def labor_budget_left
    return self.labor_budget - self.spent
  end

  # Amount spent over the budget
  def overruns
    if self.left >= 0
      return 0
    else
      return self.left * -1
    end
  end
  
  # Completation progress, expressed as a percentage whole number
  def progress
    return 100 unless self.deliverables.size > 0
    return 100 if self.budget == 0.0
    
    balance = 0.0
    
    self.deliverables.each do |deliverable|
      balance += deliverable.budget * deliverable.progress
    end
    
    return (balance / self.budget).round
  end
  
  # Budget score.  Will range from 100 (everything done with no money spent) to -100 (nothing done, all the money spent)
  def score
    return self.progress - self.budget_ratio
  end
  
  # Total profit of the deliverables.  This is *not* calculated based on the amount spent and total budget but is the total of the profit amount for the deliverables.
  def profit
    return 0.0 unless self.deliverables.size > 0
    
    # Covers fixed and percentage profit though the +profit+ method being overloaded on the Deliverable types
    return self.deliverables.collect(&:profit).delete_if { |d| d.blank?}.inject { |sum, n| sum + n } || 0.0
  end
  
  # Dollar amount of time that has been logged to the project itself
  def amount_missing_on_issues
    time_logs = TimeEntry.find_all_by_project_id_and_issue_id(self.project.id, nil)

    return time_logs.collect(&:cost).sum
  end
  
  # Dollar amount of time that has been logged to issues that are not assigned to deliverables
  def amount_missing_on_deliverables
    # Bisect the issues because NOT IN isn't reliable
    all_issues = self.project.issues.find(:all)
    return 0 if all_issues.empty?

    deliverable_issues = self.project.issues.find(:all, :conditions => ["deliverable_id IN (?)", self.deliverables.collect(&:id)])

    missing_issues = all_issues - deliverable_issues

    time_logs = missing_issues.collect(&:time_entries).flatten
    
    return time_logs.collect(&:cost).sum
  end
end
