package icsnet

import (
	"net"
	"strconv"
	"time"

	"gitlab.com/ics_cinnamon/voiceStatistics/icserror"
)

type IcsUDPNet struct {
	laddr *IcsNetAddr
	raddr *IcsNetAddr
	conn  *IcsConn
}

func NewUDP(laddr, raddr *IcsNetAddr) (udpnet *IcsUDPNet) {
	udpnet = &IcsUDPNet{
		laddr: laddr,
		raddr: raddr,
		conn:  &IcsConn{},
	}

	return udpnet
}

func (u *IcsUDPNet) GetRemoteAddr() *IcsNetAddr {
	addr := u.conn.ICSUDPConn.RemoteAddr()
	if addr == nil {
		return nil
	}
	ip, sport, _ := net.SplitHostPort(addr.String())
	port, _ := strconv.Atoi(sport)

	ret := NewNetAddrWithIPPort(ip, port)
	return &ret
}

func (u *IcsUDPNet) SetRemoteAddr(raddr *IcsNetAddr) {
	u.raddr = raddr
}

func (u *IcsUDPNet) Listen() *icserror.IcsError {
	addr, err := u.laddr.ResolveIcsNetAddr("udp")
	if err != nil {
		return err
	}

	var aerr error
	switch v := addr.(type) {
	case net.UDPAddr:
		u.conn.ICSUDPConn, aerr = net.ListenUDP("udp4", &v)
		if aerr != nil {
			icserror.ICSERRNETListenError.SetError(aerr)
			return icserror.ICSERRNETListenError
		}
	}

	return nil
}

func (u *IcsUDPNet) Connect(localAddr, remoteAddr *IcsNetAddr) *icserror.IcsError {
	//var ra *net.UDPAddr
	var la, ra *net.UDPAddr

	if localAddr != nil {
		u.laddr = localAddr
	}
	if remoteAddr != nil {
		u.raddr = remoteAddr
	}

	if u.laddr != nil {
		laddr, err := u.laddr.ResolveIcsNetAddr("udp")
		if err != nil {
			return err
		}
		switch v := laddr.(type) {
		case net.UDPAddr:
			la = &v
		}
	} else {
		la = nil
	}

	if u.raddr != nil {
		raddr, err := u.raddr.ResolveIcsNetAddr("udp")
		if err != nil {
			return err
		}
		switch v := raddr.(type) {
		case net.UDPAddr:
			ra = &v
		}
	} else {
		return icserror.ICSERRInvalidParam
	}

	var derr error
	u.conn.ICSUDPConn, derr = net.DialUDP("udp", la, ra)
	if derr != nil {
		icserror.ICSERRNETConnectError.SetError(derr)
		return icserror.ICSERRNETConnectError
	}

	return nil
}

func (u *IcsUDPNet) Close() *icserror.IcsError {
	//fmt.Println("UDP close!!")

	if u.conn.ICSUDPConn == nil {
		return icserror.ICSERRNETNotConnectError
	}

	err := u.conn.ICSUDPConn.Close()
	if err != nil {
		icserror.ICSERRNETCloseError.SetError(err)
		return icserror.ICSERRNETCloseError
	}

	return nil
}

func (u *IcsUDPNet) Write(b []byte) (int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return 0, icserror.ICSERRNETNotConnectError
	}
	/*
		raddr := &net.UDPAddr{
			IP:   u.raddr.IPv4,
			Port: u.raddr.Port,
		}
		//fmt.Println("Write addr ", raddr)
		wsize, err := u.conn.ICSUDPConn.WriteTo(b, raddr)
	*/
	//wsize, err := u.conn.ICSUDPConn.WriteToUDP(b, raddr)
	wsize, err := u.conn.ICSUDPConn.Write(b)
	if err != nil {
		icserror.ICSERRNETWriteError.SetError(err)
		//fmt.Println("UDP WRITE ERROR -", err)
		return -1, icserror.ICSERRNETWriteError
	}
	return wsize, nil
}

