namespace :load do

task :defaults do
  set :puma_default_hooks, -> { true }
  set :puma_role, :app
  set :puma_env, -> { fetch(:rack_env, fetch(:rails_env, fetch(:stage))) }
  # Configure "min" to be the minimum number of threads to use to answer
  # requests and "max" the maximum.
  set :puma_threads, [0, 16]
  set :puma_workers, 0
  set :puma_rackup, -> { File.join(current_path, 'config.ru') }
  set :puma_state, -> { File.join(shared_path, 'tmp', 'pids', 'puma.state') }
  set :puma_pid, -> { File.join(shared_path, 'tmp', 'pids', 'puma.pid') }
  set :puma_bind, -> { File.join("unix://#{shared_path}", 'tmp', 'sockets', 'puma.sock') }
  set :puma_default_control_app, -> { File.join("unix://#{shared_path}", 'tmp', 'sockets', 'pumactl.sock') }
  set :puma_conf, -> { File.join(shared_path, 'puma.rb') }
  set :puma_access_log, -> { File.join(shared_path, 'log', 'puma_access.log') }
  set :puma_error_log, -> { File.join(shared_path, 'log', 'puma_error.log') }
  set :puma_init_active_record, false
  set :puma_preload_app, false

  # Chruby, Rbenv and RVM integration
  append :chruby_map_bins, 'puma', 'pumactl'
  append :rbenv_map_bins, 'puma', 'pumactl'
  append :rvm_map_bins, 'puma', 'pumactl'

  # Bundler integration
  append :bundle_bins, 'puma', 'pumactl'
end

end

namespace :deploy do

before :starting, :check_puma_hooks do
  invoke 'puma:add_default_hooks' if fetch(:puma_default_hooks)
end

end

namespace :puma do

desc 'Setup Puma config file'
task :config do
  on roles(fetch(:puma_role)) do |role|
    template_puma 'puma', fetch(:puma_conf), role
  end
end

desc 'Start puma'
task :start do
  on roles (fetch(:puma_role)) do |role|
    puma_switch_user(role) do
      if test "[ -f #{fetch(:puma_conf)} ]"
        info "using conf file #{fetch(:puma_conf)}"
      else
        invoke 'puma:config'
      end
      within current_path do
        with rack_env: fetch(:puma_env) do
          execute :puma, "-C #{fetch(:puma_conf)} --daemon"
        end
      end
    end
  end
end

%w[halt stop status].map do |command|
  desc "#{command} puma"
  task command do
    on roles (fetch(:puma_role)) do |role|
      within current_path do
        puma_switch_user(role) do
          with rack_env: fetch(:puma_env) do
            if test "[ -f #{fetch(:puma_pid)} ]"
              if test :kill, "-0 $( cat #{fetch(:puma_pid)} )"
                execute :pumactl, "-S #{fetch(:puma_state)} -F #{fetch(:puma_conf)} #{command}"
              else
                # delete invalid pid file , process is not running.
                execute :rm, fetch(:puma_pid)
              end
            else
              #pid file not found, so puma is probably not running or it using another pidfile
              warn 'Puma not running'
            end
          end
        end
      end
    end
  end
end

%w[phased-restart restart].map do |command|
  desc "#{command} puma"
  task command do
    on roles (fetch(:puma_role)) do |role|
      within current_path do
        puma_switch_user(role) do
          with rack_env: fetch(:puma_env) do
            if test "[ -f #{fetch(:puma_pid)} ]" and test :kill, "-0 $( cat #{fetch(:puma_pid)} )"
              # NOTE pid exist but state file is nonsense, so ignore that case
              execute :pumactl, "-S #{fetch(:puma_state)} -F #{fetch(:puma_conf)} #{command}"
            else
              # Puma is not running or state file is not present : Run it
              invoke 'puma:start'
            end
          end
        end
      end
    end
  end
end

task :check do
  on roles (fetch(:puma_role)) do |role|
    #Create puma.rb for new deployments
    unless  test "[ -f #{fetch(:puma_conf)} ]"
      warn 'puma.rb NOT FOUND!'
      #TODO DRY
      template_puma 'puma', fetch(:puma_conf), role
      info 'puma.rb generated'
    end
  end
end

task :smart_restart do
  if !puma_preload_app? && puma_workers.to_i > 1
    invoke 'puma:phased-restart'
  else
    invoke 'puma:restart'
  end
end

def puma_switch_user(role, &block)
  user = puma_user(role)
  if user == role.user
    block.call
  else
    as user do
      block.call
    end
  end
end

def puma_user(role)
  properties = role.properties
  properties.fetch(:puma_user) ||               # local property for puma only
  fetch(:puma_user) ||
  properties.fetch(:run_as) || # global property across multiple capistrano gems
  role.user
end

def puma_workers
  fetch(:puma_workers, 0)
end

def puma_preload_app?
  fetch(:puma_preload_app)
end

def puma_bind
  Array(fetch(:puma_bind)).collect do |bind|
    "bind '#{bind}'"
  end.join("\n")
end

def puma_plugins
  Array(fetch(:puma_plugins)).collect do |bind|
    "plugin '#{bind}'"
  end.join("\n")
end

def template_puma(from, to, role)
  [
      "lib/capistrano/templates/#{from}-#{role.hostname}-#{fetch(:stage)}.rb",
      "lib/capistrano/templates/#{from}-#{role.hostname}.rb",
      "lib/capistrano/templates/#{from}-#{fetch(:stage)}.rb",
      "lib/capistrano/templates/#{from}.rb.erb",
      "lib/capistrano/templates/#{from}.rb",
      "lib/capistrano/templates/#{from}.erb",
      "config/deploy/templates/#{from}.rb.erb",
      "config/deploy/templates/#{from}.rb",
      "config/deploy/templates/#{from}.erb",
      File.expand_path("../../templates/#{from}.rb.erb", __FILE__),
      File.expand_path("../../templates/#{from}.erb", __FILE__)
  ].each do |path|
    if File.file?(path)
      erb = File.read(path)
      upload! StringIO.new(ERB.new(erb, nil, '-').result(binding)), to
      break
    end
  end
end

task :add_default_hooks do
  after 'deploy:check', 'puma:check'
  after 'deploy:finished', 'puma:smart_restart'
end

end