#
# Author:: Richard Lavey (richard.lavey@calastone.com)
# Cookbook:: windows
# Provider:: dns
#
# Copyright:: 2015, Calastone Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# See this for info on DNSCMD
# https://technet.microsoft.com/en-gb/library/cc772069.aspx#BKMK_10

include Windows::Helper

action :create do
  if @current_resource.exists
    needs_change = (@new_resource.record_type != @current_resource.record_type) ||
                   (@new_resource.ttl > 0 && @new_resource.ttl != @current_resource.ttl) ||
                   (@new_resource.target.is_a?(String) && @new_resource.target != @current_resource.target) ||
                   (@new_resource.target.is_a?(Array) && !(@new_resource.target - @current_resource.target).empty?)

    if needs_change
      converge_by("Changing #{@new_resource.host_name}") do
        update_dns
      end
    else
      Chef::Log.debug("#{@new_resource.host_name} already exists - nothing to do")
    end
  else
    converge_by("Creating #{@new_resource.host_name}") do
      update_dns
    end
  end
end

action :delete do
  if @current_resource.exists
    converge_by("Deleting #{@current_resource.host_name}") do
      execute_command! 'recorddelete', "#{@current_resource.record_type} /f"
    end
  else
    Chef::Log.debug("#{@new_resource.host_name} does not exist - nothing to do")
  end
end

def load_current_resource
  # validate the new resource params : A records should be an array
  if @new_resource.record_type == 'A' && @new_resource.target.is_a?(String)
    raise 'target property must be an array for record_type A'
  end

  @current_resource = Chef::Resource::WindowsDns.new(@new_resource.name)
  @current_resource.host_name(@new_resource.host_name)
  @current_resource.dns_server(@new_resource.dns_server)

  parts = @current_resource.host_name.scan(/(\w+)\.(.*)/)
  @host = parts[0][0]
  @domain = parts[0][1]

  fetch_attributes
end

private

def fetch_attributes
  @command = locate_sysnative_cmd('dnscmd.exe')
  cmd = shell_out("#{@command} #{@current_resource.dns_server} /enumrecords #{@domain} #{@host}")
  Chef::Log.debug "dnscmd reports: #{cmd.stdout}"

  # extract values from returned text
  if cmd.stdout.include?('DNS_ERROR_NAME_DOES_NOT_EXIST')
    @current_resource.exists = false
    @current_resource.target([])
  elsif cmd.exitstatus == 0
    @current_resource.exists = true

    m = cmd.stdout.scan(/(\d+)\s(A)\s+(\d+\.\d+\.\d+\.\d+)/)
    if m.empty?
      m = cmd.stdout.scan(/(\d+)\s(CNAME)\s+((?:\w+\.)+)/)
      if m.empty?
        @current_resource.exists = false
        @current_resource.target([])
      else
        # We have a cname record
        @current_resource.record_type('CNAME')
        @current_resource.ttl(m[0][0].to_i)
        @current_resource.target(m[0][2].chomp('.'))
      end
    else
      # we have A entries
      @current_resource.record_type('A')
      @current_resource.ttl(m[0][0].to_i)
      addresses = []
      m.each do |match|
        addresses.push(match[2])
      end
      @current_resource.target(addresses)
    end
  else
    raise "dnscmd returned error #{cmd.exitstatus} : #{cmd.stderr} #{cmd.stdout}"
  end
end

def update_dns
  ttl = @new_resource.ttl if @new_resource.ttl > 0

  if @current_resource.record_type != @new_resource.record_type
    # delete current record(s) as we're changing the type
    execute_command! 'recorddelete', "#{@current_resource.record_type} /f"
  end

  if @new_resource.record_type == 'A'
    # delete existing records that are no longer defined
    (@current_resource.target - @new_resource.target).each do |address|
      Chef::Log.info "Deleting #{address}"
      execute_command! 'recorddelete', "A #{address} /f"
    end

    # add new records that don't exist
    # if ttl has changed then update all records
    addresses = if @current_resource.ttl == @new_resource.ttl
                  (@new_resource.target - @current_resource.target)
                else
                  @new_resource.target
                end
    addresses.each do |address|
      Chef::Log.info "Adding/Changing #{address}"
      execute_command! 'recordadd', "#{ttl} A #{address}"
    end
  else
    execute_command! 'recordadd', "#{ttl} CNAME #{@new_resource.target}"
  end
end

def execute_command!(mode, options)
  shell_out!("#{@command} #{@current_resource.dns_server} /#{mode} #{@domain} #{@host} #{options}")
end
