#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require_relative "spec_helper"
SimpleCov.command_name "Service Tests" if Object.const_defined? "SimpleCov"
# find the library without external help
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)

require "dbus"

PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties".freeze

class Test < DBus::Object
  INTERFACE = "org.ruby.SampleInterface".freeze
  def initialize(path)
    super path
    @read_me = "READ ME"
    @read_or_write_me = "READ OR WRITE ME"
  end

  # Create an interface aggregating all upcoming dbus_method defines.
  dbus_interface INTERFACE do
    dbus_method :hello, "in name:s, in name2:s" do |name, name2|
      puts "hello(#{name}, #{name2})"
    end

    dbus_method :test_variant, "in stuff:v" do |variant|
      DBus.logger.debug variant.inspect
    end

    dbus_method :bounce_variant, "in stuff:v, out chaff:v" do |variant|
      [variant]
    end

    dbus_method :variant_size, "in stuff:v, out size:u" do |variant|
      [variant.size]
    end

    dbus_method :the_answer, "out answer:i" do
      42
    end

    dbus_method :will_raise, "" do
      raise "Handle this"
    end

    dbus_method :will_raise_error_failed, "" do
      raise DBus.error, "failed as designed"
    end

    dbus_method :will_raise_name_error, "" do
      "foo".frobnicate
    end

    dbus_method :Error, "in name:s, in description:s" do |name, description|
      raise DBus.error(name), description
    end

    dbus_method :mirror_byte_array, "in bytes:ay, out mirrored:ay" do |bytes|
      [bytes]
    end
  end

  # closing and reopening the same interface
  dbus_interface INTERFACE do
    dbus_method :multibyte_string, "out string:s" do
      "あいうえお"
    end

    dbus_signal :SomethingJustHappened, "toto:s, tutu:u"
  end

  dbus_interface "org.ruby.AnotherInterface" do
    dbus_method :ThatsALongMethodNameIThink do
      puts "ThatsALongMethodNameIThink"
    end
    dbus_method :Reverse, "in instr:s, out outstr:s" do |instr|
      outstr = instr.split(//).reverse.join
      [outstr]
    end
  end

  dbus_interface "org.ruby.Ticket30" do
    dbus_method :Sybilla, "in choices:av, out advice:s" do |choices|
      ["Do #{choices[0]}"]
    end
  end

  dbus_interface "org.ruby.Duplicates" do
    dbus_method :the_answer, "out answer:i" do
      [0]
    end
    dbus_method :interfaces, "out answer:i" do
      raise "This DBus method is currently shadowed by ProxyObject#interfaces"
    end
  end

  dbus_interface "org.ruby.Loop" do
    # starts doing something long, but returns immediately
    # and sends a signal when done
    dbus_method :LongTaskBegin, "in delay:i" do |delay|
      # FIXME: did not complain about mismatch between signature and block args
      self.LongTaskStart
      DBus.logger.debug "Long task began"
      task = Thread.new do
        DBus.logger.debug "Long task thread started (#{delay}s)"
        sleep delay
        DBus.logger.debug "Long task will signal end"
        self.LongTaskEnd
      end
      task.abort_on_exception = true # protect from test case bugs
    end

    dbus_signal :LongTaskStart
    dbus_signal :LongTaskEnd
  end

  # Properties:
  # ReadMe:string, returns "READ ME" at first, then what WriteMe received
  # WriteMe:string
  # ReadOrWriteMe:string, returns "READ OR WRITE ME" at first
  dbus_interface PROPERTY_INTERFACE do
    dbus_method :Get, "in interface:s, in propname:s, out value:v" do |interface, propname|
      unless interface == INTERFACE
        raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"),
              "Interface '#{interface}' not found on object '#{@path}'"
      end

      case propname
      when "ReadMe"
        [@read_me]
      when "ReadOrWriteMe"
        [@read_or_write_me]
      when "WriteMe"
        raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"),
              "Property '#{interface}.#{propname}' (on object '#{@path}') is not readable"
      else
        # what should happen for unknown properties
        # plasma: InvalidArgs (propname), UnknownInterface (interface)
        raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"),
              "Property '#{interface}.#{propname}' not found on object '#{@path}'"
      end
    end

    dbus_method :Set, "in interface:s, in propname:s, in  value:v" do |interface, propname, value|
      unless interface == INTERFACE
        raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"),
              "Interface '#{interface}' not found on object '#{@path}'"
      end

      case propname
      when "ReadMe"
        raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"),
              "Property '#{interface}.#{propname}' (on object '#{@path}') is not writable"
      when "ReadOrWriteMe"
        @read_or_write_me = value
        self.PropertiesChanged(interface, { propname => value }, [])
      when "WriteMe"
        @read_me = value
        self.PropertiesChanged(interface, { "ReadMe" => value }, [])
      else
        raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"),
              "Property '#{interface}.#{propname}' not found on object '#{@path}'"
      end
    end

    dbus_method :GetAll, "in interface:s, out value:a{sv}" do |interface|
      unless interface == INTERFACE
        raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"),
              "Interface '#{interface}' not found on object '#{@path}'"
      end

      [
        {
          "ReadMe" => @read_me,
          "ReadOrWriteMe" => @read_or_write_me
        }
      ]
    end

    dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
  end
end

class Derived < Test
end

class Test2 < DBus::Object
  dbus_interface "org.ruby.Test2" do
    dbus_method :hi, "in name:s, out greeting:s" do |name|
      "Hi, #{name}!"
    end
  end
end

bus = DBus::SessionBus.instance
service = bus.request_service("org.ruby.service")
myobj = Test.new("/org/ruby/MyInstance")
service.export(myobj)
derived = Derived.new "/org/ruby/MyDerivedInstance"
service.export derived
test2 = Test2.new "/org/ruby/MyInstance2"
service.export test2

# introspect every other connection, Ticket #34
#  (except the one that activates us - it has already emitted
#  NOC by the time we run this. Therefore the test for #34 will not work
#  by running t2.rb alone, one has to run t1 before it; 'rake' does it)
mr = DBus::MatchRule.new.from_s "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
bus.add_match(mr) do |msg|
  new_unique_name = msg.params[2]
  unless new_unique_name.empty?
    DBus.logger.debug "RRRING #{new_unique_name}"
    bus.introspect_data(new_unique_name, "/") do
      # ignore the result
    end
  end
end

puts "listening, with ruby-#{RUBY_VERSION}"
main = DBus::Main.new
main << bus
begin
  main.run
rescue SystemCallError
  # the test driver will kill the bus, that's OK
end
