package krpc

import (
	"github.com/anacrolix/torrent/bencode"
)

// Msg represents messages that nodes in the network send to each other as specified by the protocol.
// They are also referred to as the KRPC messages.
// There are three types of messages: QUERY, RESPONSE, ERROR
// The message is a dictionary that is then
// "bencoded" (serialization & compression format adopted by the BitTorrent)
// and sent via the UDP connection to peers.
//
// A KRPC message is a single dictionary with two keys common to every message and additional keys depending on the type of message.
// Every message has a key "t" with a string value representing a transaction ID.
// This transaction ID is generated by the querying node and is echoed in the response, so responses
// may be correlated with multiple queries to the same node. The transaction ID should be encoded as a short string of binary numbers, typically 2 characters are enough as they cover 2^16 outstanding queries. The other key contained in every KRPC message is "y" with a single character value describing the type of message. The value of the "y" key is one of "q" for query, "r" for response, or "e" for error.
// 3 message types:  QUERY, RESPONSE, ERROR
type Msg struct {
	Q        string   `bencode:"q,omitempty"` // Query method (one of 4: "ping", "find_node", "get_peers", "announce_peer")
	A        *MsgArgs `bencode:"a,omitempty"` // named arguments sent with a query
	T        string   `bencode:"t"`           // required: transaction ID
	Y        string   `bencode:"y"`           // required: type of the message: q for QUERY, r for RESPONSE, e for ERROR
	R        *Return  `bencode:"r,omitempty"` // RESPONSE type only
	E        *Error   `bencode:"e,omitempty"` // ERROR type only
	IP       NodeAddr `bencode:"ip,omitempty"`
	ReadOnly bool     `bencode:"ro,omitempty"` // BEP 43. Sender does not respond to queries.
	// https://www.libtorrent.org/dht_extensions.html
	ClientId string `bencode:"v,omitempty"`
}

const (
	YQuery    = "q"
	YResponse = "r"
	YError    = "e"
)

type MsgArgs struct {
	ID       ID `bencode:"id"`                  // ID of the querying Node
	InfoHash ID `bencode:"info_hash,omitempty"` // InfoHash of the torrent
	Target   ID `bencode:"target,omitempty"`    // ID of the node sought
	// Token received from an earlier get_peers query. Also used in a BEP 44 put.
	Token       string `bencode:"token,omitempty"`
	Port        *int   `bencode:"port,omitempty"`         // Sender's torrent port
	ImpliedPort bool   `bencode:"implied_port,omitempty"` // Use senders apparent DHT port
	Want        []Want `bencode:"want,omitempty"`         // Contains strings like "n4" and "n6" from BEP 32.
	NoSeed      int    `bencode:"noseed,omitempty"`       // BEP 33
	Scrape      int    `bencode:"scrape,omitempty"`       // BEP 33

	// BEP 44

	// I don't know if we should use bencode.Bytes for this. If we unmarshalled bytes that didn't
	// marshal back the same, our hashes will not match. But this might also serve to prevent abuse.
	V interface{} `bencode:"v,omitempty"`
	// Why is this optional? Because I think we need to know if it wasn't set rather than use a
	// default value.
	Seq  *int64   `bencode:"seq,omitempty"`
	Cas  int64    `bencode:"cas,omitempty"`
	K    [32]byte `bencode:"k,omitempty"`
	Salt []byte   `bencode:"salt,omitempty"`
	Sig  [64]byte `bencode:"sig,omitempty"`
}

type Want string

const (
	WantNodes  Want = "n4"
	WantNodes6 Want = "n6"
)

// BEP 51 (DHT Infohash Indexing)
type Bep51Return struct {
	Interval *int64 `bencode:"interval,omitempty"`
	Num      *int64 `bencode:"num,omitempty"`
	// Nodes supporting this extension should always include the samples field in the response, even
	// when it is zero-length. This lets indexing nodes to distinguish nodes supporting this
	// extension from those that respond to unknown query types which contain a target field [2].
	Samples *CompactInfohashes `bencode:"samples,omitempty"`
}

type Bep44Return struct {
	V   bencode.Bytes `bencode:"v,omitempty"`
	K   [32]byte      `bencode:"k,omitempty"`
	Sig [64]byte      `bencode:"sig,omitempty"`
	Seq *int64        `bencode:"seq,omitempty"`
}

type Return struct {
	// All returns are supposed to contain an ID, but what if they don't?
	ID ID `bencode:"id"` // ID of the queried (and responding) node

	// K closest nodes to the requested target. Included in responses to queries that imply
	// traversal, for example get_peers, find_nodes, get, sample_infohashes.
	Nodes  CompactIPv4NodeInfo `bencode:"nodes,omitempty"`
	Nodes6 CompactIPv6NodeInfo `bencode:"nodes6,omitempty"`

	Token  *string    `bencode:"token,omitempty"`  // Token for future announce_peer or put (BEP 44)
	Values []NodeAddr `bencode:"values,omitempty"` // Torrent peers

	// BEP 33 (scrapes)
	BFsd *ScrapeBloomFilter `bencode:"BFsd,omitempty"`
	BFpe *ScrapeBloomFilter `bencode:"BFpe,omitempty"`

	Bep51Return

	// BEP 44
	Bep44Return
}

func (r Return) ForAllNodes(f func(NodeInfo)) {
	for _, n := range r.Nodes {
		f(n)
	}
	for _, n := range r.Nodes6 {
		f(n)
	}
}

// The node ID of the source of this Msg. Returns nil if it isn't present.
// TODO: Can we verify Msgs more aggressively so this is guaranteed to return
// a valid ID for a checked Msg?
func (m Msg) SenderID() *ID {
	switch m.Y {
	case "q":
		if m.A == nil {
			return nil
		}
		return &m.A.ID
	case "r":
		if m.R == nil {
			return nil
		}
		return &m.R.ID
	}
	return nil
}

// This does not return an error, but (*Error)(nil) is still a non-nil error. You have been warned!
// This language is evil.
func (m Msg) Error() *Error {
	if m.Y != "e" {
		return nil
	}
	return m.E
}
