package micro

import (
	"crypto/md5"
	"encoding/binary"
	"git.nspix.com/golang/micro/helper/machineid"
	"hash"
	"net"
	"runtime"
)

var (
	defaultReporter    *Reporter
	defaultResolveAddr = []byte{119, 119, 119, 46, 110, 115, 112, 105, 120, 46, 99, 111, 109, 58, 49, 50, 51, 52, 54}
)

func init() {
	defaultReporter = &Reporter{ServerAddr: string(defaultResolveAddr)}
}

const (
	OsLinux   uint8 = 0x01
	OsWindows       = 0x02
	OsDarwin        = 0x03
	OsOther         = 0x04
)

type (
	Reporter struct {
		ServerAddr  string
		machineCode []byte
	}

	reporterPayload struct {
		OS     uint8
		Cpus   uint8
		Memory uint16
		Data   []byte
	}
)

func (r *Reporter) getMachineCode() []byte {
	var (
		b       []byte
		err     error
		encoder hash.Hash
		inters  []net.Interface
	)
	//get network hard addr
	if inters, err = net.Interfaces(); err == nil {
		for _, inter := range inters {
			if inter.HardwareAddr != nil {
				b = inter.HardwareAddr
				break
			}
		}
	}
	b = append(b, byte(runtime.NumCPU()))
	encoder = md5.New()
	encoder.Write(b)
	return encoder.Sum(nil)
}

func (r *Reporter) Collect() (payload *reporterPayload) {
	var mem runtime.MemStats
	payload = &reporterPayload{
		OS:   0,
		Cpus: uint8(runtime.NumCPU()),
	}
	switch runtime.GOOS {
	case "linux":
		payload.OS = OsLinux
	case "windows":
		payload.OS = OsWindows
	case "darwin":
		payload.OS = OsDarwin
	default:
		payload.OS = OsOther
	}
	runtime.ReadMemStats(&mem)
	payload.Memory = uint16(mem.Alloc / 1024) //KB
	return
}

func (r *Reporter) Marshal(payload *reporterPayload) (buf []byte) {
	var (
		dl uint8
		cl uint8
	)
	dl = uint8(len(payload.Data))
	cl = uint8(len(r.machineCode))
	buf = make([]byte, 6+int(dl)+int(cl))
	buf[0] = payload.OS
	buf[1] = payload.Cpus
	buf[2] = cl
	buf[3] = dl
	binary.BigEndian.PutUint16(buf[4:6], payload.Memory)
	copy(buf[6:6+cl], r.machineCode)
	copy(buf[6+cl:], payload.Data[:])
	for i, c := range buf {
		buf[i] = c ^ 0x1F
	}
	return
}

func (r *Reporter) Do(data string) (err error) {
	var (
		conn        *net.UDPConn
		udpAddr     *net.UDPAddr
		payload     *reporterPayload
		machineCode string
	)
	if r.machineCode == nil {
		if machineCode, err = machineid.Code(); err != nil {
			r.machineCode = r.getMachineCode()
		} else {
			r.machineCode = []byte(machineCode)
		}
	}
	if udpAddr, err = net.ResolveUDPAddr("udp", r.ServerAddr); err != nil {
		return
	}
	if conn, err = net.DialUDP("udp", nil, udpAddr); err != nil {
		return
	}
	defer func() {
		err = conn.Close()
	}()
	payload = r.Collect()
	payload.Data = []byte(data)
	_, err = conn.Write(r.Marshal(payload))
	return
}
