package dbus

import (
	"bytes"
	"reflect"
	"strings"
	"sync"
)

func newIntrospectIntf(h *defaultHandler) *exportedIntf {
	methods := make(map[string]Method)
	methods["Introspect"] = exportedMethod{
		reflect.ValueOf(func(msg Message) (string, *Error) {
			path := msg.Headers[FieldPath].value.(ObjectPath)
			return h.introspectPath(path), nil
		}),
	}
	return newExportedIntf(methods, true)
}

//NewDefaultHandler returns an instance of the default
//call handler. This is useful if you want to implement only
//one of the two handlers but not both.
func NewDefaultHandler() *defaultHandler {
	h := &defaultHandler{
		objects:     make(map[ObjectPath]*exportedObj),
		defaultIntf: make(map[string]*exportedIntf),
	}
	h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
	return h
}

type defaultHandler struct {
	sync.RWMutex
	objects     map[ObjectPath]*exportedObj
	defaultIntf map[string]*exportedIntf
}

func (h *defaultHandler) PathExists(path ObjectPath) bool {
	_, ok := h.objects[path]
	return ok
}

func (h *defaultHandler) introspectPath(path ObjectPath) string {
	subpath := make(map[string]struct{})
	var xml bytes.Buffer
	xml.WriteString("<node>")
	for obj, _ := range h.objects {
		p := string(path)
		if p != "/" {
			p += "/"
		}
		if strings.HasPrefix(string(obj), p) {
			node_name := strings.Split(string(obj[len(p):]), "/")[0]
			subpath[node_name] = struct{}{}
		}
	}
	for s, _ := range subpath {
		xml.WriteString("\n\t<node name=\"" + s + "\"/>")
	}
	xml.WriteString("\n</node>")
	return xml.String()
}

func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
	h.RLock()
	defer h.RUnlock()
	object, ok := h.objects[path]
	if ok {
		return object, ok
	}

	// If an object wasn't found for this exact path,
	// look for a matching subtree registration
	subtreeObject := newExportedObject()
	path = path[:strings.LastIndex(string(path), "/")]
	for len(path) > 0 {
		object, ok = h.objects[path]
		if ok {
			for name, iface := range object.interfaces {
				// Only include this handler if it registered for the subtree
				if iface.isFallbackInterface() {
					subtreeObject.interfaces[name] = iface
				}
			}
			break
		}

		path = path[:strings.LastIndex(string(path), "/")]
	}

	for name, intf := range h.defaultIntf {
		if _, exists := subtreeObject.interfaces[name]; exists {
			continue
		}
		subtreeObject.interfaces[name] = intf
	}

	return subtreeObject, true
}

func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
	h.Lock()
	h.objects[path] = object
	h.Unlock()
}

func (h *defaultHandler) DeleteObject(path ObjectPath) {
	h.Lock()
	delete(h.objects, path)
	h.Unlock()
}

type exportedMethod struct {
	reflect.Value
}

func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
	t := m.Type()

	params := make([]reflect.Value, len(args))
	for i := 0; i < len(args); i++ {
		params[i] = reflect.ValueOf(args[i]).Elem()
	}

	ret := m.Value.Call(params)

	err := ret[t.NumOut()-1].Interface().(*Error)
	ret = ret[:t.NumOut()-1]
	out := make([]interface{}, len(ret))
	for i, val := range ret {
		out[i] = val.Interface()
	}
	if err == nil {
		//concrete type to interface nil is a special case
		return out, nil
	}
	return out, err
}

func (m exportedMethod) NumArguments() int {
	return m.Value.Type().NumIn()
}

func (m exportedMethod) ArgumentValue(i int) interface{} {
	return reflect.Zero(m.Type().In(i)).Interface()
}

func (m exportedMethod) NumReturns() int {
	return m.Value.Type().NumOut()
}

func (m exportedMethod) ReturnValue(i int) interface{} {
	return reflect.Zero(m.Type().Out(i)).Interface()
}

func newExportedObject() *exportedObj {
	return &exportedObj{
		interfaces: make(map[string]*exportedIntf),
	}
}

type exportedObj struct {
	interfaces map[string]*exportedIntf
}

func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
	if name == "" {
		return obj, true
	}
	intf, exists := obj.interfaces[name]
	return intf, exists
}

func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
	obj.interfaces[name] = iface
}

func (obj *exportedObj) DeleteInterface(name string) {
	delete(obj.interfaces, name)
}

func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
	for _, intf := range obj.interfaces {
		method, exists := intf.LookupMethod(name)
		if exists {
			return method, exists
		}
	}
	return nil, false
}

func (obj *exportedObj) isFallbackInterface() bool {
	return false
}

func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
	return &exportedIntf{
		methods:        methods,
		includeSubtree: includeSubtree,
	}
}

type exportedIntf struct {
	methods map[string]Method

	// Whether or not this export is for the entire subtree
	includeSubtree bool
}

func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
	out, exists := obj.methods[name]
	return out, exists
}

func (obj *exportedIntf) isFallbackInterface() bool {
	return obj.includeSubtree
}

//NewDefaultSignalHandler returns an instance of the default
//signal handler. This is useful if you want to implement only
//one of the two handlers but not both.
func NewDefaultSignalHandler() *defaultSignalHandler {
	return &defaultSignalHandler{}
}

func isDefaultSignalHandler(handler SignalHandler) bool {
	_, ok := handler.(*defaultSignalHandler)
	return ok
}

type defaultSignalHandler struct {
	sync.RWMutex
	closed  bool
	signals []chan<- *Signal
}

func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
	go func() {
		sh.RLock()
		defer sh.RUnlock()
		if sh.closed {
			return
		}
		for _, ch := range sh.signals {
			ch <- signal
		}
	}()
}

func (sh *defaultSignalHandler) Init() error {
	sh.Lock()
	sh.signals = make([]chan<- *Signal, 0)
	sh.Unlock()
	return nil
}

func (sh *defaultSignalHandler) Terminate() {
	sh.Lock()
	sh.closed = true
	for _, ch := range sh.signals {
		close(ch)
	}
	sh.signals = nil
	sh.Unlock()
}

func (sh *defaultSignalHandler) addSignal(ch chan<- *Signal) {
	sh.Lock()
	defer sh.Unlock()
	if sh.closed {
		return
	}
	sh.signals = append(sh.signals, ch)

}

func (sh *defaultSignalHandler) removeSignal(ch chan<- *Signal) {
	sh.Lock()
	defer sh.Unlock()
	if sh.closed {
		return
	}
	for i := len(sh.signals) - 1; i >= 0; i-- {
		if ch == sh.signals[i] {
			copy(sh.signals[i:], sh.signals[i+1:])
			sh.signals[len(sh.signals)-1] = nil
			sh.signals = sh.signals[:len(sh.signals)-1]
		}
	}
}