require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.libs << "lib"
  t.test_files = FileList["test/**/*_test.rb"].reject do |path|
    path =~ %r{test/stdlib/}
  end
end

multitask :default => [:test, :stdlib_test, :rubocop, :validate]

task :validate => :parser do
  sh "rbs validate"
end

FileList["test/stdlib/**/*_test.rb"].each do |test|
  multitask test => :parser do
    sh "ruby bin/test_runner.rb #{test}"
  end
  multitask stdlib_test: test
end

task :rubocop do
  sh "rubocop --parallel"
end

rule ".rb" => ".y" do |t|
  sh "racc -v -o #{t.name} #{t.source}"
end

task :parser => "lib/rbs/parser.rb"
task :test => :parser
task :stdlib_test => :parser
task :build => :parser

namespace :generate do
  task :stdlib_test, [:class] do |_task, args|
    klass = args.fetch(:class) do
      raise "Class name is necessary. e.g. rake 'generate:stdlib_test[String]'"
    end

    path = Pathname("test/stdlib/#{klass}_test.rb")
    raise "#{path} already exists!" if path.exist?

    require "erb"
    require "ruby/signature"

    class TestTemplateBuilder
      attr_reader :klass, :env

      def initialize(klass)
        @klass = klass

        @env = Ruby::Signature::Environment.new
        Ruby::Signature::EnvironmentLoader.new.load(env: @env)
      end

      def call
        ERB.new(<<~ERB, trim_mode: "-").result(binding)
          require_relative "test_helper"

          class <%= klass %>Test < StdlibTest
            target <%= klass %>
            # library "pathname", "set", "securerandom"     # Declare library signatures to load
            using hook.refinement
          <%- class_methods.each do |method_name, definition| %>
            def test_class_method_<%= test_name_for(method_name) %>
            <%- definition.method_types.each do |method_type| -%>
              # <%= method_type %>
              <%= klass %>.<%= method_name %>
            <%- end -%>
            end
          <%- end -%>
          <%- instance_methods.each do |method_name, definition| %>
            def test_<%= test_name_for(method_name) %>
            <%- definition.method_types.each do |method_type| -%>
              # <%= method_type %>
              <%= klass %>.new.<%= method_name %>
            <%- end -%>
            end
          <%- end -%>
          end
        ERB
      end

      private

      def test_name_for(method_name)
        {
          :==  => 'double_equal',
          :!=  => 'not_equal',
          :=== => 'triple_equal',
          :[]  => 'square_bracket',
          :[]= => 'square_bracket_assign',
          :>   => 'greater_than',
          :<   => 'less_than',
          :>=  => 'greater_than_equal_to',
          :<=  => 'less_than_equal_to',
          :<=> => 'spaceship',
          :+   => 'plus',
          :-   => 'minus',
          :*   => 'multiply',
          :/   => 'divide',
          :**  => 'power',
          :%   => 'modulus',
          :&   => 'and',
          :|   => 'or',
          :^   => 'xor',
          :>>  => 'right_shift',
          :<<  => 'left_shift',
          :=~  => 'pattern_match',
          :!~  => 'does_not_match',
          :~   => 'tilde'
        }.fetch(method_name, method_name)
      end

      def type_name
        @type_name ||= Ruby::Signature::TypeName.new(name: klass.to_sym, namespace: Ruby::Signature::Namespace.new(path: [], absolute: true))
      end

      def class_methods
        @class_methods ||= Ruby::Signature::DefinitionBuilder.new(env: env).build_singleton(type_name).methods.select {|_, definition|
          definition.implemented_in.name.absolute! == type_name
        }
      end

      def instance_methods
        @instance_methods ||= Ruby::Signature::DefinitionBuilder.new(env: env).build_instance(type_name).methods.select {|_, definition|
          definition.implemented_in.name.absolute! == type_name
        }
      end
    end

    path.write TestTemplateBuilder.new(klass).call

    puts "Created: #{path}"
  end
end

CLEAN.include("lib/rbs/parser.rb")