require 'htree/doc'
require 'htree/elem'
require 'htree/leaf'
require 'htree/tag'
require 'htree/raw_string'
require 'htree/context'

module HTree
  # compare tree structures.
  def ==(other)
    check_equality(self, other, :usual_equal_object)
  end
  alias eql? ==

  # hash value for the tree structure.
  def hash
    return @hash_code if defined? @hash_code
    @hash_code = usual_equal_object.hash
  end

  # :stopdoc:

  def usual_equal_object
    return @usual_equal_object if defined? @usual_equal_object
    @usual_equal_object = make_usual_equal_object
  end

  def make_usual_equal_object
    raise NotImplementedError
  end

  def exact_equal_object
    return @exact_equal_object if defined? @exact_equal_object
    @exact_equal_object = make_exact_equal_object
  end

  def make_exact_equal_object
    raise NotImplementedError
  end

  def exact_equal?(other)
    check_equality(self, other, :exact_equal_object)
  end

  def check_equality(obj1, obj2, equal_object_method)
    return false unless obj1.class == obj2.class
    if obj1.class == Array
      return false unless obj1.length == obj2.length
      obj1.each_with_index {|c1, i|
        return false unless c1.class == obj2[i].class
      }
      obj1.each_with_index {|c1, i|
        return false unless check_equality(c1, obj2[i], equal_object_method)
      }
      true
    elsif obj1.respond_to? equal_object_method
      o1 = obj1.send(equal_object_method)
      o2 = obj2.send(equal_object_method)
      check_equality(o1, o2, equal_object_method)
    else
      obj1 == obj2
    end
  end

  class Doc
    alias exact_equal_object children
    alias usual_equal_object children
  end

  class Elem
    def make_exact_equal_object
      [@stag, @children, @empty, @etag]
    end

    def make_usual_equal_object
      [@stag, @children]
    end
  end

  class Name
    def make_exact_equal_object
      [@namespace_prefix, @namespace_uri, @local_name]
    end

    def make_usual_equal_object
      xmlns? ? @local_name : [@namespace_uri, @local_name]
    end
  end

  module Util
    module_function
    def cmp_with_nil(a, b)
      if a == nil
        if b == nil
          0
        else
          -1
        end
      else
        if b == nil
          1
        else
          a <=> b
        end
      end
    end
  end

  class Context
    def make_exact_equal_object
      @namespaces.keys.sort {|prefix1, prefix2|
        Util.cmp_with_nil(prefix1, prefix2)
      }.map {|prefix| [prefix, @namespaces[prefix]] }
    end

    # make_usual_equal_object is not used through STag#make_usual_equal_object
    # NotImplementedError is suitable?
    alias make_usual_equal_object make_exact_equal_object
  end

  class STag
    def make_exact_equal_object
      [@raw_string,
       @name,
       @attributes.sort {|(n1, _), (n2, _)|
         Util.cmp_with_nil(n1.namespace_prefix, n2.namespace_prefix).nonzero? ||
         Util.cmp_with_nil(n1.namespace_uri, n2.namespace_uri).nonzero? ||
         Util.cmp_with_nil(n1.local_name, n2.local_name)
        },
        @inherited_context
      ]
    end

    def make_usual_equal_object
      [@name,
       @attributes.find_all {|n,t| !n.xmlns? }.sort {|(n1, _), (n2, _)|
         Util.cmp_with_nil(n1.namespace_prefix, n2.namespace_prefix).nonzero? ||
         Util.cmp_with_nil(n1.namespace_uri, n2.namespace_uri).nonzero? ||
         Util.cmp_with_nil(n1.local_name, n2.local_name)
        }
      ]
    end

  end

  class ETag
    def make_exact_equal_object
      [@raw_string, @qualified_name]
    end

    alias usual_equal_object qualified_name
  end

  class Text
    def make_exact_equal_object
      [@raw_string, @rcdata]
    end

    def make_usual_equal_object
      @normalized_rcdata
    end
  end

  class XMLDecl
    def make_exact_equal_object
      [@raw_string, @version, @encoding, @standalone]
    end

    def make_usual_equal_object
      [@version, @encoding, @standalone]
    end
  end

  class DocType
    def make_exact_equal_object
      [@raw_string, @root_element_name, @system_identifier, @public_identifier]
    end

    def make_usual_equal_object
      [@root_element_name, @system_identifier, @public_identifier]
    end
  end

  class ProcIns
    def make_exact_equal_object
      [@raw_string, @target, @content]
    end

    def make_usual_equal_object
      [@target, @content]
    end
  end

  class Comment
    def make_exact_equal_object
      [@raw_string, @content]
    end

    alias usual_equal_object content
  end

  class BogusETag
    def make_exact_equal_object
      [@etag]
    end

    alias usual_equal_object make_exact_equal_object
  end

  class Location
    def make_exact_equal_object
      [@parent, @index, @node]
    end

    alias usual_equal_object make_exact_equal_object
  end

  # :startdoc:
end
