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

Transfer message encoding:
Offset  Size   Info
0       1      Control
1       1      Transfer Protocol
2       32     File Hash
34      ?      Embedded protocol data

*/

package protocol

import (
	"encoding/binary"
	"errors"

	"github.com/PeernetOfficial/core/btcec"
)

// MessageTransfer is the decoded transfer message.
// It is sent to initiate a file transfer, and to send data as part of a file transfer. The actual file data is encapsulated via UDT.
type MessageTransfer struct {
	*MessageRaw             // Underlying raw message.
	Control          uint8  // Control. See TransferControlX.
	TransferProtocol uint8  // Embedded transfer protocol: 0 = UDT
	Hash             []byte // Hash of the file to transfer.
	Offset           uint64 // Offset to start reading at. Only TransferControlRequestStart.
	Limit            uint64 // Limit (count of bytes) to read starting at the offset. Only TransferControlRequestStart.
	Data             []byte // Embedded protocol data. Only TransferControlActive.
}

const (
	TransferControlRequestStart = 0 // Request start transfer of file. Data at byte 34 is offset and limit to read, each 8 bytes. Limit may be 0 to indicate entire file.
	TransferControlNotAvailable = 1 // Requested file not available
	TransferControlActive       = 2 // Active file transfer
	TransferControlTerminate    = 3 // Terminate
)

const transferPayloadHeaderSize = 34

// DecodeTransfer decodes a transfer message
func DecodeTransfer(msg *MessageRaw) (result *MessageTransfer, err error) {
	if len(msg.Payload) < transferPayloadHeaderSize {
		return nil, errors.New("transfer: invalid minimum length")
	}

	result = &MessageTransfer{
		MessageRaw: msg,
		Hash:       make([]byte, HashSize),
	}

	result.Control = msg.Payload[0]
	result.TransferProtocol = msg.Payload[1]
	copy(result.Hash, msg.Payload[2:2+HashSize])

	switch result.Control {
	case TransferControlRequestStart:
		// Offset and Limit must be provided after the header.
		if len(msg.Payload) < transferPayloadHeaderSize+16 {
			return nil, errors.New("transfer: invalid minimum length")
		}

		result.Offset = binary.LittleEndian.Uint64(msg.Payload[34 : 34+8])
		result.Limit = binary.LittleEndian.Uint64(msg.Payload[42 : 42+8])

	case TransferControlActive:
		result.Data = msg.Payload[34:]

	}

	return result, nil
}

// TransferMaxEmbedSize is a recommended default upper size of embedded data inside the Transfer message, to be used as MaxPacketSize limit in the embedded protocol.
// This value is chosen as the lowest denominator of different environments (IPv4, IPv6, Ethernet, Internet) for safe transfer, not for highest performance.
// The caller may send bigger payloads but may risk that data packets are simply dropped and never arrive. A MTU negotiation or detection could pimp that.
const TransferMaxEmbedSize = internetSafeMTU - PacketLengthMin - transferPayloadHeaderSize

// EncodeTransfer encodes a transfer message. The embedded packet size must be smaller than TransferMaxEmbedSize.
func EncodeTransfer(senderPrivateKey *btcec.PrivateKey, data []byte, control, transferProtocol uint8, hash []byte, offset, limit uint64) (packetRaw []byte, err error) {
	if control == TransferControlRequestStart && len(data) != 0 {
		return nil, errors.New("transfer encode: payload not allowed in start")
	} else if isPacketSizeExceed(transferPayloadHeaderSize, len(data)) {
		return nil, errors.New("transfer encode: embedded packet too big")
	}

	packetSize := transferPayloadHeaderSize
	if control == TransferControlRequestStart {
		packetSize += 16
	} else if control == TransferControlActive {
		packetSize += len(data)
	}

	raw := make([]byte, packetSize)

	raw[0] = control
	raw[1] = transferProtocol
	copy(raw[2:2+HashSize], hash)

	if control == TransferControlRequestStart {
		binary.LittleEndian.PutUint64(raw[34:34+8], offset)
		binary.LittleEndian.PutUint64(raw[42:42+8], limit)
	} else if control == TransferControlActive {
		copy(raw[34:34+len(data)], data)
	}

	return raw, nil
}

// IsLast checks if the incoming message is the last one in this transfer.
func (msg *MessageTransfer) IsLast() bool {
	return msg.Control == TransferControlTerminate || msg.Control == TransferControlNotAvailable
}
