require "vmc/cli/app/base"

module VMC::App
  class Start < Base
    APP_CHECK_LIMIT = 60

    desc "Start an application"
    group :apps, :manage
    input :apps, :desc => "Applications to start", :argument => :splat,
          :singular => :app, :from_given => by_name(:app)
    input :debug_mode, :desc => "Debug mode to start in", :aliases => "-d"
    input :all, :desc => "Start all applications", :default => false
    def start
      apps = input[:all] ? client.apps : input[:apps]
      fail "No applications given." if apps.empty?

      spaced(apps) do |app|
        app = filter(:start_app, app)

        switch_mode(app, input[:debug_mode])

        if app.started?
          err "Application #{b(app.name)} is already started."
          next
        end

        log = start_app(app)
        stream_start_log(log) if log
        check_application(app)

        if app.debug_mode && !quiet?
          line
          invoke :instances, :app => app
        end
      end
    end

    private

    def start_app(app)
      log = nil
      with_progress("Starting #{c(app.name, :name)}") do
        app.start!(true) do |url|
          log = url
        end
      end
      log
    end

    def stream_start_log(log)
      offset = 0

      while true
        begin
          client.stream_url(log + "&tail&tail_offset=#{offset}") do |out|
            offset += out.size
            print out
          end
        rescue Timeout::Error
        end
      end
    rescue CFoundry::APIError
    end

    # set app debug mode, ensuring it's valid, and shutting it down
    def switch_mode(app, mode)
      mode = nil if mode == "none"
      mode = "run" if mode == "" # no value given

      return false if app.debug == mode

      if mode.nil?
        with_progress("Removing debug mode") do
          app.debug = nil
          app.stop! if app.started?
        end

        return true
      end

      with_progress("Switching mode to #{c(mode, :name)}") do |s|
        app.debug = mode
        app.stop! if app.started?
      end
    end

    def check_application(app)
      if app.debug == "suspend"
        line "Application is in suspended debugging mode."
        line "It will wait for you to attach to it before starting."
        return
      end

      line "Checking #{c(app.name, :name)}..."

      seconds = 0
      while instances = app.instances
        indented { print_instances_summary(instances) }

        if all_instances_running?(instances)
          line "#{c("OK", :good)}"
          return
        end

        if any_instance_flapping?(instances) || seconds == APP_CHECK_LIMIT
          err "Application failed to start."
          return
        end

        sleep 1
        seconds += 1
      end
    end

    def all_instances_running?(instances)
      instances.all? { |i| i.state == "RUNNING" }
    end

    def any_instance_flapping?(instances)
      instances.any? { |i| i.state == "FLAPPING" }
    end

    def print_instances_summary(instances)
      counts = Hash.new { 0 }
      instances.each do |i|
        counts[i.state] += 1
      end

      states = []
      %w{RUNNING STARTING DOWN FLAPPING}.each do |state|
        if (num = counts[state]) > 0
          states << "#{b(num)} #{c(state.downcase, state_color(state))}"
        end
      end

      total = instances.count
      running = counts["RUNNING"].to_s.rjust(total.to_s.size)

      ratio = "#{running}#{d("/")}#{total} instances:"
      line "#{ratio} #{states.join(", ")}"
    end
  end
end
