# frozen_string_literal: true
module WordSearch
  class Solver
    attr_accessor :script, :word_bank, :plane, :failed

    def initialize(script, word_list_file, plane_file)
      @script = script
      @word_bank = WordBank.new(word_list_file)
      @plane = Plane.make_from_file(plane_file)
      @failed = false
    end

    def perform
      master_solutions # load master solutions so it doesn't effect benchmark
      bm = benchmark_solution

      return bm if !failed && solved?

      "Word Search incorrectly solved"
    end

    def master_solutions
      @master_solutions ||=
        import_solutions(
          if File.exist?("solution_#{plane.digest}")
            File.read("solution_#{plane.digest}")
          else
            generate_master_solution
          end
        )
    end

    private

    def benchmark_solution
      Benchmark.measure do
        begin
          users_solution
        rescue
          self.failed = true
        end
      end
    end

    def users_solution
      @users_solution ||=
        import_solutions(File.read(JSON.parse(`ruby #{script}`)))
    end

    def solved?
      @word_bank.all? { |word| users_solution[word] == master_solutions[word] }
    end

    def import_solutions(solution_array)
      solution_array.split("---").map do |positions|
        {
          positions.strip.split("\n").map(&:first).join =>
            positions.strip.split("\n").map.with_index do |letter, index|
              [index, JSON.parse(letter[2..-1])]
            end.to_h
        }
      end.reduce({}, :merge)
    end

    def generate_master_solution
      word_bank.map do |word|
        find_word(word)
      end.join("---")
    end

    def directions
      @directions ||=
        if plane.two_dimensional?
          WordSearch::TwoDimensional::Direction
        else
          WordSearch::ThreeDimensional::Direction
        end.values
    end

    def find_word(word)
      string = ""
      plane.catalog[word[0]].find do |point|
        directions.find do |direction|
          next if (spot = find_point(point, word.size - 1, direction)).blank? ||
                  not_found?(spot, word, point, direction)

          string = letter_positions(word, direction, point)
        end
      end

      string
    end

    def letter_positions(word, direction, point)
      solution = ""
      word.split("").each_with_index do |letter, index|
        solution += "#{letter} #{point.coordinate}\n"
        next if index == (word.length - 1)

        point = plane.find_next_point(point, direction)
      end

      solution
    end

    def not_found?(spot, word, point, direction)
      !(spot.letter == word[-1] && double_check(word, point, direction))
    end

    def find_point(point, move, direction)
      plane.dig(
        point.x + (move * direction[0]),
        point.y + (move * direction[1]),
      )
    end

    def double_check(word, point, direction)
      matching = true

      (word.length - 2).times do |i|
        matching &&= find_point(point, (1 + i), direction).letter == word[1 + i]
      end

      matching
    end
  end
end