require 'awesome_print'
require 'terminal-table'

module VagrantPlugins
  module VCloud
    class Command < Vagrant.plugin('2', :command)
      def self.synopsis
        'namespace to interact with vCloud Director specifics [vcloud provider only]'
      end

      def command_vcloud_status(cfg, vapp_id)
        # Set our handlers to the driver and objects

        puts "Fetching vCloud Director status..."
        cnx = cfg.vcloud_cnx.driver
        vapp = cnx.get_vapp(vapp_id)

        organization = cnx.get_organization_by_name(cfg.org_name)
        cfg.vdc_id = cnx.get_vdc_id_by_name(organization, cfg.vdc_name)

        # Create a new table for the general information
        table = Terminal::Table.new
        table.title = "Vagrant vCloud Director Status : #{cfg.hostname}"

        table << ['Organization Name', cfg.org_name]
        table << ['Organization vDC Name', cfg.vdc_name]
        table << ['Organization vDC ID', cfg.vdc_id]
        table << ['Organization vDC Network Name', cfg.vdc_network_name]
        table << ['Organization vDC Edge Gateway Name',
                  cfg.vdc_edge_gateway] unless cfg.vdc_edge_gateway.nil?
        table << ['Organization vDC Edge IP',
                  cfg.vdc_edge_gateway_ip] unless cfg.vdc_edge_gateway_ip.nil?
        table << :separator
        table << ['vApp Name', vapp[:name]]
        table << ['vAppID', vapp_id]

        vapp[:vms_hash].each do |vm|
          # This should be checked indivudually
          # When 1 VM is destroyed, ID is still populated, should be cleaned.
          table << ["-> #{vm[0]}", vm[1][:id]]
        end

        # Print the General Information Table
        puts table
      end

      def command_vcloud_network(cfg, vapp_id, ssh_host)
        # FIXME: this needs to be fixed to accomodate the bridged scenario
        # potentially showing only the assigned IPs in the VMs

        puts 'Fetching vCloud Director network settings ...'
        cnx = cfg.vcloud_cnx.driver
        vapp = cnx.get_vapp(vapp_id)

        organization = cnx.get_organization_by_name(cfg.org_name)
        cfg.vdc_id = cnx.get_vdc_id_by_name(organization, cfg.vdc_name)

        if !cfg.network_bridge.nil?
          # Create a new table for the network information
          network_table = Terminal::Table.new
          network_table.title = 'Network Map'

          network_table << ['VM Name', 'IP Address', 'Connection']
          network_table << :separator

          vapp[:vms_hash].each do |vm|
            network_table << [vm[0], vm[1][:addresses][0], 'Direct']
          end
        else
          vapp_edge_ip = cnx.get_vapp_edge_public_ip(vapp_id)
          vapp_edge_rules = cnx.get_vapp_port_forwarding_rules(vapp_id)
          edge_gateway_rules = cnx.get_edge_gateway_rules(cfg.vdc_edge_gateway,
                                                          cfg.vdc_id)

          # Create a new table for the network information
          network_table = Terminal::Table.new
          network_table.title = 'Vagrant vCloud Director Network Map'

          network_table << ['VM Name', 'Destination NAT Mapping', 'Enabled']
          network_table << :separator

          # Fetching Destination NAT Rules for each vApp/Edge/VM/Mapping
          vapp_edge_rules.each do |vapp_edge_rule|
            edge_gateway_rule = edge_gateway_rules.find {|r|
                    (r[:rule_type] == 'DNAT' &&
                     r[:original_ip] == cfg.vdc_edge_gateway_ip &&
                     r[:translated_ip] == vapp_edge_ip)}

            # Loop on every VM in the vApp
            vapp[:vms_hash].each do |vm|
              # Only Map valid vAppEdge scope to VM scope
              vm_scope = vm[1][:vapp_scoped_local_id]
              vapp_edge_scope = vapp_edge_rule[:vapp_scoped_local_id]

              if vm_scope == vapp_edge_scope
                # Generate DNAT Mappings for the valid machines
                # If rules don't match, you will not see them !
                if edge_gateway_rule
                  # DNAT rule from edge to vapp to vm
                  connect_host = nil

                  # Add support for config.ssh.host
                  if ssh_host
                    connect_host = "#{ssh_host}:" +
                                   "#{vapp_edge_rule[:nat_external_port]}" +
                                   ' -> ' +
                                   "#{cfg.vdc_edge_gateway_ip}:" +
                                   "#{vapp_edge_rule[:nat_external_port]}"
                  else
                    connect_host = "#{cfg.vdc_edge_gateway_ip}:" +
                                   "#{vapp_edge_rule[:nat_external_port]}"
                  end

                  network_table << [
                    "#{vm[0]}",
                    "#{connect_host}" +
                    " -> #{vapp_edge_ip}:" +
                    "#{vapp_edge_rule[:nat_external_port]}" +
                    " -> #{vm[1][:addresses][0]}:" +
                    "#{vapp_edge_rule[:nat_internal_port]}",
                    edge_gateway_rule[:is_enabled]
                  ]
                else
                  # DNAT rule only from vapp to vm
                  network_table << [
                    "#{vm[0]}",
                    "#{vapp_edge_ip}:" +
                    "#{vapp_edge_rule[:nat_external_port]}" +
                    " -> #{vm[1][:addresses][0]}:" +
                    "#{vapp_edge_rule[:nat_internal_port]}",
                    true
                  ]
                end
              end
            end
          end

          # Fetching Source NAT Rules for the vApp
          network_table << :separator
          network_table << ['Network Name', 'Source NAT Mapping', 'Enabled']
          network_table << :separator

          edge_gateway_rules.each do |edge_gateway_rule|
            # Only check SNAT and src/dst
            if edge_gateway_rule[:rule_type] == 'SNAT' &&
               edge_gateway_rule[:original_ip] == vapp_edge_ip &&
               edge_gateway_rule[:translated_ip] == cfg.vdc_edge_gateway_ip

              network_table << [
                edge_gateway_rule[:interface_name],
                "#{vapp_edge_ip} -> #{cfg.vdc_edge_gateway_ip}",
                edge_gateway_rule[:is_enabled]
              ]
            end
          end

          # Fetching Edge Gateway Firewall Rules
          network_table << :separator
          network_table << ['Rule# - Description', 'Firewall Rules', 'Enabled']
          network_table << :separator
          edge_gateway_rules.each do |edge_gateway_rule|
            # Only add firewall rules
            if edge_gateway_rule[:rule_type] == 'Firewall'
              network_table << [
                "#{edge_gateway_rule[:id]} - " +
                "(#{edge_gateway_rule[:description]})",
                "#{edge_gateway_rule[:policy]} " +
                "SRC:#{edge_gateway_rule[:source_ip]}:" +
                "#{edge_gateway_rule[:source_portrange]} to " +
                "DST:#{edge_gateway_rule[:destination_ip]}:" +
                "#{edge_gateway_rule[:destination_portrange]}",
                "#{edge_gateway_rule[:is_enabled]}"
              ]
            end
          end
        end
        # Print the Network Table
        puts network_table
      end

      def command_vcloud_redeploy_edge_gw(cfg)
        cnx = cfg.vcloud_cnx.driver

        organization = cnx.get_organization_by_name(cfg.org_name)
        cfg.vdc_id = cnx.get_vdc_id_by_name(organization, cfg.vdc_name)

        edge_gw_id = cnx.find_edge_gateway_id(cfg.vdc_edge_gateway, cfg.vdc_id)
        task_id = cnx.redeploy_edge_gateway(edge_gw_id)

        puts "Redeploying #{cfg.vdc_edge_gateway} vShield Edge Gateway... " +
             '(This task can take a few minutes)'
        cnx.wait_task_completion(task_id)
        puts 'Done'
      end

      def execute
        options = {}
        opts = OptionParser.new do |o|
          o.banner = 'Usage: vagrant vcloud [options]'

          # We can probably extend this if needed for specific information
          o.on(
            '-n',
            '--network',
            'Display the vCloud Director network mapping information'
          ) do |f|
            options[:network] = true
          end

          o.on(
            '-s',
            '--status',
            'Display the vCloud Director objects IDs'
          ) do |f|
            options[:status] = true
          end

          o.on(
            '-r',
            '--redeploy-edge-gw',
            'Redeploy the vCloud Director Edge Gateway'
          ) do |f|
            options[:redeploy_edge_gw] = true
          end

        end

        @argv = parse_options(opts)
        return unless @argv

        # If no arguments, print help
        if options.keys.count() == 0
          puts opts
          exit 1
        end

        puts 'Initializing vCloud Director provider...'
        # initialize some variables
        ssh_host = nil
        vapp_id = nil
        cfg = nil

        # Go through the vagrant machines
        with_target_vms(@argv) do |machine|

          # FIXME/Bug: why does the provider switch to virtualbox when
          # destroying VMs within the the vApp:
          # .vagrant/machines/<machine>/virtualbox Cannot trace why this
          # happens :-( (tsugliani)
          if machine.provider_name != :vcloud
            # Not a vCloud Director provider, exit with explicit error message
            puts "#{machine.provider_name} provider is incompatible with " +
                  'this command'
            exit 1
          end

          # Force reloads on objects by fetching the ssh_info
          machine.provider.ssh_info

          # populate cfg & vApp Id for later use.
          cfg = machine.provider_config
          vapp_id = machine.get_vapp_id
          ssh_host = machine.config.ssh.host
          break
        end

        # iterate through each option and call the according command.
        options.keys.each do |key|
          case key
          when :status
            command_vcloud_status(cfg, vapp_id)
          when :network
            command_vcloud_network(cfg, vapp_id, ssh_host)
          when :redeploy_edge_gw
            command_vcloud_redeploy_edge_gw(cfg)
          end
        end

        0
      end
    end
  end
end
