# stolen wholesale from capistrano, thanks Jamis!
require 'fileutils'
require 'json'

module EY
  class DeployBase < Task
    # default task
    def deploy
      update_repository_cache
      require_custom_tasks
      push_code

      puts "~> Starting full deploy"

      copy_repository_cache
      bundle
      symlink_configs
      migrate
      symlink
      restart
      cleanup

      puts "~> finalizing deploy"
    end

    # task
    def push_code
      puts "~> Pushing code to all servers"
      EY::Server.all.each do |server|
        server.push_code
      end
    end

    # task
    def restart
      puts "~> Restarting app servers"
      roles :app_master, :app, :solo do
        restart_command = case c.stack
        when "nginx_unicorn"
          "/etc/init.d/unicorn_#{c.app} restart"
        when "nginx_mongrel"
          "monit restart all -g #{c.app}"
        when "nginx_passenger", "apache_passenger"
          "touch #{c.latest_release}/tmp/restart.txt"
        end
        if restart_command
          puts "~> restarting app: #{c.latest_release}"
          sudo("cd #{c.current_path} && INLINEDIR=/tmp #{c.framework_envs} #{restart_command}")
        end
        callback(:after_restart)
      end
    end

    # task
    def bundle
      roles :app_master, :app, :solo do
        if File.exist?("#{c.latest_release}/Gemfile")
          puts "~> Gemfile detected, bundling gems"
          run %|cd #{c.latest_release} && bundle install|
        end
      end
    end

    # task
    def cleanup
      puts "~> cleaning up old releases"
      sudo "ls #{c.release_dir} | head -n -3 | xargs -I{} rm -rf #{c.release_dir}/{}"
    end

    # task
    def rollback
      puts "~> rolling back to previous release"
      symlink(c.previous_release)
      FileUtils.rm_rf c.latest_release
      puts "~> restarting with previous release"
      restart
    end

    # task
    def migrate
      roles :app_master, :solo do
        if c.migrate?
          callback(:before_migrate)
          puts "~> migrating"
          cmd = "cd #{c.latest_release} && #{c.framework_envs} #{c.migration_command}"
          puts "~> Migrating: #{cmd}"
          run(cmd)
        end
      end
    end

    # task
    def copy_repository_cache
      puts "~> copying to #{c.release_path}"
      sudo("mkdir -p #{c.release_path} && rsync -aq #{c.exclusions} #{c.repository_cache}/* #{c.release_path}")

      puts "~> ensuring proper ownership"
      sudo("chown -R #{c.user}:#{c.group} #{c.deploy_to}")
    end

    def symlink_configs(release_to_link=c.latest_release)
      puts "~> Symlinking configs"
      sudo [ "chmod -R g+w #{release_to_link}",
        "rm -rf #{release_to_link}/log #{release_to_link}/public/system #{release_to_link}/tmp/pids",
        "mkdir -p #{release_to_link}/tmp",
        "ln -nfs #{c.shared_path}/log #{release_to_link}/log",
        "mkdir -p #{release_to_link}/public",
        "mkdir -p #{release_to_link}/config",
        "ln -nfs #{c.shared_path}/system #{release_to_link}/public/system",
        "ln -nfs #{c.shared_path}/pids #{release_to_link}/tmp/pids",
        "ln -nfs #{c.shared_path}/config/database.yml #{release_to_link}/config/database.yml",
        "chown -R #{c.user}:#{c.group} #{release_to_link}"
        ].join(" && ")
    end

    # task
    def symlink(release_to_link=c.latest_release)
      callback(:before_symlink)
      puts "~> symlinking code"
      begin
        sudo "rm -f #{c.current_path} && ln -nfs #{release_to_link} #{c.current_path} && chown -R #{c.user}:#{c.group} #{c.current_path}"
      rescue => e
        sudo "rm -f #{c.current_path} && ln -nfs #{c.previous_release(release_to_link)} #{c.current_path} && chown -R #{c.user}:#{c.group} #{c.current_path}"
        sudo "rm -rf #{release_to_link}"
        raise e
      end
    end

    # before_symlink
    # before_restart

    class CallbackContext
      def initialize(deploy)
        @deploy = deploy
      end

      def method_missing(meth, *args, &blk)
        if @deploy.respond_to?(meth)
          @deploy.send(meth, *args, &blk)
        elsif @deploy.config.respond_to?(meth)
          @deploy.config.send(meth, *args, &blk)
        else
          super
        end
      end

      def respond_to(meth)
        if @deploy.respond_to?(meth) || @deploy.config.respond_to?(meth)
          true
        else
          super
        end
      end
    end

    def callback_context
      @context ||= CallbackContext.new(self)
    end

    def callback(what)
      if File.exist?("#{c.latest_release}/deploy/#{what}.rb")
        Dir.chdir(c.latest_release) do
          puts "~> running deploy hook: deploy/#{what}.rb"
          callback_context.instance_eval(IO.read("#{c.latest_release}/deploy/#{what}.rb"))
        end
      end
    end

  end

  class Deploy < DeployBase
    def self.new(opts={})
      # include the correct fetch strategy
      include EY::Strategies.const_get(opts.strategy)::Helpers
      super
    end

    def self.run(opts={})
      conf = EY::Deploy::Configuration.new(opts)
      EY::Server.config = conf

      dep = new(conf)
      dep.require_custom_tasks
      dep.send(opts["default_task"])
    end
  end
end