
{ Governor } = require './governor'
config = require './config'

# note: names lowercased
costsHourly =
  'standard-1x': 0.035 # 25 usd/mnt
  'standard-2x': 0.070 # 50
  'performance-m': 0.350 # 250
  'performance-l': 0.700 # 500
  '1x': 0.050 # legacy
  '2x': 0.100 # legacy
  'px': 0.800 # legacy

calculateCosts = (computeSeconds, dynoType) ->
  costs = {}
  for role, seconds of computeSeconds
    type = dynoType[role]
    type = 'standard-1x' if not type
    type = type.toLowerCase()
    hours = (seconds/(60*60))
    cost = costsHourly[type]*hours
    costs[role] = cost
  return costs

# Accumulate the compute time used
class ComputeTimer
  constructor: () ->
    @accumulated = {} # role -> seconds
    @previousTimes = {} # role -> timestamp

  addState: (state, times) ->
    for role, s of state
      newTime = times[role]
      #console.log 'ss', role, s.current_workers
      @accumulated[role] = 0 if not @accumulated[role]?

      if @previousTimes[role]
        timeDiff = Math.ceil((newTime - @previousTimes[role])/(1000))
        increment = (timeDiff * s.current_workers)
        @accumulated[role] += increment
      if s.new_workers and s.previous_workers
        # compensate for boot-up/shutdown time?
        bootTime = 60
        #console.log 'adding', s.new_workers - s.previous_workers
        change = Math.abs(s.new_workers - s.previous_workers)
        @accumulated[role] += bootTime * change

      @previousTimes[role] = newTime
    
arrayEquals = (a, b) ->
  A = a.toString()
  B = b.toString()
  return A == B # Lazy

queueDataFromEvents = (cfg, events) ->
  # sort events time-wise
  events = events.sort (a, b) -> (a.timestamp - b.timestamp)

  # calculate full set of queue data, as they would be returned by RabbitMQ
  allRoles = Object.keys(cfg).filter((r) -> r != '*').sort()
  data = []
  lastTimestamp = 0
  lastByRole = {}
  for e in events
    if e.timestamp < lastTimestamp
      console.log 'WARN: unordered event data', e.timestamp, lastTimestamp
    lastByRole[e.role] = e
    lastTimestamp = e.timestamp

    haveRoles = Object.keys(lastByRole).sort()
    #console.log haveRoles, allRoles
    if arrayEquals haveRoles, allRoles
      queues = {}
      timestamps = {}
      for role, v of lastByRole
        queue = cfg[role].queue
        queues[queue] = v.jobs
        timestamps[role] = v.timestamp
      #console.log 'full', queues, lastByRole
      data.push
        queues: queues
        timestamps: timestamps
      lastByRole = {}

  return data

# with a given config
# replay a set of GuvScaled events
# invariant:  events are sorted according to increasing time
# determine an initial stable state, where we have internal state for all roles
# from this time on, calculate number of worker-compute-seconds (or minutes) we have
# based on this data, allow calculating cost (per role, per app, total)
#
# TODO: allow filtering on app?
# TODO: store some config identifier into events? hash of normalized config? so we can detect changes
# TODO: allow calculating min and max, costs from a config
# TODO: allow calculating typical costs, given total number of events for period

main = () ->
  # node.js only
  fs = require 'fs'

  [ interpreter, prog, configFile, eventFile ] = process.argv

  c = fs.readFileSync configFile
  cfg = config.parse c
  governor = new Governor cfg
  compute = new ComputeTimer

  #console.log governor.config

  events = JSON.parse(fs.readFileSync(eventFile))
  console.log "got #{events.length} events\n"

  # XXX: have to combine all events at a given timestamp, to give queue data for everything at once
  # if history was done per role instead of globally, this would not be neccesary
  queueData = queueDataFromEvents cfg, events

  for d in queueData
    try
      s = governor.nextState null, d.queues
      compute.addState s, d.timestamps # TODO: keep timestamp state internally?
    catch e
      console.log d.queues, s?, e
      console.log governor.history
      console.log e.stack

  types = {}
  for role, val of cfg
    types[role] = val.dynosize
#  console.log 'dyno sizes', types

#  console.log 'compute seconds', compute.accumulated
  costs = calculateCosts compute.accumulated, types
#  console.log 'costs', costs
  total = 0
  for role, v of costs
    console.log "#{role}: #{v.toFixed()} USD"
    total += v
  console.log "Total: #{total.toFixed()} USD"

main() if not module.parent
