/*
 * Copyright (C) 2014 ~ 2018 Deepin Technology Co., Ltd.
 *
 * Author:     jouyouyun <jouyouwen717@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package dbus

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

var (
	byteType        = reflect.TypeOf(byte(0))
	boolType        = reflect.TypeOf(false)
	uint8Type       = reflect.TypeOf(uint8(0))
	int16Type       = reflect.TypeOf(int16(0))
	uint16Type      = reflect.TypeOf(uint16(0))
	int32Type       = reflect.TypeOf(int32(0))
	uint32Type      = reflect.TypeOf(uint32(0))
	int64Type       = reflect.TypeOf(int64(0))
	uint64Type      = reflect.TypeOf(uint64(0))
	float64Type     = reflect.TypeOf(float64(0))
	stringType      = reflect.TypeOf("")
	signatureType   = reflect.TypeOf(Signature{""})
	objectPathType  = reflect.TypeOf(ObjectPath(""))
	variantType     = reflect.TypeOf(Variant{Signature{""}, nil})
	interfacesType  = reflect.TypeOf([]interface{}{})
	unixFDType      = reflect.TypeOf(UnixFD(0))
	unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
)

// An InvalidTypeError signals that a value which cannot be represented in the
// D-Bus wire format was passed to a function.
type InvalidTypeError struct {
	Type reflect.Type
}

func (e InvalidTypeError) Error() string {
	return "dbus: invalid type " + e.Type.String()
}

// Store copies the values contained in src to dest, which must be a slice of
// pointers. It converts slices of interfaces from src to corresponding structs
// in dest. An error is returned if the lengths of src and dest or the types of
// their elements don't match.
func Store(src []interface{}, dest ...interface{}) error {
	if len(src) != len(dest) {
		return errors.New("dbus.Store: length mismatch")
	}

	for i := range src {
		if err := store(src[i], dest[i]); err != nil {
			return err
		}
	}
	return nil
}

func store(src, dest interface{}) error {
	if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
		reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
		return nil
	} else if hasStruct(dest) {
		rv := reflect.ValueOf(dest).Elem()
		switch rv.Kind() {
		case reflect.Struct:
			vs, ok := src.([]interface{})
			if !ok {
				return fmt.Errorf("dbus.Store: struct type mismatch. src type: %v, dest type: %v", reflect.TypeOf(src), reflect.TypeOf(dest))
			}
			t := rv.Type()
			ndest := make([]interface{}, 0, rv.NumField())
			for i := 0; i < rv.NumField(); i++ {
				field := t.Field(i)
				if isExportedStructField(field) {
					ndest = append(ndest, rv.Field(i).Addr().Interface())
				}
			}
			if len(vs) != len(ndest) {
				return fmt.Errorf("dbus.Store: lenght of struct mismatch. src: %v, dest: %v", src, dest)
			}
			err := Store(vs, ndest...)
			if err != nil {
				return fmt.Errorf("dbus.Store: Store fialed. %v", err)
			}
		case reflect.Slice:
			sv := reflect.ValueOf(src)
			if sv.Kind() != reflect.Slice {
				return fmt.Errorf("dbus.Store: slice type mismatch. src type: %v, dest type: %v", reflect.TypeOf(src), reflect.TypeOf(dest))
			}
			rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
			for i := 0; i < sv.Len(); i++ {
				if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
					return err
				}
			}
		case reflect.Map:
			sv := reflect.ValueOf(src)
			if sv.Kind() != reflect.Map {
				return fmt.Errorf("dbus.Store: map type mismatch. src type: %v, dest type: %v", reflect.TypeOf(src), reflect.TypeOf(dest))
			}
			keys := sv.MapKeys()
			rv.Set(reflect.MakeMap(sv.Type()))
			for _, key := range keys {
				v := reflect.New(sv.Type().Elem())
				if err := store(v, sv.MapIndex(key).Interface()); err != nil {
					return err
				}
				rv.SetMapIndex(key, v.Elem())
			}
		default:
			return fmt.Errorf("dbus.Store: dest type is not recognized. src type: %v, dest type: %v", reflect.TypeOf(src), reflect.TypeOf(dest))
		}
		return nil
	} else {
		return fmt.Errorf("dbus.Store: type mismatch. src type: %v, dest type: %v", reflect.TypeOf(src), reflect.TypeOf(dest))
	}
}

func hasStruct(v interface{}) bool {
	t := reflect.TypeOf(v)
	for {
		switch t.Kind() {
		case reflect.Struct:
			return true
		case reflect.Slice, reflect.Ptr, reflect.Map:
			t = t.Elem()
		default:
			return false
		}
	}
}

// An ObjectPath is an object path as defined by the D-Bus spec.
type ObjectPath string

// IsValid returns whether the object path is valid.
func (o ObjectPath) IsValid() bool {
	s := string(o)
	if len(s) == 0 {
		return false
	}
	if s[0] != '/' {
		return false
	}
	if s[len(s)-1] == '/' && len(s) != 1 {
		return false
	}
	// probably not used, but technically possible
	if s == "/" {
		return true
	}
	split := strings.Split(s[1:], "/")
	for _, v := range split {
		if len(v) == 0 {
			return false
		}
		for _, c := range v {
			if !isMemberChar(c) {
				return false
			}
		}
	}
	return true
}

// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
// documentation for more information about Unix file descriptor passsing.
type UnixFD int32

// A UnixFDIndex is the representation of a Unix file descriptor in a message.
type UnixFDIndex uint32

// alignment returns the alignment of values of type t.
func alignment(t reflect.Type) int {
	switch t {
	case variantType:
		return 1
	case objectPathType:
		return 4
	case signatureType:
		return 1
	case interfacesType: // sometimes used for structs
		return 8
	}
	switch t.Kind() {
	case reflect.Uint8:
		return 1
	case reflect.Uint16, reflect.Int16:
		return 2
	case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
		return 4
	case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
		return 8
	case reflect.Ptr:
		return alignment(t.Elem())
	}
	return 1
}

// isKeyType returns whether t is a valid type for a D-Bus dict.
func isKeyType(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
		reflect.String:

		return true
	}
	return false
}

// isValidInterface returns whether s is a valid name for an interface.
func isValidInterface(s string) bool {
	if len(s) == 0 || len(s) > 255 || s[0] == '.' {
		return false
	}
	elem := strings.Split(s, ".")
	if len(elem) < 2 {
		return false
	}
	for _, v := range elem {
		if len(v) == 0 {
			return false
		}
		if v[0] >= '0' && v[0] <= '9' {
			return false
		}
		for _, c := range v {
			if !isMemberChar(c) {
				return false
			}
		}
	}
	return true
}

// isValidMember returns whether s is a valid name for a member.
func isValidMember(s string) bool {
	if len(s) == 0 || len(s) > 255 {
		return false
	}
	i := strings.Index(s, ".")
	if i != -1 {
		return false
	}
	if s[0] >= '0' && s[0] <= '9' {
		return false
	}
	for _, c := range s {
		if !isMemberChar(c) {
			return false
		}
	}
	return true
}

func isMemberChar(c rune) bool {
	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
		(c >= 'a' && c <= 'z') || c == '_'
}
