require File.join(File.dirname(__FILE__), "..","..","..",
                  "puppet/provider/neutron")

Puppet::Type.type(:neutron_port).provide(
  :neutron,
  :parent => Puppet::Provider::Neutron
) do
  desc <<-EOT
    Neutron provider to manage neutron_port type.

    Assumes that the neutron service is configured on the same host.
  EOT
  #TODO No security group support

  mk_resource_methods

  def self.instances
    list_neutron_resources("port").collect do |id|
      attrs = get_neutron_resource_attrs("port", id)
      attrs["name"] = attrs["id"] if attrs["name"].empty?
      new(
        :ensure           => :present,
        :name             => attrs["name"],
        :id               => attrs["id"],
        :status           => attrs["status"],
        :tenant_id        => attrs["tenant_id"],
        :network_id       => attrs["network_id"],
        :admin_state_up   => attrs["admin_state_up"],
        :network_name     => get_network_name(attrs["network_id"]),
        :subnet_name      => get_subnet_name(parse_subnet_id(attrs["fixed_ips"])),
        :subnet_id        => parse_subnet_id(attrs["fixed_ips"]),
        :ip_address       => parse_ip_address(attrs["fixed_ips"]),
        :binding_profile  => parse_binding_profile_interface_name(attrs["binding:profile"]),
        :binding_host_id  => attrs["binding:host_id"],
      )
    end
  end

  def self.prefetch(resources)
    instances_ = instances
    resources.keys.each do |name|
      if provider = instances_.find{ |instance| instance.name == name }
        resources[name].provider = provider
      end
    end
  end

  def exists?
    @property_hash[:ensure] == :present
  end

  def create
    opts = Array.new
    dict_opts = Array.new

    if @resource[:admin_state_up] == "False"
      opts << "--admin-state-down"
    end

    if @resource[:ip_address]
      # The spec says that multiple ip addresses may be specified, but this
      # doesn't seem to work yet.
      opts << "--fixed-ip"
      opts << resource[:ip_address].map{|ip|"ip_address=#{ip}"}.join(',')
    end

    if @resource[:subnet_name]
      # The spec says that multiple subnets may be specified, but this doesn't
      # seem to work yet.
      opts << "--fixed-ip"
      opts << resource[:subnet_name].map{|s|"subnet_id=#{s}"}.join(',')
    end

    if @resource[:tenant_name]
      tenant_id = self.class.get_tenant_id(
        @resource.catalog,
        @resource[:tenant_name]
      )
      opts << "--tenant_id=#{tenant_id}"
    elsif @resource[:tenant_id]
      opts << "--tenant_id=#{@resource[:tenant_id]}"
    end

    if @resource[:binding_host_id]
      opts << "--binding:host_id=#{@resource[:binding_host_id]}"
    end

    if @resource[:binding_profile]
      binding_profile_opts = @resource[:binding_profile].map{|k,v| "#{k}=#{v}"}.join(' ')
      dict_opts << "--binding:profile"
      dict_opts << "type=dict"
      dict_opts << "#{binding_profile_opts}"
    end

    results = auth_neutron(
      "port-create",
      "--format=shell",
      "--name=#{resource[:name]}",
      opts,
      resource[:network_name],
      dict_opts
    )

    attrs = self.class.parse_creation_output(results)
    @property_hash = {
      :ensure         	=> :present,
      :name           	=> resource[:name],
      :id             	=> attrs["id"],
      :status         	=> attrs["status"],
      :tenant_id      	=> attrs["tenant_id"],
      :network_id     	=> attrs["network_id"],
      :admin_state_up 	=> attrs["admin_state_up"],
      :network_name   	=> resource[:network_name],
      :subnet_name    	=> resource[:subnet_name],
      :subnet_id      	=> self.class.parse_subnet_id(attrs["fixed_ips"]),
      :ip_address     	=> self.class.parse_ip_address(attrs["fixed_ips"]),
      :binding_profile  => self.class.parse_binding_profile_interface_name(attrs["binding:profile"]),
      :binding_host_id  => attrs["binding:host_id"],
    }
  end

  def destroy
    auth_neutron("port-delete", name)
    @property_hash[:ensure] = :absent
  end

  def admin_state_up=(value)
    auth_neutron("port-update", "--admin-state-up=#{value}", name)
  end

  private

  def self.get_network_name(network_id_)
    if network_id_
      network_instances = Puppet::Type.type("neutron_network").instances
      network_name = network_instances.find do |instance|
        instance.provider.id == network_id_
      end.provider.name
    end
    network_name
  end

  def get_network_name(network_id_)
    @get_network_name ||= self.class.get_network_name(network_id_)
  end

  def self.get_subnet_name(subnet_id_)
    if subnet_id_
      subnet_ids = Array(subnet_id_)
      subnet_instances = Puppet::Type.type("neutron_subnet").instances
      subnet_names = subnet_instances.collect do |instance|
        if subnet_ids.include?(instance.provider.id)
          instance.provider.name
        else
          nil
        end
      end.compact
      if subnet_names.length > 1
        subnet_names
      else
        subnet_names.first
      end
    end
  end

  def get_subnet_name(subnet_id_)
    @subnet_name ||= self.class.subnet_name(subnet_id_)
  end

  def self.parse_subnet_id(fixed_ips_)
    subnet_ids = Array(fixed_ips_).collect do |json|
      match_data = /\{"subnet_id": "(.*)", /.match(json)
      if match_data
        match_data[1]
      else
        nil
      end
    end.compact
    if subnet_ids.length > 1
      subnet_ids
    else
      subnet_ids.first
    end
  end

  def self.parse_ip_address(fixed_ips_)
    ip_addresses = Array(fixed_ips_).collect do |json|
      match_data = /"ip_address": "(.*)"\}/.match(json)
      if match_data
        match_data[1]
      else
        nil
      end
    end.compact
    if ip_addresses.length > 1
      ip_addresses
    else
      ip_addresses.first
    end
  end

  def self.parse_binding_profile_interface_name(binding_profile_)
    match_data = /\{"interface_name": "(.*)"\}/.match(binding_profile_)
      if match_data
        match_data[1]
      else
        nil
      end
  end

end
