# Patch to improve generated addXXX and setXXX method performance
#
# Submitted to rgen as PR: https://github.com/mthiede/rgen/pull/22. This
# patch should be removed once that PR has been included in an rgen
# release.
#
# The current implementation of the generated addXXX method will iterate
# over the contained array where each iteration will then test for object_id
# equality. Once the iteration is done and such an entry is found it
# checks if the added value is an instance of MMBase or MMGeneric. If it
# is, then it's considered a duplicate and the method returns.
#
# This patch changes the implementation so that the test for MMBase/
# MMGeneric is made prior to the iteration, thus avoiding the iteration
# altogether for all non model objects
#
# The patch also changes the setXXX method to allow it to optimize the way
# it ensures that the added elements are unique. Calling the addXXX method
# to do an increasingly expensive sequential scan is very expensive when
# large sets of values are assigned.
#
require 'rgen/util/name_helper'
require 'rgen/metamodel_builder/constant_order_helper'
require 'rgen/metamodel_builder/builder_extensions'

module RGen
module MetamodelBuilder
module BuilderExtensions

  def _build_many_methods(props, other_props=nil)
    name = props.value(:name)
    other_role = other_props && other_props.value(:name)

    if props.value(:derived)
      build_derived_method(name, props, :many)
    else
      @@many_read_builder ||= ERB.new <<-CODE

          def get<%= firstToUpper(name) %>
            ( @<%= name %> ? @<%= name %>.dup : [] )
          end
          <% if name != "class" %>
            alias <%= name %> get<%= firstToUpper(name) %>
          <% end %>

      CODE
      self::ClassModule.module_eval(@@many_read_builder.result(binding))
    end

    if props.value(:changeable)
      @@many_write_builder ||= ERB.new <<-CODE

          def add<%= firstToUpper(name) %>(val, index=-1)
            @<%= name %> = [] unless @<%= name %>
            return if val.nil? || (val.is_a?(MMBase) || val.is_a?(MMGeneric)) && @<%= name %>.any? {|e| e.equal?(val)}
            <%= type_check_code("val", props) %>
            @<%= name %>.insert(index, val)
            <% if other_role %>
              val._register<%= firstToUpper(other_role) %>(self) unless val.is_a?(MMGeneric)
            <% end %>
            <% if props.reference? && props.value(:containment) %>
              val._set_container(self, :<%= name %>)
            <% end %>
          end

          def remove<%= firstToUpper(name) %>(val)
            @<%= name %> = [] unless @<%= name %>
            @<%= name %>.each_with_index do |e,i|
              if e.equal?(val)
                @<%= name %>.delete_at(i)
                <% if props.reference? && props.value(:containment) %>
                  val._set_container(nil, nil)
                <% end %>
                <% if other_role %>
                  val._unregister<%= firstToUpper(other_role) %>(self) unless val.is_a?(MMGeneric)
                <% end %>
                return
              end
            end
          end

          def set<%= firstToUpper(name) %>(val)
            return if val.nil?
            raise _assignmentTypeError(self, val, Enumerable) unless val.is_a? Enumerable
            get<%= firstToUpper(name) %>.each {|e|
              remove<%= firstToUpper(name) %>(e)
            }
            @<%= name %> = [] unless @<%= name %>
            <% if props.reference? %>
            val.uniq {|elem| elem.object_id }.each {|elem|
              next if elem.nil?
              <%= type_check_code("elem", props) %>
              @<%= name %> << elem
              <% if other_role %>
                elem._register<%= firstToUpper(other_role) %>(self) unless elem.is_a?(MMGeneric)
              <% end %>
              <% if props.value(:containment) %>
                elem._set_container(self, :<%= name %>)
              <% end %>
            }
            <% else %>
            val.each {|elem|
              <%= type_check_code("elem", props) %>
              @<%= name %> << elem
           }
           <% end %>
          end
          alias <%= name %>= set<%= firstToUpper(name) %>

          def _register<%= firstToUpper(name) %>(val)
            @<%= name %> = [] unless @<%= name %>
            @<%= name %>.push val
            <% if props.reference? && props.value(:containment) %>
              val._set_container(self, :<%= name %>)
            <% end %>
          end

          def _unregister<%= firstToUpper(name) %>(val)
            @<%= name %>.delete val
            <% if props.reference? && props.value(:containment) %>
              val._set_container(nil, nil)
            <% end %>
          end

      CODE
      self::ClassModule.module_eval(@@many_write_builder.result(binding))
    end
  end
end
end
end

