# Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
# License:   Apache License, Version 2.0

module RTM
  class MergeException < Exception; end
  class TopicMergeException < MergeException; end
  module Merging
    module ReWrap
      protected
      def merge_rewrap(other)
        # let the wrapper point to the new object
        old_getobj = other.__getobj__
        other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        # and destroy the old one
        old_getobj.reload
        __getobj__.reload
        old_getobj.destroy
      end
    end
    module MergeItemIdentifiers
      protected
      def merge_item_identifiers(other)
        #self.item_identifiers.add_all other.item_identifiers
        RTM::AR::TMDM::ItemIdentifier.move_all(
          ["construct_id", other.__getobj__.id, self.__getobj__.id],
          ["construct_type", other.__getobj__.class.name, self.__getobj__.class.name]
        )
      end
    end
    module MergeReifiable
      include MergeItemIdentifiers
      include ReWrap
      protected
      def merge_reifiable(other)
        if other.reifier && self.reifier
          self.reifier.merge(other.reifier)
        elsif other.reifier
          self.reifier = other.reifier
        end
      end
    end

    module Association
      include Merging::MergeReifiable

      def merge(other)
        # The procedure for merging two association items A and B is given below.

        # 1. Create a new association item, C.
        # -> we will instead just modify a

        # 2. Set C's [type] property to the value of A's [type] property. B's value is equal to that of A and need not be taken into account.
        # 3. Set C's [scope] property to the value of A's [scope] property. B's value is equal to that of A and need not be taken into account.
        # 4. Set C's [roles] property to the value of A's [roles] property. B's value is equal to that of A and need not be taken into account.
        # -> nothing to do till here

        # 5. Set C's [reifier] property to the value of A's [reifier] property if it is not null, and to the value of B's [reifier] property if A's property is null. If both A and B have non-null values, the topic items shall be merged, and the topic item resulting from the merge set as the value of C's [reifier] property.
        self.merge_reifiable other

        # 6. Set C's [item identifiers] property to the union of the values of A's and B's [item identifiers] properties.
        self.merge_item_identifiers other

        # 7. Remove A and B from the [associations] property of the topic map item in their [parent] properties, and add C.
        #self.parent.associations.remove other

        # let the wrapper point to the new object
        #other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        self.merge_rewrap other

        self
      end
    end

    module Role
      include Merging::MergeReifiable

      def merge(other)
        # The procedure for merging two association role items A and B is given below.
        # -> we will instead just modify a

        # 1. Create a new association role item, C.
        # 2. Set C's [player] property to the value of A's [player] property. B's value is equal to that of A and need not be taken into account.
        # 3. Set C's [type] property to the value of A's [type] property. B's value is equal to that of A and need not be taken into account.
        # -> nothing to do till here

        # 4. Set C's [item identifiers] property to the union of the values of A's and B's [item identifiers] properties.
        self.merge_item_identifiers other

        # 5. Set C's [reifier] property to the value of A's [reifier] property if it is not null,
        #    and to the value of B's [reifier] property if A's property is null.
        #    If both A and B have non-null values, the topic items shall be merged,
        #    and the topic item resulting from the merge set as the value of C's [reifier] property.
        self.merge_reifiable other

        # 6. Remove A and B from the [roles] property of the association item in their [parent] properties, and add C.
        #self.parent.roles.remove other

        #puts "#{self.parent.object_id} and #{other.parent.object_id}"

        # let the wrapper point to the new object
        #other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        self.merge_rewrap other

        self
      end
    end

    module Occurrence
      include Merging::MergeReifiable

      def merge(other)
        # 1. Create a new occurrence item, C.
        # -> we will instead just modify a

        # 2. Set C's [value] property to the value of A's [value] property. B's value is equal to that of A and need not be taken into account.
        # 3. Set C's [datatype] property to the value of A's [datatype] property. B's value is equal to that of A and need not be taken into account.
        # 4. Set C's [scope] property to the value of A's [scope] property. B's value is equal to that of A and need not be taken into account.
        # 5. Set C's [type] property to the value of A's [type] property. B's value is equal to that of A and need not be taken into account.
        # -> nothing to do till here

        # 6. Set C's [reifier] property to the value of A's [reifier] property if it is not null, and to the value of B's [reifier] property if A's property is null. If both A and B have non-null values, the topic items shall be merged, and the topic item resulting from the merge set as the value of C's [reifier] property.
        self.merge_reifiable other

        # 7. Set C's [item identifiers] property to the union of the values of A's and B's [item identifiers] properties.
        self.merge_item_identifiers other

        # 8. Remove A and B from the [occurrences] property of the topic item in their [parent] properties, and add C.
        #self.parent.occurrences.remove other

        # let the wrapper point to the new object
        #other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        self.merge_rewrap other

        self
      end
    end

    module Topic
      include Merging::ReWrap
      include Merging::MergeItemIdentifiers

      def merge(other)
        return unless other
        # The procedure for merging two topic items A and B
        # -> we will instead just modify a
        # (whose [parent] properties shall contain the same topic map item) is given below.
        raise TopicMergeException, "Both topics must belong to the same topic map!" if self.parent.__getobj__ != other.parent.__getobj__
        # It is an error if A and B both have non-null values in their [reified] properties which are different.
        raise TopicMergeException, "The Topics must not reify different things." if self.reified && other.reified && self.reified.__getobj__ != other.reified.__getobj__

        # 1. Create a new topic item C.
        # 2. Replace A by C wherever it appears in one of the following properties
        #    of an information item: [topics], [scope], [type], [player], and [reifier].
        # -> nothing to do here

        # 3. Repeat for B.
        # [topics]
        # done below, in rewrap
        # [scope]
        RTM::AR::TMDM::ScopedObjectsTopic.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])
        # [type]
        RTM::AR::TMDM::Association.move_all(["ttype_id", other.__getobj__.id, self.__getobj__.id])
        RTM::AR::TMDM::Role.move_all(["ttype_id", other.__getobj__.id, self.__getobj__.id])
        RTM::AR::TMDM::Name.move_all(["ttype_id", other.__getobj__.id, self.__getobj__.id])
        RTM::AR::TMDM::Occurrence.move_all(["ttype_id", other.__getobj__.id, self.__getobj__.id])

        # [player]
        RTM::AR::TMDM::AssociationRole.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])

        # [reifier]
        if other.reified
          self.reified = other.reified
        end
        # 4. Set C's [topic names] property to the union of the values of A and B's [topic names] properties.
        #self.names.add_all other.names
        count = RTM::AR::TMDM::Name.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])
        if count != 0 && count != names.size
          names.each_with_index do |n,i|
            (i+1).upto(names.size-1) do |j|
              if n == names[j]
                n.merge names[j]
              end
            end
          end
        end

        # 5. Set C's [occurrences] property to the union of the values of A and B's [occurrences] properties.
        #self.occurrences.add_all other.occurrences
        RTM::AR::TMDM::Occurrence.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])
        if count != 0 && count != occurrences.size
          occurrences.each_with_index do |o,i|
            (i+1).upto(occurrences.size-1) do |j|
              if o == occurrences[j]
                o.merge occurrences[j]
              end
            end
          end
        end

        # 6. Set C's [subject identifiers] property to the union of the values of A and B's [subject identifiers] properties.
        #self.subject_identifiers.add_all other.subject_identifiers
        RTM::AR::TMDM::SubjectIdentifier.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])

        # 7. Set C's [subject locators] property to the union of the values of A and B's [subject locators] properties.
        #self.subject_locators.add_all other.subject_locators
        RTM::AR::TMDM::SubjectLocator.move_all(["topic_id", other.__getobj__.id, self.__getobj__.id])

        # 8. Set C's [item identifiers] property to the union of the values of A and B's [item identifiers] properties.
        #self.item_identifiers.add_all other.item_identifiers
        #RTM::AR::TMDM::ItemIdentifier.update_all "construct_id = #{self.__getobj__.id}, construct_type = '#{self.__getobj__.class.name}'", "construct_id = #{other.__getobj__.id} and construct_type = '#{other.__getobj__.class.name}'"
        RTM::AR::TMDM::ItemIdentifier.move_all(
          ["construct_id", other.__getobj__.id, self.__getobj__.id],
          ["construct_type", other.__getobj__.class.name, self.__getobj__.class.name]
        )

        self.merge_rewrap other

        self
      end
      alias :merge_in :merge
    end

    module TopicMap
      include Merging::MergeReifiable

      def merge(other) # other:TopicMap
        self.topics.add_all other.topics
        self.associations.add_all other.associations
        self.merge_reifiable other
        self.merge_item_identifiers other

        self.merge_rewrap other
        self
      end
      alias :merge_in :merge
    end

    module Name
      def merge(other)
        # The procedure for merging two topic name items A and B is given below.
        # -> we will instead just modify a

        # 1. Create a new topic name item C.
        # 2. Set C's [value] property to the value of the [value] property of A. B's value is equal that of A and need not be taken into account.
        # 3. Set C's [type] property to the value of the [type] property of A. B's value is equal that of A and need not be taken into account.
        # 4. Set C's [scope] property to the value of the [scope] property of A. B's value is equal that of A and need not be taken into account.
        # -> nothing to do till here

        # 5. Set C's [variants] property to the union of the [variants] properties of A and B.
        #self.variants.add_all other.variants
        count = RTM::AR::TMDM::Variant.move_all(["name_id", other.__getobj__.id, self.__getobj__.id])
        if count != 0 && count != variants.size
          variants.each_with_index do |v,i|
            (i+1).upto(variants.size-1) do |j|
              if v == variants[j]
                v.merge variants[j]
              end
            end
          end
        end

        # 6. Set C's [reifier] property to the value of A's [reifier] property if it is not null, and to the value of B's [reifier] property if A's property is null. If both A and B have non-null values, the topic items shall be merged, and the topic item resulting from the merge be set as the value of C's [reifier] property.
        self.merge_reifiable other

        # 7. Set C's [item identifiers] property to the union of the value of the [item identifiers] properties of A and B.
        self.merge_item_identifiers other

        # 8. Remove A and B from the [topic names] property of the topic item in their [parent] properties, and add C.
        #self.parent.names.remove other

        # let the wrapper point to the new object
        #other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        self.merge_rewrap other

        self
      end
    end

    module Variant
      include Merging::MergeReifiable

      def merge(other)
        #The procedure for merging two variant items A and B is given below.
        # -> we will instead just modify a

        # 1. Create a new variant item, C.
        # 2. Set C's [value] property to the value of A's [value] property. B's value is equal to that of A and need not be taken into account.
        # 3. Set C's [datatype] property to the value of A's [datatype] property. B's value is equal to that of A and need not be taken into account.
        # 4. Set C's [scope] property to the value of A's [scope] property. B's value is equal to that of A and need not be taken into account.
        # -> nothing to do till here

        # 5. Set C's [reifier] property to the value of A's [reifier] property if it is not null, and to the value of B's [reifier] property if A's property is null. If both A and B have non-null values, the topic items shall be merged, and the topic item resulting from the merge set as the value of C's [reifier] property.
        self.merge_reifiable other

        # 6. Set C's [item identifiers] property to the union of the values of A's and B's [item identifiers] properties.
        self.merge_item_identifiers other

        # 7. Remove A and B from the [variants] property of the topic name item in their [parent] properties, and add C.
        #self.parent.variants.remove other

        # let the wrapper point to the new object
        #other.__setobj__(self.__getobj__) if self.respond_to?(:__getobj__) && other.respond_to?(:__setobj__)
        self.merge_rewrap other

        self
      end
    end
    RTM.register_extension( self )
  end
end