require 'simple_deploy/stack/deployment'
require 'simple_deploy/stack/execute'
require 'simple_deploy/stack/output_mapper'
require 'simple_deploy/stack/stack_attribute_formatter'
require 'simple_deploy/stack/stack_creator'
require 'simple_deploy/stack/stack_destroyer'
require 'simple_deploy/stack/stack_formatter'
require 'simple_deploy/stack/stack_lister'
require 'simple_deploy/stack/stack_reader'
require 'simple_deploy/stack/stack_updater'
require 'simple_deploy/stack/status'

module SimpleDeploy
  class Stack

    def initialize(args)
      @environment = args[:environment]
      @name = args[:name]

      @config = SimpleDeploy.config
      @logger = SimpleDeploy.logger

      @use_internal_ips = !!args[:internal]
      @use_external_ips = !!args[:external]
      @entry = Entry.new :name => @name
    end

    def create(args)
      attributes = stack_attribute_formatter.updated_attributes args[:attributes]
      @template_file = args[:template]

      @entry.set_attributes attributes
      stack_creator.create

      @entry.save
    end

    def update(args)
      if !deployment.clear_for_deployment? && args[:force]
        deployment.clear_deployment_lock true

        Backoff.exp_periods do |p|
          sleep p
          break if deployment.clear_for_deployment?
        end
      end

      if deployment.clear_for_deployment?
        @logger.info "Updating #{@name}."
        attributes = stack_attribute_formatter.updated_attributes args[:attributes]
        @template_body = args[:template_body] || template

        @entry.set_attributes attributes
        stack_updater.update_stack attributes
        @logger.info "Update complete for #{@name}."

        @entry.save
        true
      else
        @logger.info "Not clear to update."
        false
      end
    end

    def in_progress_update(args)
      if args[:caller].kind_of? Stack::Deployment::Status
        @logger.info "Updating #{@name}."
        attributes = stack_attribute_formatter.updated_attributes args[:attributes]
        @template_body = args[:template_body] || template

        @entry.set_attributes attributes
        stack_updater.update_stack attributes
        @logger.info "Update complete for #{@name}."

        @entry.save
        true
      else
        false
      end
    end

    def deploy(force = false)
      deployment.execute force
    end

    def execute(args)
      executer.execute args
    end

    def ssh
      deployment.ssh
    end

    def destroy
      unless exists?
        @logger.error "#{@name} does not exist"
        return false
      end

      if attributes['protection'] != 'on'
        stack_destroyer.destroy
        @entry.delete_attributes
        @logger.info "#{@name} destroyed."
        true
      else
        @logger.warn "#{@name} could not be destroyed because it is protected. Run the protect subcommand to unprotect it"
        false
      end
    end

    def events(limit)
      stack_reader.events limit
    end

    def outputs
      stack_reader.outputs
    end

    def resources
      stack_reader.resources
    end

    def instances
      stack_reader.instances.map do |instance|
        instance['instancesSet'].map do |info|
          determine_ip_address(info)  
        end
      end.flatten.compact
    end

    def raw_instances
      stack_reader.instances
    end

    def status
      stack_reader.status
    end

    def wait_for_stable
      stack_status.wait_for_stable
    end

    def exists?
      status
      true
    rescue Exceptions::UnknownStack
      false
    end

    def attributes
      stack_reader.attributes
    end

    def parameters
      stack_reader.parameters
    end

    def template
      stack_reader.template
    end

    private

    def stack_creator
      @stack_creator ||= StackCreator.new :name          => @name,
                                          :entry         => @entry,
                                          :template_file => @template_file
    end

    def stack_updater
      @stack_updater ||= StackUpdater.new :name          => @name,
                                          :entry         => @entry,
                                          :template_body => @template_body
    end

    def stack_reader
      @stack_reader ||= StackReader.new :name   => @name
    end

    def stack_destroyer
      @stack_destroyer ||= StackDestroyer.new :name   => @name
    end

    def stack_status
      @status ||= Status.new :name   => @name
    end

    def stack_attribute_formatter
      @saf ||= StackAttributeFormatter.new :main_attributes => attributes
    end

    def executer
      @executer ||= Stack::Execute.new :environment => @environment,
                                       :name        => @name,
                                       :stack       => self,
                                       :instances   => instances,
                                       :ssh_user    => ssh_user,
                                       :ssh_key     => ssh_key
    end

    def deployment
      @deployment ||= Stack::Deployment.new :environment => @environment,
                                            :name        => @name,
                                            :stack       => self,
                                            :instances   => instances,
                                            :ssh_user    => ssh_user,
                                            :ssh_key     => ssh_key
    end

    def determine_ip_address(info)
      if info['vpcId']
        address = @use_external_ips ? info['ipAddress'] : info['privateIpAddress']
        unless address
          @logger.warn "Instance '#{info['instanceId']}' does not have an external address, skipping."
        end
        address
      else
        @use_internal_ips ? info['privateIpAddress'] : info['ipAddress']
      end
    end

    def ssh_key
      ENV['SIMPLE_DEPLOY_SSH_KEY'] ||= "#{ENV['HOME']}/.ssh/id_rsa"
    end

    def ssh_user
      ENV['SIMPLE_DEPLOY_SSH_USER'] ||= ENV['USER']
    end

  end
end
