module VagrantPlugins
  module VCloud
    module Action
      class ReadSSHInfo
        def initialize(app, env, port = 22)
          @app = app
          @port = port
          @logger = Log4r::Logger.new('vagrant_vcloud::action::read_ssh_info')
        end

        def call(env)
          env[:machine_ssh_info] = read_ssh_info(env)

          @app.call env
        end

        # Small method to check the tcp connection to an ip:port works.
        # Return false if anything fails, and true if it succeeded.
        def check_for_port(ip, port, port_name)
          @logger.debug("Checking #{port_name} (#{ip}:#{port})...")
          begin
            Timeout::timeout(1) do
              begin
                s = TCPSocket.new(ip, port)
                s.close
                @logger.debug("#{port_name} Connection successful !")
                return true
              rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
                @logger.debug("#{port_name} Connection Refused/Host Unreachable...")
                return false
              end
            end
          rescue Timeout::Error
            @logger.debug("#{port_name} Connection Timeout...")
          end

          return false
        end

        def read_ssh_info(env)
          return nil if env[:machine].id.nil?

          cfg = env[:machine].provider_config
          cnx = cfg.vcloud_cnx.driver
          vapp_id = env[:machine].get_vapp_id
          vm_name = cfg.name ? cfg.name.to_sym : env[:machine].name

          @logger.debug('Getting vApp information...')
          vm = cnx.get_vapp(vapp_id)
          myhash = vm[:vms_hash][vm_name.to_sym]

          if vm.nil?
            # The Virtual Machine couldn't be found.
            @logger.info(
              'Machine couldn\'t be found, assuming it got destroyed.'
            )
            machine.id = nil
            return nil
          end

          if !cfg.network_bridge.nil?
            @logger.debug(
              'We\'re running in bridged mode, ' \
              'fetching the IP directly from the VM'
            )
            vm_info = cnx.get_vm(env[:machine].id)
            @logger.debug(
              "IP address for #{vm_name}: " \
              "#{vm_info[:networks]['Vagrant-vApp-Net'][:ip]}"
            )

            @external_ip = vm_info[:networks]['Vagrant-vApp-Net'][:ip]
            @external_port = "#{@port}"
          else
            network_name = nil
            if cfg.nics
              cfg.nics.each do |nic|
                next if nic[:forwarded_port].nil?
                if !cfg.networks.nil? && !cfg.networks[:vapp].nil?
                  cfg.networks[:vapp].each do |net|
                    next if net[:name] != nic[:network]
                    network_name = net[:vdc_network_name]
                    break
                  end
                end
              end
            end

            @logger.debug('Getting port forwarding rules...')
            rules = cnx.get_vapp_port_forwarding_rules(vapp_id, network_name)

            rules.each do |rule|
              if rule[:vapp_scoped_local_id] == myhash[:vapp_scoped_local_id] && rule[:nat_internal_port] == "#{@port}"
                @external_ip = rule[:nat_external_ip]
                @external_port = rule[:nat_external_port]
                break

              end
            end

            if cfg.vdc_edge_gateway_ip && cfg.vdc_edge_gateway
              @logger.debug(
                "We're running vagrant behind an Organization vDC Edge"
              )

              #
              # Add config.ssh.host support
              # http://docs.vagrantup.com/v2/vagrantfile/ssh_settings.html
              #
              if env[:machine].config.ssh.host
                @logger.debug(
                  'SSH Host setting configured too: ' \
                  "#{env[:machine].config.ssh.host}"
                )
                @external_ip = env[:machine].config.ssh.host
              else
                @logger.debug(
                  "Using Edge Gateway IP: #{cfg.vdc_edge_gateway_ip}"
                )
                @external_ip = cfg.vdc_edge_gateway_ip
              end
            end
          end

          port_name = "SSH"
          if @port == 5985
            port_name = "WinRM"
          end

          @logger.debug(
            "#{port_name} INFO: IP #{@external_ip} and Port #{@external_port}"
          )

          # tsugliani: Temporary Fix for Issue #56
          # SSH unavailable makes the deployment fails.
          # Wait infinitely right now for SSH...
          # sleep_counter incremented by 1s each loop.
          #
          # This should be fixed with implementing Vagrant::Util::Retryable
          # and something like:
          #
          # retryable(:on => Vagrant::Errors::SSHSomething, :tries => 10, :sleep => 5) do
          #   check_for_port(ip, port, "SSH", :error_class => Vagrant::Errors::SSHSomething)
          # end
          #
          sleep_counter = 5

          if @port == 22 || @port == 5985
            while check_for_port(@external_ip, @external_port, port_name) == false
              env[:ui].info(
                "Waiting for #{port_name} Access on #{@external_ip}:#{@external_port} ... "
              )
              sleep sleep_counter
              sleep_counter += 1
            end
          end

          # If we are here, then SSH is ready, continue
          {
            :host => @external_ip,
            :port => @external_port
          }
        end
      end
    end
  end
end
