// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"math/rand"
	"time"

	"github.com/aristanetworks/glog"
	kcp "github.com/xtaci/kcp-go"
)

type udpClient struct {
	addr    string
	conn    *kcp.UDPSession
	parity  int
	timeout time.Duration
}

func newUDPClient(addr string, parity int, timeout time.Duration) OpenTSDBConn {
	return &udpClient{
		addr:    addr,
		parity:  parity,
		timeout: timeout,
	}
}

func (c *udpClient) Put(d *DataPoint) error {
	var err error
	if c.conn == nil {
		// Prevent a bunch of clients all disconnecting and attempting to reconnect
		// at nearly the same time.
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)

		c.conn, err = kcp.DialWithOptions(c.addr, nil, 10, c.parity)
		if err != nil {
			return err
		}
		c.conn.SetNoDelay(1, 40, 1, 1) // Suggested by kcp-go to lower cpu usage
	}

	dStr := d.String()
	glog.V(3).Info(dStr)

	c.conn.SetWriteDeadline(time.Now().Add(c.timeout))
	_, err = c.conn.Write([]byte(dStr))
	if err != nil {
		c.conn.Close()
		c.conn = nil
	}
	return err
}

type udpServer struct {
	lis    *kcp.Listener
	telnet *telnetClient
}

func newUDPServer(udpAddr, tsdbAddr string, parity int) (*udpServer, error) {
	lis, err := kcp.ListenWithOptions(udpAddr, nil, 10, parity)
	if err != nil {
		return nil, err
	}
	return &udpServer{
		lis:    lis,
		telnet: newTelnetClient(tsdbAddr).(*telnetClient),
	}, nil
}

func (c *udpServer) Run() error {
	for {
		conn, err := c.lis.AcceptKCP()
		if err != nil {
			return err
		}
		conn.SetNoDelay(1, 40, 1, 1) // Suggested by kcp-go to lower cpu usage
		if glog.V(3) {
			glog.Infof("New connection from %s", conn.RemoteAddr())
		}
		go func() {
			defer conn.Close()
			var buf [4096]byte
			for {
				n, err := conn.Read(buf[:])
				if err != nil {
					if n != 0 { // Not EOF
						glog.Error(err)
					}
					return
				}
				if glog.V(3) {
					glog.Info(string(buf[:n]))
				}
				err = c.telnet.PutBytes(buf[:n])
				if err != nil {
					glog.Error(err)
					return
				}
			}
		}()
	}
}
