/*
File Name:  Node.go
Copyright:  2021 Peernet s.r.o.
Author:     Peter Kleissner
*/

package dht

import (
	"bytes"
	"math/big"
	"time"
)

// Node is the over-the-wire representation of a node
type Node struct {
	// ID is the unique identifier
	ID []byte

	// LastSeen when was this node last considered seen by the DHT
	LastSeen time.Time

	// Info is an arbitrary pointer specified by the caller
	Info interface{}
}

// shortList is used in order to sort a list of arbitrary nodes against a comparator. These nodes are sorted by xor distance
type shortList struct {
	// Nodes are a list of nodes to be compared
	Nodes []*Node

	// Comparator is the ID to compare to
	Comparator []byte

	// Contacted is a list of nodes that are considered contacted
	Contacted map[string]bool
}

func newShortList() *shortList {
	return &shortList{
		Contacted: make(map[string]bool),
	}
}

func areNodesEqual(n1 *Node, n2 *Node, allowNilID bool) bool {
	if n1 == nil || n2 == nil {
		return false
	}
	if !allowNilID {
		if n1.ID == nil || n2.ID == nil {
			return false
		}
		if bytes.Compare(n1.ID, n2.ID) != 0 {
			return false
		}
	}
	return true
}

func (n *shortList) RemoveNode(ID []byte) {
	for i := 0; i < n.Len(); i++ {
		if bytes.Compare(n.Nodes[i].ID, ID) == 0 {
			n.Nodes = append(n.Nodes[:i], n.Nodes[i+1:]...)
			return
		}
	}
}

func (n *shortList) AppendUniqueNodes(nodes ...*Node) {
nodesLoop:
	for _, vv := range nodes {
		for _, v := range n.Nodes {
			if bytes.Compare(v.ID, vv.ID) == 0 {
				continue nodesLoop
			}
		}
		n.Nodes = append(n.Nodes, vv)
	}
}

func (n *shortList) Len() int {
	return len(n.Nodes)
}

func (n *shortList) Swap(i, j int) {
	n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
}

func (n *shortList) Less(i, j int) bool {
	iDist := getDistance(n.Nodes[i].ID, n.Comparator)
	jDist := getDistance(n.Nodes[j].ID, n.Comparator)

	return iDist.Cmp(jDist) == -1
}

func getDistance(id1 []byte, id2 []byte) *big.Int {
	buf1 := new(big.Int).SetBytes(id1)
	buf2 := new(big.Int).SetBytes(id2)
	result := new(big.Int).Xor(buf1, buf2)
	return result
}

// GetUncontacted returns a list of uncontacted nodes. Each returned node will be marked as contacted.
func (n *shortList) GetUncontacted(count int, useCount bool) (Nodes []*Node) {
	for _, node := range n.Nodes {
		if useCount && count <= 0 {
			break
		}

		// Don't contact nodes already contacted
		if n.Contacted[string(node.ID)] == true {
			continue
		}

		n.Contacted[string(node.ID)] = true
		Nodes = append(Nodes, node)

		count--
	}

	return Nodes
}

// NodeMessage is a message sent by a node
type NodeMessage struct {
	SenderID []byte  // Sender of the message
	Data     []byte  // FIND_VALUE: Actual data
	Closest  []*Node // FIND_VALUE, FIND_NODE: Closest nodes to the requested key
	Storing  []*Node // FIND_VALUE: Nodes known to store the value
}

// NodeFilterFunc is called to filter nodes based on the callers choice
type NodeFilterFunc func(node *Node) (accept bool)
