require 'forwardable'

# :reek:FeatureEnvy
module Neat
  class Mutation
    extend Forwardable
    def_delegators :@genome, :connections, :nodes
    def_delegators 'Neat.configuration', :mutate_connection_chance, :mutate_node_chance,
                   :mutate_weights_chance, :toggle_connection_chance, :randomize_weight_chance

    def initialize(genome)
      @genome = genome
    end

    def mutate
      add_connection if rand <= mutate_connection_chance
      add_node if rand <= mutate_node_chance
      toggle_connection if rand <= toggle_connection_chance
      mutate_weights if rand <= mutate_weights_chance
    end

    def add_connection
      100.times do
        input, output = nodes.values.sample(2)

        next unless can_connect?(input, output)

        return @genome.create_connection(input.id, output.id)
      end
    end

    def add_node
      conn = connections.sample
      layer = @genome.nodes[conn.input_id].layer + 1
      node = @genome.create_node(HiddenNode, layer: layer)

      replace_connection_with_node(conn, node.id)

      @genome.reorder
    end

    def mutate_weights
      connections.each do |conn|
        perturb = rand > randomize_weight_chance
        perturb ? conn.perturb_weight : conn.randomize_weight
      end
    end

    def toggle_connection
      conn = connections.sample
      conn.enabled = !conn.enabled?
    end

    private

    def replace_connection_with_node(conn, node_id)
      @genome.create_connection(conn.input_id, node_id, weight: 1, enabled: true)
      @genome.create_connection(node_id, conn.output_id, weight: conn.weight, enabled: conn.enabled?)
      conn.disable
    end

    def can_connect?(input, output)
      return false if input.output? || output.bias? || output.input?
      return false if input.layer >= output.layer

      !@genome.connected?(input.id, output.id) && !@genome.connected?(output.id, input.id)
    end
  end
end
