require 'eventmachine'

# Instances of this class wrap the PowerDNS I/O stream with a nice API
module EventMachine::Protocols
  module PowerDNS
    class Error < RuntimeError
      attr_reader :original_exception

      def initialize(data, exception)
        @original_exception = exception
        super "Unexpected error in the EM::P::PowerDNS:\nPowerDNS Data: #{data.inspect}\nError: #{exception}\n#{exception.backtrace}"
      end
    end

    include EM::Protocols::LineText2
    include Output

    SEPARATOR = "\t"
    EVENT_MAP = Hash.new(:receive_garbage)
    EVENT_MAP.update(
      'HELO' => :receive_raw_handshake,
      'Q'    => :receive_raw_query,
      'AXFR' => :receive_raw_axfr,
      'PING' => :receive_raw_ping
    )

    def self.start
      EM.open_keyboard(self)
    end

    def initialize
      $stdin.sync = true
      $stdout.sync = true
    end

    def receive_line(data)
      parts = data.split SEPARATOR
      type = parts.shift
      send EVENT_MAP[type], *parts
    rescue => e
      receive_error Error.new(data, e)
    end

    def receive_raw_handshake(*parts)
      @version = parts.first.to_i
      if [1,2].include?(@version)
        receive_handshake(@version)
      else
        fail "Received unexpected handshake: #{parts.inspect}"
      end
    end

    def receive_handshake(version)
      ok "Backend starting version #{version}."
    end

    class Query < Struct.new(:qname, :qclass, :qtype, :id, :remote_ip, :local_ip)
    end

    def receive_raw_query(*parts)
      if parts.size != parts_size_for_version
        fail "Received unexpected format for Q: #{parts.inspect}"
      else
        query = Query.new(*parts)
        receive_query(query)
      end
    end

    def parts_size_for_version
      @version == 1 ? 5 : 6 # already chopped off the 'Q'
    end

    # Stub method for users implementing this protocol
    def receive_query(query)
      done
    end

    def receive_raw_axfr(*parts)
      if parts.size != 1
        fail "Received unexpected format for AXFR: #{parts.inspect}"
      else
        receive_axfr(parts.first)
      end
    end

    # Stub method for users implementing this protocol
    def receive_axfr(soa_resource)
      done
    end

    def receive_raw_ping(*parts)
      receive_ping
    end

    # Stub method for users implementing this protocol
    def receive_ping
      done
    end

    def receive_garbage(line)
      fail "Unknown Question: #{line.inspect}"
    end

    def receive_error(error)
      fail "An unexpected error occurred in backend: #{error.original_exception.message}"
      raise error
    end
    module Output
    def ok(line)
      send_line "OK", line
    end

    def data(*parts)
      send_line "DATA", *parts
    end

    def log(line)
      send_line "LOG", line
    end

    def fail(line = nil)
      log(line) if line
      send_line "FAIL"
    end

    def done(line = nil)
      log(line) if line
      send_line "END"
    end

    def send_line(*parts)
      send_data parts.join(PowerDNS::Connection::SEPARATOR)
    end

    def send_data(data)
      $stdout.puts data
    end
  end
end