///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC                                          //
//                                                                           //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file                                                              //
///////////////////////////////////////////////////////////////////////////////

package groupChat

import (
	"encoding/binary"
	"fmt"
	"github.com/pkg/errors"
	"gitlab.com/xx_network/primitives/id"
	"strconv"
	"time"
)

// Sizes of marshaled data, in bytes.
const (
	timestampLen           = 8
	idLen                  = id.ArrIDLen
	internalPayloadSizeLen = 2
	internalMinLen         = timestampLen + idLen + internalPayloadSizeLen
)

// Error messages
const (
	newInternalSizeErr       = "max message size %d < %d minimum required"
	unmarshalInternalSizeErr = "size of data %d < %d minimum required"
)

// internalMsg is the internal, unencrypted data in a group message.
//
// +-------------------------------------------+
// |                    data                   |
// +-----------+----------+---------+----------+
// | timestamp | senderID |  size   | payload  |
// |  8 bytes  | 32 bytes | 2 bytes | variable |
// +-----------+----------+---------+----------+
type internalMsg struct {
	data      []byte // Serial of all the parts of the message
	timestamp []byte // 64-bit Unix time timestamp stored in nanoseconds
	senderID  []byte // 264-bit sender ID
	size      []byte // Size of the payload
	payload   []byte // Message contents
}

// newInternalMsg creates a new internalMsg of size maxDataSize. An error is
// returned if the maxDataSize is smaller than the minimum internalMsg size.
func newInternalMsg(maxDataSize int) (internalMsg, error) {
	if maxDataSize < internalMinLen {
		return internalMsg{},
			errors.Errorf(newInternalSizeErr, maxDataSize, internalMinLen)
	}

	return mapInternalMsg(make([]byte, maxDataSize)), nil
}

// mapInternalMsg maps all the parts of the internalMsg to the passed in data.
func mapInternalMsg(data []byte) internalMsg {
	return internalMsg{
		data:      data,
		timestamp: data[:timestampLen],
		senderID:  data[timestampLen : timestampLen+idLen],
		size:      data[timestampLen+idLen : timestampLen+idLen+internalPayloadSizeLen],
		payload:   data[timestampLen+idLen+internalPayloadSizeLen:],
	}
}

// unmarshalInternalMsg unmarshal the data into an internalMsg. An error is
// returned if the data length is smaller than the minimum allowed size.
func unmarshalInternalMsg(data []byte) (internalMsg, error) {
	if len(data) < internalMinLen {
		return internalMsg{},
			errors.Errorf(unmarshalInternalSizeErr, len(data), internalMinLen)
	}

	return mapInternalMsg(data), nil
}

// Marshal returns the serial of the internalMsg.
func (im internalMsg) Marshal() []byte {
	return im.data
}

// GetTimestamp returns the timestamp as a time.Time.
func (im internalMsg) GetTimestamp() time.Time {
	return time.Unix(0, int64(binary.LittleEndian.Uint64(im.timestamp)))
}

// SetTimestamp converts the time.Time to Unix nano and save as bytes.
func (im internalMsg) SetTimestamp(t time.Time) {
	binary.LittleEndian.PutUint64(im.timestamp, uint64(t.UnixNano()))
}

// GetSenderID returns the sender ID bytes as an id.ID.
func (im internalMsg) GetSenderID() (*id.ID, error) {
	return id.Unmarshal(im.senderID)
}

// SetSenderID sets the sender ID.
func (im internalMsg) SetSenderID(sid *id.ID) {
	copy(im.senderID, sid.Marshal())
}

// GetPayload returns the payload truncated to the correct size.
func (im internalMsg) GetPayload() []byte {
	return im.payload[:im.GetPayloadSize()]
}

// SetPayload sets the payload and saves it size.
func (im internalMsg) SetPayload(payload []byte) {
	// Save size of payload
	binary.LittleEndian.PutUint16(im.size, uint16(len(payload)))

	// Save payload
	copy(im.payload, payload)
}

// GetPayloadSize returns the length of the content in the payload.
func (im internalMsg) GetPayloadSize() int {
	return int(binary.LittleEndian.Uint16(im.size))
}

// GetPayloadMaxSize returns the maximum size of the payload.
func (im internalMsg) GetPayloadMaxSize() int {
	return len(im.payload)
}

// String prints a string representation of internalMsg. This functions
// satisfies the fmt.Stringer interface.
func (im internalMsg) String() string {
	timestamp := "<nil>"
	if len(im.timestamp) > 0 {
		timestamp = im.GetTimestamp().String()
	}

	senderID := "<nil>"
	if sid, _ := im.GetSenderID(); sid != nil {
		senderID = sid.String()
	}

	size := "<nil>"
	if len(im.size) > 0 {
		size = strconv.Itoa(im.GetPayloadSize())
	}

	payload := "<nil>"
	if len(im.size) > 0 {
		payload = fmt.Sprintf("%q", im.GetPayload())
	}

	return "{timestamp:" + timestamp + ", senderID:" + senderID +
		", size:" + size + ", payload:" + payload + "}"
}