newmen/versatile-diamond

View on GitHub
analyzer/lib/generators/code/algorithm/units/context_specie_unit.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module VersatileDiamond
  module Generators
    module Code
      module Algorithm::Units

        # Decorates unit for bulding specie dependent code on context
        class ContextSpecieUnit < ContextBaseUnit
          # @param [Array] _
          def initialize(*)
            super

            @_all_nodes_with_atoms, @_all_popular_atoms_nodes = nil

            @_is_over_used_atom, @_is_atom_many_usages_like_in_context = nil
            @_is_full_usages_match = nil
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_existence(&block)
            check_avail_atoms do
              check_that_context_specie_not_found(&block)
            end
          end

        protected

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def select_specie_definition(&block)
            if !many_similar_species? && (symmetric? || over_used_atom?)
              check_many_undefined_species(&block)
            elsif atom_many_usages_like_in_context?
              check_similar_undefined_species(&block)
            else
              unit.define_undefined_species(&block)
            end
          end

        private

          # @return [Array]
          def splitten_inner_units
            inner_units = unit.complete_inner_units
            if totally_splitten?(inner_units)
              inner_units
            else
              inner_units.flat_map(&:units)
            end
          end

          # @return [Array]
          def check_inner_units_symmetries_procs
            if units.all?(&:symmetric?)
              check_inner_symmetric_units_procs
            else
              check_inner_asymmetric_units_procs
            end
          end

          # @return [Array]
          def check_inner_symmetric_units_procs
            units.map do |inner_unit|
              -> &block { inner_unit.check_symmetry_with_atom(&block) }
            end
          end

          # @return [Array]
          def check_inner_asymmetric_units_procs
            units.select(&:asymmetric_related_atoms?).map do |inner_unit|
              -> &block { inner_unit.iterate_symmetries(&block) }
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_avail_atoms(&block)
            if any_defined?(species)
              check_symmetries do
                check_close_atoms(&block)
              end
            else
              unit.check_atoms_roles(atoms, &block)
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_close_atoms(&block)
            checking_nodes = context_nodes_with_undefined_atoms
            if checking_nodes.empty?
              block.call
            else
              pure_unit = pure_factory.unit(checking_nodes)
              pure_unit.define_undefined_atoms do
                pure_unit.check_different_atoms_roles(&block)
              end
            end
          end

          # @yield incorporating statement
          # @return [Expressions::OrCondition]
          def check_that_context_specie_not_found(&block)
            checks = unit.atom_with_specie_calls(:not_found, atoms, &:actual_anchor?)
            Expressions::OrCondition[checks, block.call]
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          # @override
          def check_many_undefined_species(&block)
            if over_used_atom? || atom_many_usages_like_in_context?
              iterate_undefined_species(&block)
            else
              super(&block)
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def iterate_undefined_species(&block)
            unit.iterate_species_by_role do
              check_similar_defined_species do
                check_defined_context_parts do
                  check_symmetries(&block)
                end
              end
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def iterate_portioned_species(&block)
            checking_nodes = context_nodes_with_undefined_atoms
            if seems_different?(checking_nodes)
              unit.iterate_species_by_loop(&block)
            else
              block.call
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_inner_symmetries(&block)
            call_procs(check_inner_units_symmetries_procs, &block)
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_similar_undefined_species(&block)
            if select_undefined(species).one?
              check_many_undefined_species(&block)
            else
              unit.iterate_portions_of_similar_species do
                iterate_portioned_species do
                  check_inner_symmetries(&block)
                end
              end
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_similar_defined_species(&block)
            similar_nodes = original_different_defined_species_nodes
            species_pairs = similar_species_with(similar_nodes)
            if species_pairs.empty?
              block.call
            else
              vars_pairs = species_pairs.map(&method(:vars_for))
              Expressions::NotEqualsCondition[vars_pairs, block.call]
            end
          end

          # @yield incorporating statement
          # @return [Expressions::Core::Statement]
          def check_defined_context_parts(&block)
            nodes_pairs = common_atoms_nodes_pairs
            if nodes_pairs.empty?
              block.call
            else
              exprs_pairs = nodes_pairs_to_atoms_exprs(nodes_pairs)
              Expressions::NotEqualsCondition[exprs_pairs, block.call]
            end
          end

          # @return [Array]
          def context_nodes_with_undefined_atoms
            context.reachable_bone_nodes_with(select_defined(species))
          end

          # @return [Array]
          def original_different_defined_species_nodes
            different_defined_species_nodes.select do |node|
              nodes.any? do |n|
                n.uniq_specie.original == node.uniq_specie.original &&
                  n.sub_properties == node.sub_properties
              end
            end
          end

          # @param [Array] checking_nodes
          # @return [Array]
          def similar_species_with(checking_nodes)
            similar_species = checking_nodes.map(&:uniq_specie)
            similar_species.flat_map do |specie|
              species.select { |s| s.original == specie.original }.zip([specie].cycle)
            end
          end

          # Gets list of pairs of nodes which atoms are similar but belongs to
          # different species
          #
          # @return [Array]
          def common_atoms_nodes_pairs
            avail_species = all_nodes_with_atoms.map(&:uniq_specie).uniq
            defined_species = select_defined(avail_species)
            context.similar_atoms_nodes_pairs(defined_species).reject do |ns|
              ns.any? { |node| nodes.include?(node) }
            end
          end

          # @param [Array] atoms_pairs
          # @return [Array]
          def nodes_pairs_to_atoms_exprs(nodes_pairs)
            nodes_pairs.map { |ns| ns.map(&method(:atom_var_or_specie_call)) }
          end

          # Gets nodes which uses the atoms like inner unit, but contains defined
          # specie which is not same as any specie of inner unit
          #
          # @return [Array]
          def different_defined_species_nodes
            other_nodes = all_nodes_with_atoms - nodes
            if other_nodes.empty?
              []
            else
              other_nodes.select do |node|
                other_specie = node.uniq_specie
                !species.include?(other_specie) && dict.var_of(other_specie)
              end
            end
          end

          # @return [Array]
          def all_nodes_with_atoms
            @_all_nodes_with_atoms ||= context.atoms_nodes(atoms)
          end

          # @return [Array]
          def all_popular_atoms_nodes
            @_all_popular_atoms_nodes ||=
              all_nodes_with_atoms.select(&:used_many_times?)
          end

          # @return [Integer]
          def count_possible_atom_usages
            sum = all_popular_atoms_nodes.map(&:usages_num).reduce(:+)
            num = all_popular_atoms_nodes.size
            sum % num == 0 ? (sum / num) : sum
          end

          # @return [Boolean]
          def full_usages_match?
            return @_is_full_usages_match unless @_is_full_usages_match.nil?
            coincident_nodes = nodes.select(&:coincide?)
            comparing_nodes = coincident_nodes.empty? ? nodes : coincident_nodes
            @_is_full_usages_match =
              (count_possible_atom_usages == comparing_nodes.size)
          end

          # @return [Boolean]
          def atom_used_many_times?
            atoms.one? &&
              nodes.any?(&all_popular_atoms_nodes.public_method(:include?))
          end

          # @return [Boolean]
          def over_used_atom?
            return @_is_over_used_atom unless @_is_over_used_atom.nil?
            @_is_over_used_atom = atom_used_many_times? && !full_usages_match?
          end

          # @return [Boolean]
          def many_similar_species?
            atom_many_usages_like_in_context? &&
              !species.one? && species.map(&:original).uniq.one?
          end

          # @return [Boolean]
          def atom_many_usages_like_in_context?
            unless @_is_atom_many_usages_like_in_context.nil?
              return @_is_atom_many_usages_like_in_context
            end

            @_is_atom_many_usages_like_in_context =
              if atom_used_many_times?
                context_prop = all_popular_atoms_nodes.first.properties
                parent_props = all_popular_atoms_nodes.map(&:sub_properties)
                if parent_props.one? && !totally_popular?
                  parent_props *= count_possible_atom_usages
                end
                parent_props.reduce(:safe_plus) == context_prop
              else
                false
              end
          end

          # @return [Boolean]
          def totally_popular?
            lists_are_identical?(nodes, all_popular_atoms_nodes)
          end

          # @param [Array] inner_units
          # @return [Boolean]
          def totally_splitten?(inner_units)
            inner_units.empty? || inner_units != [unit] ||
              !atom_many_usages_like_in_context? || (totally_popular? &&
                inner_units.flat_map(&:species).map(&:original).uniq.one?)
          end

          # @param [Array] checking_nodes
          # @return [Boolean]
          def same_specie_in?(checking_nodes)
            if checking_nodes.any?(&:splittable?)
              false
            else
              checking_species = checking_nodes.map(&:uniq_specie).uniq
              checking_species.one? && !checking_species.all?(&:none?)
            end
          end

          # @param [Nodes::SpecieNode] a
          # @param [Nodes::SpecieNode] b
          # @return [Boolean]
          def checkable_neighbour_species?(a, b)
            [a, b].map(&:uniq_specie).reject(&:none?).size > 1
          end
        end

      end
    end
  end
end