package introspect

import (
	"encoding/xml"
	"reflect"
	"strings"

	"github.com/godbus/dbus/v5"
)

// Introspectable implements org.freedesktop.Introspectable.
//
// You can create it by converting the XML-formatted introspection data from a
// string to an Introspectable or call NewIntrospectable with a Node. Then,
// export it as org.freedesktop.Introspectable on you object.
type Introspectable string

// NewIntrospectable returns an Introspectable that returns the introspection
// data that corresponds to the given Node. If n.Interfaces doesn't contain the
// data for org.freedesktop.DBus.Introspectable, it is added automatically.
func NewIntrospectable(n *Node) Introspectable {
	found := false
	for _, v := range n.Interfaces {
		if v.Name == "org.freedesktop.DBus.Introspectable" {
			found = true
			break
		}
	}
	if !found {
		n.Interfaces = append(n.Interfaces, IntrospectData)
	}
	b, err := xml.Marshal(n)
	if err != nil {
		panic(err)
	}
	return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b))
}

// Introspect implements org.freedesktop.Introspectable.Introspect.
func (i Introspectable) Introspect() (string, *dbus.Error) {
	return string(i), nil
}

// Methods returns the description of the methods of v. This can be used to
// create a Node which can be passed to NewIntrospectable.
func Methods(v interface{}) []Method {
	t := reflect.TypeOf(v)
	ms := make([]Method, 0, t.NumMethod())
	for i := 0; i < t.NumMethod(); i++ {
		if t.Method(i).PkgPath != "" {
			continue
		}
		mt := t.Method(i).Type
		if mt.NumOut() == 0 ||
			mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{}) {

			continue
		}
		var m Method
		m.Name = t.Method(i).Name
		m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
		for j := 1; j < mt.NumIn(); j++ {
			if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() &&
				mt.In(j) != reflect.TypeOf((*dbus.Message)(nil)).Elem() {
				arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
				m.Args = append(m.Args, arg)
			}
		}
		for j := 0; j < mt.NumOut()-1; j++ {
			arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
			m.Args = append(m.Args, arg)
		}
		m.Annotations = make([]Annotation, 0)
		ms = append(ms, m)
	}
	return ms
}
