# Main class for the slow actions plugin library
require File.join(File.dirname(__FILE__), 'slow_actions_parser')
require File.join(File.dirname(__FILE__), 'slow_actions_controller')
require File.join(File.dirname(__FILE__), 'slow_actions_action')
require File.join(File.dirname(__FILE__), 'slow_actions_session')
require 'date'

# SlowActions class that is the master controller for processing slow actions
class SlowActions
  # Takes an options hash where you can specify :start_date and :end_date as "YYYY-MM-DD" strings
  def initialize(opts = {})
    @log_entries = []
    if opts[:start_date]
      @start_date = opts[:start_date]
    else
      @start_date = '0000-00-00' 
    end
    if opts[:end_date]
      @end_date = opts[:end_date]
    else
      @end_date = "#{Date.today.year}-#{Date.today.month}-#{Date.today.day}"
    end
  end

  # Parse the file found at "file_path" and add the log entries to its collection of entries.
  def parse_file(file_path)
    parser = Parser.new(file_path, @start_date, @end_date)
    @log_entries += parser.parse
    process
  end

  # All the #LogEntry objects
  def log_entries
    return @log_entries
  end

  # Print out all the actions and their statistics
  #
  #  :mincost  Lower bound on the cost of this action. See Computable.
  #  :minavg   Lower bound on the average amount of time this action ever took
  #  :minmax   Lower bound on the maximum amount of time this action ever took
  def print_actions(opts = {})
    str = ""
    str += "           Cost    Average Max\n"
    actions.sort{|x,y| y.total_cost <=> x.total_cost}.each do |a|
      next if opts[:mincost] and a.total_cost < opts[:mincost]
      next if opts[:minavg] and a.total_avg < opts[:minavg]
      next if opts[:minmax] and a.total_max < opts[:minmax]
      str += "- #{a.controller.name} : #{a.name} "
      str += "(#{a.log_entries.size} entries, #{(a.error_avg*100).to_i}% Error)\n"
      str += "  Total:   #{ftos a.total_cost}#{ftos a.total_avg}#{ftos a.total_max}\n"
      str += "  Render:  #{ftos a.render_cost}#{ftos a.render_avg}#{ftos a.render_max}\n"
      str += "  DB:      #{ftos a.db_cost}#{ftos a.db_avg}#{ftos a.db_max}\n"
      str += "\n"
    end
    return str
  end

  # Print out all the controllers and their statistics, nesting actions as a tree. See #print_actions for options.
  def print_controller_tree(opts = {})
    str = ""
    str += "             Cost    Average Max\n"
    controllers.sort{|x,y| y.total_cost <=> x.total_cost}.each do |c|
      next if opts[:mincost] and c.total_cost < opts[:mincost]
      next if opts[:minavg] and c.total_avg < opts[:minavg]
      next if opts[:minmax] and c.total_max < opts[:minmax]
      str += "+ #{c.name} (#{c.log_entries.size} entries, #{(c.error_avg*100).to_i}% Error)\n"
      str += "| Total:     #{ftos c.total_cost}#{ftos c.total_avg}#{ftos c.total_max}\n"
      str += "| Render:    #{ftos c.render_cost}#{ftos c.render_avg}#{ftos c.render_max}\n"
      str += "| DB:        #{ftos c.db_cost}#{ftos c.db_avg}#{ftos c.db_max}\n"
      c.actions.sort{|x,y| y.total_cost <=> x.total_cost}.each do |a|
        next if opts[:mincost] and a.total_cost < opts[:mincost]
        next if opts[:minavg] and a.total_avg < opts[:minavg]
        next if opts[:minmax] and a.total_max < opts[:minmax]
        str += "|-+ #{a.name} (#{a.log_entries.size} entries, #{(a.error_avg*100).to_i}% Error)\n"
        str += "| | Total:   #{ftos a.total_cost}#{ftos a.total_avg}#{ftos a.total_max}\n"
        str += "| | Render:  #{ftos a.render_cost}#{ftos a.render_avg}#{ftos a.render_max}\n"
        str += "| | DB:      #{ftos a.db_cost}#{ftos a.db_avg}#{ftos a.db_max}\n"
      end
      str += "\n"
    end
    return str
  end

  # Print out all the session_ids and their statistics. See #print_actions for options.
  def print_sessions(opts = {})
    str = ""
    str += "           Cost    Average Max\n"
    sessions.sort{|x,y| y.total_cost <=> x.total_cost}.each do |s|
      next if opts[:mincost] and s.total_cost < opts[:mincost]
      next if opts[:minavg] and s.total_avg < opts[:minavg]
      next if opts[:minmax] and s.total_max < opts[:minmax]
      str += "+ #{s.name} (#{s.log_entries.size} entries, #{(s.error_avg*100).to_i}% Error)\n"
      str += "| Total:   #{ftos s.total_cost}#{ftos s.total_avg}#{ftos s.total_max}\n"
      str += "| Render:  #{ftos s.render_cost}#{ftos s.render_avg}#{ftos s.render_max}\n"
      str += "| DB:      #{ftos s.db_cost}#{ftos s.db_avg}#{ftos s.db_max}\n"
      str += "\n"
    end
    return str
  end

  # Print out the stats as html. Not implemented.
  def to_html
    raise "Not Implemented"
  end

  # All the #Controller objects.
  def controllers
    @controllers.values
  end

  # All the #Action objects
  def actions
    @actions.values
  end

  # All the #Session objects
  def sessions
    @sessions.values
  end

  private

  # The Date to start parsing from
  attr_accessor :start_date
  # The Date to stop parsing at
  attr_accessor :end_date

  # Process statistics for all #Actions #Controllers and #Sessions
  def process
    @controllers ||= {}
    @actions ||= {}
    @sessions ||= {}
    @log_entries.each do |la|
      next if la.nil? or la.processed
      c = @controllers[la.controller]
      if c.nil?
        c = Controller.new(la.controller)
        @controllers[la.controller] = c
      end
      c.add_entry(la)

      a = @actions[la.action]
      if a.nil?
        a = Action.new(la.action, c)
        @actions[la.action] = a
        c.add_action(a)
      end
      a.add_entry(la)

      s = @sessions[la.session]
      if s.nil?
        s = Session.new(la.session)
        @sessions[la.session] = s
      end
      s.add_entry(la)
      la.processed = true
    end

    # now compute the times for each
    @controllers.values.each{|c| c.compute_times}
    @actions.values.each{|a| a.compute_times}
    @sessions.values.each{|s| s.compute_times}

    # return something simple. This helps irb not barf on itself trying to print everything
    return true
  end

  # Convert a float to 7 places padded with zeros then one space
  def ftos(float)
    str = ((float*1000).to_i.to_f/1000).to_s
    while str.size < 7
      str += "0"
    end
    str += " "
  end
end