require File.join(File.dirname(__FILE__), '..','..','..',
                  'puppet/provider/nova')

Puppet::Type.type(:nova_aggregate).provide(
  :nova,
  :parent => Puppet::Provider::Nova
) do

  desc "Manage nova aggregations"

  commands :nova => 'nova'

  mk_resource_methods

  def self.instances
    nova_aggregate_resources_ids().collect do |el|
      attrs = nova_aggregate_resources_attr(el['Name'])
      new(
          :ensure => :present,
          :name => attrs['Name'],
          :id => attrs['Id'],
          :availability_zone => attrs['Availability Zone'],
          :metadata => attrs['Metadata'],
          :hosts => attrs['Hosts'].sort
          )
    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 destroy
    #delete hosts first
    if not @property_hash[:hosts].nil?
      @property_hash[:hosts].each do |h|
        auth_nova("aggregate-remove-host", name, h)
      end
    end
    #now delete aggregate
    auth_nova("aggregate-delete", name)
    @property_hash[:ensure] = :absent
  end

  def create
    extras = Array.new
    #check for availability zone
    if not @resource[:availability_zone].nil? and not @resource[:availability_zone].empty?
      extras << "#{@resource[:availability_zone]}"
    end
    #run the command
    result = auth_nova("aggregate-create", resource[:name], extras)

    #get Id by Name
    #force a refresh of the aggregate list on creation
    id = self.class.nova_aggregate_resources_get_name_by_id(resource[:name], true)

    @property_hash = {
      :ensure => :present,
      :name => resource[:name],
      :id => id,
      :availability_zone => resource[:availability_zone]
      }

    #add metadata
    if not @resource[:metadata].nil? and not @resource[:metadata].empty?
      @resource[:metadata].each do |key, value|
        set_metadata_helper(resource[:name], key, value)
      end
      @property_hash[:metadata] = resource[:metadata]
    end

    #add hosts - This throws an error if the host is already attached to another aggregate!
    if not @resource[:hosts].nil? and not @resource[:hosts].empty?
      @resource[:hosts].each do |host|
        # make sure the host exists in nova, or nova will fail the call
        # this solves weird ordering issues with a compute node that's
        # not 100% up being added to the host aggregate
        if is_host_in_nova?(host)
          auth_nova("aggregate-add-host", resource[:name], "#{host}")
        else
          warning("Cannot add #{host} to host aggregate, it's not available yet in nova host-list")
        end
      end
      @property_hash[:hosts] = resource[:hosts]
    end
  end

  def is_host_in_nova?(host)
    return host==self.class.nova_get_host_by_name_and_type(host, "compute")
  end

  def hosts=(val)
    #get current hosts
    attrs = self.class.nova_aggregate_resources_attr(name)
    #remove all hosts which are not in new value list
    attrs['Hosts'].each do |h|
      if not val.include? h
        auth_nova("aggregate-remove-host", name, "#{h}")
      end
    end

    #add hosts from the value list
    val.each do |h|
      if not attrs['Hosts'].include? h
        if is_host_in_nova?(h)
          auth_nova("aggregate-add-host", name, "#{h}")
        else
          warning("Cannot add #{h} to host aggregate, it's not available yet in nova host-list")
        end
      end
    end
  end

  def set_metadata_helper(agg_id, key, value)
    auth_nova("aggregate-set-metadata", agg_id, "#{key}=#{value}")
  end

  def metadata
    #get current metadata
    attrs = self.class.nova_aggregate_resources_attr(name)
    #just ignore the availability_zone. that's handled directly by nova
    attrs['Metadata'].delete('availability_zone')
    return attrs['Metadata']
  end

  def metadata=(val)
    #get current metadata
    attrs = self.class.nova_aggregate_resources_attr(name)
    #get keys which are in current metadata but not in val. Make sure it has data first!
    if attrs['Metadata'].length > 0
      obsolete_keys = attrs['Metadata'].keys - val.keys
    end
    # clear obsolete keys. If there are any!
    if obsolete_keys
      obsolete_keys.each do |key|
        if not key.include? 'availability_zone'
          auth_nova("aggregate-set-metadata", name, "#{key}")
        end
      end
      #handle keys (with obsolete keys)
      new_keys = val.keys - obsolete_keys
    else
      #handle keys (without obsolete keys)
      new_keys = val.keys
    end
    #set new metadata if value changed
    new_keys.each do |key|
      if val[key] != attrs['Metadata'][key.to_s]
        value = val[key]
        set_metadata_helper(name, key, value)
      end
    end
  end

  def availability_zone=(val)
    auth_nova("aggregate-set-metadata", name, "availability_zone=#{val}")
  end

end
