require 'rake/testtask'

require 'rexml/document'
require 'rexml/streamlistener'

# Listener class used to process GIR xml data, for creating test stubs.
class Listener
  include REXML::StreamListener

  def initialize
    @inside_class = false
    @stack = []
    @skip_state = []
  end

  attr_accessor :result
  attr_accessor :namespace

  def tag_start name, attrs
    @stack.push [name, attrs]
    if @skip_state.last || skippable?(attrs)
      @skip_state.push true
      return
    else
      @skip_state.push false
    end

    obj_name = attrs['name']
    case name
    when "constant"
      result.puts "  it 'has the constant #{obj_name}' do"
    when "record", "class", "enumeration", "bitfield", "interface", "union"
      result.puts "  describe '#{namespace}::#{obj_name}' do"
      @inside_class = name
    when "constructor"
      result.puts "    it 'creates an instance using ##{obj_name}' do"
    when "field"
      if @inside_class != 'class'
        if attrs['private'] == "1"
          result.puts "    it 'has a private field #{obj_name}' do"
        elsif attrs['writable'] == "1"
          result.puts "    it 'has a writable field #{obj_name}' do"
        else
          result.puts "    it 'has a read-only field #{obj_name}' do"
        end
      end
    when "function", "method"
      spaces = @inside_class ? "  " : ""
      result.puts "  #{spaces}it 'has a working #{name} ##{obj_name}' do"
    when "member"
      result.puts "    it 'has the member :#{obj_name}' do"
    when "namespace"
      result.puts "describe #{obj_name} do"
    when "property"
      accessor_name = obj_name.gsub(/-/, '_')
      result.puts "    describe \"its '#{obj_name}' property\" do"
      result.puts "      it 'can be retrieved with #get_property' do"
      result.puts "      end"
      result.puts "      it 'can be retrieved with ##{accessor_name}' do"
      result.puts "      end"
      if attrs['writable'] == '1'
        result.puts "      it 'can be set with #set_property' do"
        result.puts "      end"
        result.puts "      it 'can be set with ##{accessor_name}=' do"
        result.puts "      end"
      end
    when "glib:signal"
      result.puts "    it \"handles the '#{obj_name}' signal\" do"
    when "type", "alias", "return-value", "parameters",
      "instance-parameter", "parameter", "doc", "array",
      "repository", "include", "package"
      # Not printed"
    else
      puts "Skipping #{name}: #{attrs}"
    end
  end

  def tag_end name
    org_name, _ = *@stack.pop
    skipping = @skip_state.pop
    raise "Expected #{org_name}, got #{name}" if org_name != name
    return if skipping

    case name
    when "constant"
      result.puts "  end"
    when "record", "class", "enumeration", "bitfield",
      "interface", "union"
      result.puts "  end"
      @inside_class = false
    when "function", "method"
      if @inside_class
        result.puts "    end"
      else
        result.puts "  end"
      end
    when "constructor", "member", "property", "glib:signal"
      result.puts "    end"
    when "field"
      if @inside_class != 'class'
        result.puts "    end"
      end
    when "namespace"
      result.puts "end"
    end
  end

  def skippable? attrs
    return true if attrs['disguised'] == '1'
    return true if attrs['introspectable'] == '0'
    return true if attrs['glib:is-gtype-struct-for']
    false
  end
end

namespace :test do
  def define_test_task name
    Rake::TestTask.new(name) do |t|
      t.libs = ['lib']
      t.ruby_opts += ["-w -Itest"]
      yield t
    end
  end

  define_test_task(:base) do |t|
    t.test_files = FileList['test/gir_ffi-base/**/*_test.rb']
  end

  define_test_task(:introspection) do |t|
    t.test_files = FileList['test/ffi-gobject_introspection/**/*_test.rb']
  end

  define_test_task(:main) do |t|
    t.test_files = FileList['test/gir_ffi_test.rb',
                            'test/gir_ffi/**/*_test.rb']
  end

  define_test_task(:overrides) do |t|
    t.test_files = FileList['test/ffi-gobject_test.rb',
                            'test/ffi-glib/**/*_test.rb',
                            'test/ffi-gobject/**/*_test.rb']
  end

  define_test_task(:integration) do |t|
    t.test_files = FileList['test/integration/**/*_test.rb']
  end

  desc 'Build test libraries and typelibs'
  task :lib => "test/lib/Makefile" do
    sh %{cd test/lib && make}
  end

  task :introspection => :lib
  task :main => :lib
  task :overrides => :lib
  task :integration => :lib

  desc 'Run the entire test suite as one'
  define_test_task(:all) do |t|
    t.test_files = FileList['test/**/*_test.rb']
  end

  task :all => :lib

  desc 'Run all individual test suites separately'
  task :suites => [:base,
                   :introspection,
                   :main,
                   :overrides,
                   :integration]

  def make_stub_file(libname)
    file = File.new "test/lib/#{libname}-1.0.gir"
    listener = Listener.new
    listener.result = File.open("tmp/#{libname.downcase}_lines.rb", 'w')
    listener.namespace = libname
    REXML::Document.parse_stream file, listener
  end

  desc "Create stubs for integration tests"
  task :stub => :lib do
    make_stub_file 'Everything'
    make_stub_file 'GIMarshallingTests'
    make_stub_file 'Regress'
    make_stub_file 'Utility'
    make_stub_file 'WarnLib'
  end
end

file "test/lib/Makefile" => "test/lib/configure" do
  sh %{cd test/lib && ./configure --enable-maintainer-mode}
end

file "test/lib/configure" => ["test/lib/autogen.sh", "test/lib/configure.ac"] do
  sh %{cd test/lib && ./autogen.sh}
end
