// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package datachannel

import (
	"encoding/binary"
	"fmt"
)

/*
channelOpen represents a DATA_CHANNEL_OPEN Message

	0                   1                   2                   3
	0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Message Type |  Channel Type |            Priority           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Reliability Parameter                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Label Length          |       Protocol Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                             Label                             |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                            Protocol                           |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type channelOpen struct {
	ChannelType          ChannelType
	Priority             uint16
	ReliabilityParameter uint32

	Label    []byte
	Protocol []byte
}

const (
	channelOpenHeaderLength = 12
)

// ChannelType determines the reliability of the WebRTC DataChannel
type ChannelType byte

// ChannelType enums
const (
	// ChannelTypeReliable determines the Data Channel provides a
	// reliable in-order bi-directional communication.
	ChannelTypeReliable ChannelType = 0x00
	// ChannelTypeReliableUnordered determines the Data Channel
	// provides a reliable unordered bi-directional communication.
	ChannelTypeReliableUnordered ChannelType = 0x80
	// ChannelTypePartialReliableRexmit determines the Data Channel
	// provides a partially-reliable in-order bi-directional communication.
	// User messages will not be retransmitted more times than specified in the Reliability Parameter.
	ChannelTypePartialReliableRexmit ChannelType = 0x01
	// ChannelTypePartialReliableRexmitUnordered determines
	//  the Data Channel provides a partial reliable unordered bi-directional communication.
	// User messages will not be retransmitted more times than specified in the Reliability Parameter.
	ChannelTypePartialReliableRexmitUnordered ChannelType = 0x81
	// ChannelTypePartialReliableTimed determines the Data Channel
	// provides a partial reliable in-order bi-directional communication.
	// User messages might not be transmitted or retransmitted after
	// a specified life-time given in milli- seconds in the Reliability Parameter.
	// This life-time starts when providing the user message to the protocol stack.
	ChannelTypePartialReliableTimed ChannelType = 0x02
	// The Data Channel provides a partial reliable unordered bi-directional
	// communication.  User messages might not be transmitted or retransmitted
	// after a specified life-time given in milli- seconds in the Reliability Parameter.
	// This life-time starts when providing the user message to the protocol stack.
	ChannelTypePartialReliableTimedUnordered ChannelType = 0x82
)

func (c ChannelType) String() string {
	switch c {
	case ChannelTypeReliable:
	case ChannelTypeReliableUnordered:
		return "ReliableUnordered"
	case ChannelTypePartialReliableRexmit:
		return "PartialReliableRexmit"
	case ChannelTypePartialReliableRexmitUnordered:
		return "PartialReliableRexmitUnordered"
	case ChannelTypePartialReliableTimed:
		return "PartialReliableTimed"
	case ChannelTypePartialReliableTimedUnordered:
		return "PartialReliableTimedUnordered"
	}
	return "Unknown"
}

// ChannelPriority enums
const (
	ChannelPriorityBelowNormal uint16 = 128
	ChannelPriorityNormal      uint16 = 256
	ChannelPriorityHigh        uint16 = 512
	ChannelPriorityExtraHigh   uint16 = 1024
)

// Marshal returns raw bytes for the given message
func (c *channelOpen) Marshal() ([]byte, error) {
	labelLength := len(c.Label)
	protocolLength := len(c.Protocol)

	totalLen := channelOpenHeaderLength + labelLength + protocolLength
	raw := make([]byte, totalLen)

	raw[0] = uint8(dataChannelOpen)
	raw[1] = byte(c.ChannelType)
	binary.BigEndian.PutUint16(raw[2:], c.Priority)
	binary.BigEndian.PutUint32(raw[4:], c.ReliabilityParameter)
	binary.BigEndian.PutUint16(raw[8:], uint16(labelLength))
	binary.BigEndian.PutUint16(raw[10:], uint16(protocolLength))
	endLabel := channelOpenHeaderLength + labelLength
	copy(raw[channelOpenHeaderLength:endLabel], c.Label)
	copy(raw[endLabel:endLabel+protocolLength], c.Protocol)

	return raw, nil
}

// Unmarshal populates the struct with the given raw data
func (c *channelOpen) Unmarshal(raw []byte) error {
	if len(raw) < channelOpenHeaderLength {
		return fmt.Errorf("%w expected(%d) actual(%d)", ErrExpectedAndActualLengthMismatch, channelOpenHeaderLength, len(raw))
	}
	c.ChannelType = ChannelType(raw[1])
	c.Priority = binary.BigEndian.Uint16(raw[2:])
	c.ReliabilityParameter = binary.BigEndian.Uint32(raw[4:])

	labelLength := binary.BigEndian.Uint16(raw[8:])
	protocolLength := binary.BigEndian.Uint16(raw[10:])

	if expectedLen := int(channelOpenHeaderLength + labelLength + protocolLength); len(raw) != expectedLen {
		return fmt.Errorf("%w expected(%d) actual(%d)", ErrExpectedAndActualLengthMismatch, expectedLen, len(raw))
	}

	c.Label = raw[channelOpenHeaderLength : channelOpenHeaderLength+labelLength]
	c.Protocol = raw[channelOpenHeaderLength+labelLength : channelOpenHeaderLength+labelLength+protocolLength]
	return nil
}

func (c channelOpen) String() string {
	return fmt.Sprintf("Open ChannelType(%s) Priority(%v) ReliabilityParameter(%d) Label(%s) Protocol(%s)", c.ChannelType, c.Priority, c.ReliabilityParameter, string(c.Label), string(c.Protocol))
}