# encoding: UTF-8

module LibXML
  module XML
    class Error
      # Create mapping from domain constant values to keys
      DOMAIN_CODE_MAP = [:NO_ERROR, :PARSER, :TREE, :NAMESPACE, :DTD, :HTML, :MEMORY,
                         :OUTPUT, :IO, :FTP, :HTTP, :XINCLUDE, :XPATH, :XPOINTER, :REGEXP,
                         :DATATYPE, :SCHEMASP, :SCHEMASV, :RELAXNGP, :RELAXNGV, :CATALOG,
                         :C14N, :XSLT, :VALID, :CHECK, :WRITER, :MODULE, :I18N, :SCHEMATRONV].inject(Hash.new) do |hash, code|
        if const_defined?(code)
          hash[const_get(code)] = code
        end
        hash
      end

      # Create mapping from error constant values (so need to remove domain_codes) to keys
      ERROR_CODE_MAP = Hash.new.tap do |map|
        (constants -
         DOMAIN_CODE_MAP.values - #Domains
         [:NONE, :WARNING, :ERROR, :FATAL] # Levels
         ).each do |code|
          map[const_get(code)] = code
        end
      end

      # Verbose error handler
      VERBOSE_HANDLER = lambda do |error|
        STDERR << error.to_s << "\n"
        STDERR.flush
      end

      # Quiet error handler
      QUIET_HANDLER = lambda do |error|
      end

      # call-seq:
      #   error == other -> true or false
      #
      # Returns whether two errors have the same attributes.
      def ==(other)
        eql?(other)
      end

      # call-seq:
      #   error.eql?(other) -> true or false
      #
      # Returns whether two errors have identical code, domain, message,
      # level, file, line, and all other attributes.
      def eql?(other)
        self.code == other.code and
        self.domain == other.domain and
        self.message == other.message and
        self.level == other.level and
        self.file == other.file and
        self.line == other.line and
        self.str1 == other.str1 and
        self.str2 == other.str2 and
        self.str3 == other.str3 and
        self.int1 == other.int1 and
        self.int2 == other.int2 and
        self.ctxt == other.ctxt and
        self.node == other.node
      rescue
        false
      end

      # call-seq:
      #   error.level_to_s -> String
      #
      # Returns a human-readable string for the error level:
      # "Warning:", "Error:", "Fatal error:", or "".
      def level_to_s
        case self.level
          when NONE
            ''
          when WARNING
            'Warning:'
          when ERROR
            'Error:'
          when FATAL
            'Fatal error:'
        end
      end

      # call-seq:
      #   error.domain_to_s -> String
      #
      # Returns the name of the error domain (e.g. "PARSER", "XPATH").
      def domain_to_s
        DOMAIN_CODE_MAP[self.domain].to_s
      end

      # call-seq:
      #   error.code_to_s -> String
      #
      # Returns the name of the error code constant.
      def code_to_s
        ERROR_CODE_MAP[self.code].to_s
      end

      # call-seq:
      #   error.to_s -> String
      #
      # Returns a formatted error string including the level, message,
      # and file/line when available.
      def to_s
        msg = super
        msg = msg ? msg.strip: ''

        if self.line
          sprintf("%s %s at %s:%d.", self.level_to_s, msg,
                                     self.file, self.line)
        else
          sprintf("%s %s.", self.level_to_s, msg)
        end
      end
    end
  end
end

LibXML::XML::Error.set_handler(&LibXML::XML::Error::VERBOSE_HANDLER)
