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))) }
  set :puma_rackup, -> { File.join(current_path, "config.ru") }
  set :puma_conf, -> { File.join(shared_path, "puma.rb") }
  set :puma_pid, -> { File.join(shared_path, "tmp", "pids", "puma.pid") }
  set :puma_state, -> { File.join(shared_path, "tmp", "pids", "puma.state") }
  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_threads, [0, 16]
  set :puma_workers, 0
  set :puma_bind, "tcp://0.0.0.0:9292"
  set :puma_default_control_app, -> { File.join("unix://#{shared_path}", "tmp", "sockets", "pumactl.sock") }
  set :puma_tag, -> { fetch(:application) }
  set :puma_worker_timeout, 60
  set :puma_init_active_record, false
  set :puma_preload_app, false
  set :bundle_bins, fetch(:bundle_bins).to_a + %w(puma pumactl)
  set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a + %w(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

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

task :check do
  on roles fetch(:puma_role) do |_role|
    unless  test "[ -f #{fetch(:puma_conf)} ]"
      warn "puma.rb NOT FOUND!"
      invoke "puma:config"
      info "puma.rb generated"
    end
  end
end

desc "Export template file"
task :template do
  FileUtils.mkdir_p "config/deploy/templates/" unless File.exist? "config/deploy/templates/"
  FileUtils.cp File.expand_path("../../templates/puma.rb.erb", __FILE__), "config/deploy/templates/puma.rb.erb"
end

desc "Setup puma config file"
task :config do
  on roles(fetch(:puma_role)) do |role|
    template_puma 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.capitalize} 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.capitalize} 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)} ]") && (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 :smart_restart do
  if !fetch(:puma_preload_app) && fetch(:puma_workers).to_i > 1
    invoke "puma:phased-restart"
  else
    invoke "puma:restart"
  end
end

def puma_bind
  Array(fetch(:puma_bind)).collect do |bind|
    "bind '#{bind}'"
  end.join("\n")
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 template_puma(role)
  templates_path(role).each do |path|
    if File.file? path
      erb = File.read path
      upload! StringIO.new(ERB.new(erb).result(binding)), fetch(:puma_conf)
      break
    else
      next
    end
  end
end

def templates_path(role)
  [
    "config/deploy/templates/puma-#{fetch(:stage)}-#{role.hostname}.rb.erb",
    "config/deploy/templates/puma-#{role.hostname}.rb.erb",
    "config/deploy/templates/puma-#{fetch(:stage)}.rb.erb",
    "config/deploy/templates/puma.rb.erb",
    "lib/capistrano/templates/puma-#{fetch(:stage)}-#{role.hostname}.rb.erb",
    "lib/capistrano/templates/puma-#{role.hostname}.rb.erb",
    "lib/capistrano/templates/puma-#{fetch(:stage)}.rb.erb",
    "lib/capistrano/templates/puma.rb.erb",
    File.expand_path("../../templates/puma.rb.erb", __FILE__)
  ]
end

end