require 'representable/binding'

module Representable
  module XML
    module_function def Node(document, name, attributes={})
      node = Nokogiri::XML::Node.new(name.to_s, document) # Java::OrgW3cDom::DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

      attributes.each { |k, v| node[k] = v } # TODO: benchmark.
      node
    end

    class Binding < Representable::Binding
      def self.build_for(definition)
        return Collection.new(definition)      if definition.array?
        return Hash.new(definition)            if definition.hash? and not definition[:use_attributes] # FIXME: hate this.
        return AttributeHash.new(definition)   if definition.hash? and definition[:use_attributes]
        return Attribute.new(definition)       if definition[:attribute]
        return Content.new(definition)         if definition[:content]

        new(definition)
      end

      def write(parent, fragments, as)
        wrap_node = parent

        if wrap = self[:wrap]
          parent << wrap_node = XML::Node(parent, wrap)
        end

        wrap_node << serialize_for(fragments, parent, as)
      end

      def read(node, as)
        nodes = find_nodes(node, as)
        return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!

        deserialize_from(nodes)
      end

      # Creates wrapped node for the property.
      def serialize_for(value, parent, as)
        node = XML::Node(parent, as) # node doesn't have attr="" attributes!!!
        serialize_node(node, value, as)
      end

      def serialize_node(node, value, as)
        if typed?
          value.name = as if as != self[:name]
          return value
        end

        node.content = value
        node
      end

      def deserialize_from(nodes)
        content_for(nodes.first)
      end

      # DISCUSS: why is this public?
      def serialize_method
        :to_node
      end

      def deserialize_method
        :from_node
      end

    private
      def find_nodes(doc, as)
        selector  = as
        selector  = "#{self[:wrap]}/#{as}" if self[:wrap]
        doc.xpath(selector) # nodes
      end

      def content_for(node) # TODO: move this into a ScalarDecorator.
        return node if typed?

        node.content
      end


      class Collection < self
        include Representable::Binding::Collection

        def serialize_for(value, parent, as)
          # return NodeSet so << works.
          set_for(parent, value.collect { |item| super(item, parent, as) })
        end

        def deserialize_from(nodes)
          content_nodes = nodes.collect do |item| # TODO: move this to Node?
            content_for(item)
          end

          content_nodes
        end

      private
        def set_for(parent, nodes)
          Nokogiri::XML::NodeSet.new(parent.document, nodes)
        end
      end


      class Hash < Collection
        def serialize_for(value, parent, as)
          set_for(parent, value.collect do |k, v|
            node = XML::Node(parent, k)
            serialize_node(node, v, as)
          end)
        end

        def deserialize_from(nodes)
          hash = {}
          nodes.children.each do |node|
            hash[node.name] = content_for node
          end

          hash
        end
      end

      class AttributeHash < Collection
        # DISCUSS: use AttributeBinding here?
        def write(parent, value, as)  # DISCUSS: is it correct overriding #write here?
          value.collect do |k, v|
            parent[k] = v.to_s
          end
          parent
        end

        # FIXME: this is not tested!
        def deserialize_from(node)
          HashDeserializer.new(self).deserialize(node)
        end
      end


      # Represents a tag attribute. Currently this only works on the top-level tag.
      class Attribute < self
        def read(node, as)
          node[as]
        end

        def serialize_for(value, parent, as)
          parent[as] = value.to_s
        end

        def write(parent, value, as)
          serialize_for(value, parent, as)
        end
      end

      # Represents tag content.
      class Content < self
        def read(node, as)
          node.content
        end

        def serialize_for(value, parent)
          parent.content = value.to_s
        end

        def write(parent, value, as)
          serialize_for(value, parent)
        end
      end
    end # Binding
  end
end
