#!/usr/bin/env ruby

require 'rubygems' if RUBY_VERSION =~ /^1\.8/
require_relative '../lib/simplekit'

# Demonstration model of Operational Availability (Ao).
class AoModel
  include SimpleKit

  # model state
  attr_reader :num_available_jeeps,
              :num_available_mechanics,
              :maintenance_q_length,
              :breakdown_q_length

  # model parameters
  attr_reader :max_jeeps,
              :max_maintainers,
              :breakdown_rate,
              :maintenance_cycle_in_days,
              :repair_rate,
              :halt_time

  # Exponential random variate generator with specified rate.
  def exponential(rate)
    -Math.log(rand) / rate
  end

  # the actual model implementation...

  # Constructor initializes the model parameters.
  def initialize(max_jeeps, max_maintainers, breakdown_rate,
                 maintenance_cycle_in_days, mean_repair_time, halt_time)
    @max_jeeps = max_jeeps
    @max_maintainers = max_maintainers
    @breakdown_rate = breakdown_rate
    @maintenance_cycle_in_days = maintenance_cycle_in_days
    @repair_rate = 1.0 / mean_repair_time
    @halt_time = halt_time
  end

  # init method kickstarts a simplekit model.  State variables are
  # set to initial values, and some preliminary events get scheduled
  def init
    @num_available_jeeps = @max_jeeps
    @num_available_mechanics = @max_maintainers
    @maintenance_q_length = 0
    @breakdown_q_length = 0
    @num_available_jeeps.times do
      breakdown_time = exponential(@breakdown_rate)
      if (breakdown_time <= @maintenance_cycle_in_days)
        schedule(:breakdown, breakdown_time)
      else
        schedule(:maintenance, @maintenance_cycle_in_days)
      end
    end
    schedule(:halt, @halt_time)
    STDOUT.puts 'DailyAoReport'
    schedule(:daily_report, 0.0)
  end

  # Event methods follow...

  def daily_report
    STDOUT.printf "%d\n", @num_available_jeeps if model_time < @halt_time
    schedule(:daily_report, 8.0)
  end

  def breakdown
    @breakdown_q_length += 1
    @num_available_jeeps -= 1
    schedule(:begin_breakdown_service, 0.0) if @num_available_mechanics > 0
  end

  def maintenance
    @maintenance_q_length += 1
    @num_available_jeeps -= 1
    schedule(:begin_maintenance_service, 0.0) if @num_available_mechanics > 0
  end

  def begin_maintenance_service
    @maintenance_q_length -= 1
    @num_available_mechanics -= 1
    if (rand <= 0.95)
      schedule(:end_service, (6.5 - rand) / 8.0)
    else
      schedule(:end_service, exponential(@repair_rate / 4.0))
    end
  end

  def begin_breakdown_service
    @breakdown_q_length -= 1
    @num_available_mechanics -= 1
    if (rand <= 0.8)
      schedule(:end_service, exponential(@repair_rate))
    else
      schedule(:end_service, exponential(@repair_rate / 4.0))
    end
  end

  def end_service
    @num_available_mechanics += 1
    @num_available_jeeps += 1
    if @maintenance_q_length > 0
      schedule(:begin_maintenance_service, 0.0)
    else
      schedule(:begin_breakdown_service, 0.0) if @breakdown_q_length > 0
    end
    breakdown_time = exponential(@breakdown_rate)
    if (breakdown_time <= @maintenance_cycle_in_days)
      schedule(:breakdown, breakdown_time)
    else
      schedule(:maintenance, @maintenance_cycle_in_days)
    end
  end
end

# Run model based on command-line arguments...
if (ARGV.length != 6)
  STDERR.puts "\nMust supply six command-line arguments:\n"
  STDERR.puts "\tInitial Stock level (int)"
  STDERR.puts "\t#Maintenance personnel (int)"
  STDERR.puts "\tNormal breakdown rate (double)"
  STDERR.puts "\tMaintenance cycle length (int)"
  STDERR.puts "\tMean repair time (double)"
  STDERR.puts "\tNumber of days to run (int)"
  prog_name = File.basename($PROGRAM_NAME)
  STDERR.puts "\nExample: ruby #{prog_name} 50 2 0.01 90 3.0 50\n"
else
  max_jeeps = ARGV[0].to_i
  max_maintainers = ARGV[1].to_i
  breakdown_rate = ARGV[2].to_f
  maintenance_cycle_in_days = ARGV[3].to_i
  mean_repair_time = ARGV[4].to_f
  halt_time_in_days = ARGV[5].to_i
  AoModel.new(max_jeeps, max_maintainers, breakdown_rate,
              maintenance_cycle_in_days, mean_repair_time,
              8.0 * halt_time_in_days).run
end