module Orchestration::DHCP
  extend ActiveSupport::Concern

  included do
    after_validation :queue_dhcp
    before_destroy :queue_dhcp_destroy
    validate :ip_belongs_to_subnet?
  end

  def dhcp?
    name.present? and ip.present? and !subnet.nil? and subnet.dhcp? and managed? and capabilities.include?(:build)
  end

  def dhcp_record
    return unless dhcp? or @dhcp_record
    @dhcp_record ||= jumpstart? ? Net::DHCP::SparcRecord.new(dhcp_attrs) : Net::DHCP::Record.new(dhcp_attrs)
  end

  protected

  def set_dhcp
    dhcp_record.create
  end

  def set_dhcp_conflicts
    dhcp_record.conflicts.each{|conflict| conflict.create}
  end

  def del_dhcp
    dhcp_record.destroy
  end

  def del_dhcp_conflicts
    dhcp_record.conflicts.each{|conflict| conflict.destroy}
  end

  # where are we booting from
  def boot_server
    # if we don't manage tftp at all, we dont create a next-server entry.
    return unless tftp?

    # first try to ask our TFTP server for its boot server
    bs = tftp.bootServer
    # if that failed, trying to guess out tftp next server based on the smart proxy hostname
    bs ||= URI.parse(subnet.tftp.url).host
    # now convert it into an ip address (see http://theforeman.org/issues/show/1381)
    return to_ip_address(bs) if bs.present?

    failure _("Unable to determine the host's boot server. The DHCP smart proxy failed to provide this information and this subnet is not provided with TFTP services.")
  rescue => e
    failure _("failed to detect boot server: %s") % e
  end

  private

  # returns a hash of dhcp record settings
  def dhcp_attrs
    return unless dhcp?
    dhcp_attr = { :name => name, :filename => operatingsystem.boot_filename(self),
                  :ip => ip, :mac => mac, :hostname => name, :proxy => subnet.dhcp_proxy,
                  :network => subnet.network, :nextServer => boot_server }

    if jumpstart?
      jumpstart_arguments = os.jumpstart_params self, model.vendor_class
      dhcp_attr.merge! jumpstart_arguments unless jumpstart_arguments.empty?
    end
    dhcp_attr
  end

  def queue_dhcp
    return unless (dhcp? or (old and old.dhcp?)) and orchestration_errors?
    queue_remove_dhcp_conflicts if dhcp_conflict_detected?
    new_record? ? queue_dhcp_create : queue_dhcp_update
  end

  def queue_dhcp_create
    logger.debug "Scheduling new DHCP reservations for #{self}"
    queue.create(:name   => _("Create DHCP Settings for %s") % self, :priority => 10,
                 :action => [self, :set_dhcp]) if dhcp?

  end

  def queue_dhcp_update
    if dhcp_update_required?
      logger.debug("Detected a changed required for DHCP record")
      queue.create(:name => _("Remove DHCP Settings for %s") % old, :priority => 5,
                   :action => [old, :del_dhcp]) if old.dhcp?
      queue.create(:name   => _("Create DHCP Settings for %s") % self, :priority => 9,
                   :action => [self, :set_dhcp]) if dhcp?
    end
  end

  # do we need to update our dhcp reservations
  def dhcp_update_required?
    # IP Address / name changed
    return true if ((old.ip != ip) or (old.name != name) or (old.mac != mac) or (old.subnet != subnet))
    # Handle jumpstart
    #TODO, abstract this way once interfaces are fully used
    if self.kind_of?(Host::Base) and jumpstart?
      if !old.build? or (old.medium != medium or old.arch != arch) or
          (os and old.os and (old.os.name != os.name or old.os != os))
          return true
      end
    end
    false
  end

  def queue_dhcp_destroy
    return unless dhcp? and errors.empty?
    queue.create(:name   => _("Remove DHCP Settings for %s") % self, :priority => 5,
                 :action => [self, :del_dhcp])
    true
  end

  def queue_remove_dhcp_conflicts
    return unless dhcp? and errors.any? and errors.are_all_conflicts?
    return unless overwrite?
    logger.debug "Scheduling DHCP conflicts removal"
    queue.create(:name   => _("DHCP conflicts removal for %s") % self, :priority => 5,
                 :action => [self, :del_dhcp_conflicts]) if dhcp_record and dhcp_record.conflicting?
  end

  def ip_belongs_to_subnet?
    return if subnet.nil? or ip.nil?
    return unless dhcp?
    unless subnet.contains? ip
      errors.add(:ip, _("Does not match selected Subnet"))
      return false
    end
  rescue
    # probably an invalid ip / subnet were entered
    # we let other validations handle that
  end

  def dhcp_conflict_detected?
    # we can't do any dhcp based validations when our MAC address is defined afterwards (e.g. in vm creation)
    return false if mac.blank? or name.blank?

    # This is an expensive operation and we will only do it if the DNS validation failed. This will ensure
    # that we report on both DNS and DHCP conflicts when we offer to remove collisions. It retrieves and
    # caches the conflicting records so we must always do it when overwriting
    return false unless (errors.any? and errors.are_all_conflicts?) or overwrite?

    return false unless dhcp?
    status = true
    status = failure(_("DHCP records %s already exists") % dhcp_record.conflicts.to_sentence, nil, :conflict) if dhcp_record and dhcp_record.conflicting?
    overwrite? ? errors.are_all_conflicts? : status
  end

end