func (u *IcsUDPNet) WriteSIP(b []byte) (int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return 0, icserror.ICSERRNETNotConnectError
	}
	raddr := &net.UDPAddr{
		IP:   u.raddr.IPv4,
		Port: u.raddr.Port,
	}
	//fmt.Println("Write addr ", raddr)
	//wsize, err := u.conn.ICSUDPConn.WriteTo(b, raddr)
	wsize, err := u.conn.ICSUDPConn.WriteToUDP(b, raddr)
	//wsize, err := u.conn.ICSUDPConn.Write(b)
	if err != nil {
		icserror.ICSERRNETWriteError.SetError(err)
		//fmt.Println("UDP WRITE ERROR -", err)
		return -1, icserror.ICSERRNETWriteError
	}
	return wsize, nil
}

func (u *IcsUDPNet) WriteRTP(b []byte) (int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return 0, icserror.ICSERRNETNotConnectError
	}
	raddr := &net.UDPAddr{
		IP:   u.raddr.IPv4,
		Port: u.raddr.Port,
	}
	//fmt.Println("Write addr ", raddr)
	//wsize, err := u.conn.ICSUDPConn.WriteTo(b, raddr)
	wsize, err := u.conn.ICSUDPConn.WriteToUDP(b, raddr)
	//wsize, err := u.conn.ICSUDPConn.Write(b)
	if err != nil {
		icserror.ICSERRNETWriteError.SetError(err)
		//fmt.Println("UDP WRITE ERROR -", err)
		return -1, icserror.ICSERRNETWriteError
	}
	return wsize, nil
}

func (u *IcsUDPNet) Read(size int) ([]byte, int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return nil, -1, icserror.ICSERRNETNotConnectError
	}

	buf := make([]byte, size)

	for rlen := 0; ; {
		len, err := u.conn.ICSUDPConn.Read(buf)
		if err == nil {
			//fmt.Println(len, string(buf))
			rlen += len
			if rlen >= size {
				//fmt.Println(">>>", rlen, string(buf))
				return buf, rlen, nil
			}
		} else {
			icserror.ICSERRNETReadError.SetError(err)
			return nil, -1, icserror.ICSERRNETReadError
		}
	}
}

func (u *IcsUDPNet) ReadSIP() ([]byte, *net.UDPAddr, int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return nil, nil, -1, icserror.ICSERRNETNotConnectError
	}

	buf := make([]byte, 10240)

	len, addr, err := u.conn.ICSUDPConn.ReadFromUDP(buf[:])
	//len, err := u.conn.ICSUDPConn.Read(buf[:])
	if err == nil {
		//fmt.Println("remote addr", u.conn.ICSUDPConn.RemoteAddr(), u.conn.ICSUDPConn.LocalAddr())
		return buf, addr, len, nil
	} else {
		icserror.ICSERRNETReadError.SetError(err)
		return nil, nil, -1, icserror.ICSERRNETReadError
	}
}

func (u *IcsUDPNet) ReadRTP() ([]byte, *net.UDPAddr, int, *icserror.IcsError) {
	if u.conn.ICSUDPConn == nil {
		return nil, nil, -1, icserror.ICSERRNETNotConnectError
	}

	buf := make([]byte, 10240)

	len, addr, err := u.conn.ICSUDPConn.ReadFromUDP(buf[:])
	if err == nil {
		//fmt.Println("remote addr", u.conn.ICSUDPConn.RemoteAddr(), u.conn.ICSUDPConn.LocalAddr())
		return buf, addr, len, nil
	} else {
		icserror.ICSERRNETReadError.SetError(err)
		return nil, nil, -1, icserror.ICSERRNETReadError
	}
}

func (u *IcsUDPNet) LocalAddr() net.Addr {
	return u.conn.ICSUDPConn.LocalAddr()
}

func (u *IcsUDPNet) RemoteAddr() net.Addr {
	raddr := &net.UDPAddr{
		IP:   u.raddr.IPv4,
		Port: u.raddr.Port,
	}
	return raddr
}

func (u *IcsUDPNet) SetDeadLine(sec int64) {
	u.conn.ICSUDPConn.SetDeadline(time.Now().Add(time.Second * time.Duration(sec)))
}

func (u *IcsUDPNet) SetWriteDeadLine(sec int64) {
	u.conn.ICSUDPConn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(sec)))
}

func (u *IcsUDPNet) SetReadDeadLine(sec int64) {
	u.conn.ICSUDPConn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(sec)))
}
