// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package binaryserialization

import (
	"encoding/binary"

	iggcon "github.com/apache/iggy/foreign/go/contracts"
)

func CreateGroup(request iggcon.CreateConsumerGroupRequest) []byte {
	if request.ConsumerGroupId == nil {
		request.ConsumerGroupId = new(uint32)
	}
	customIdOffset := 4 + request.StreamId.Length + request.TopicId.Length
	bytes := make([]byte, 4+request.StreamId.Length+request.TopicId.Length+1+4+len(request.Name))
	copy(bytes[0:customIdOffset], SerializeIdentifiers(request.StreamId, request.TopicId))
	binary.LittleEndian.PutUint32(bytes[customIdOffset:customIdOffset+4], *request.ConsumerGroupId)
	bytes[customIdOffset+4] = byte(len(request.Name))
	copy(bytes[customIdOffset+5:], request.Name)
	return bytes
}

func UpdateOffset(request iggcon.StoreConsumerOffsetRequest) []byte {
	if request.PartitionId == nil {
		request.PartitionId = new(uint32)
	}
	bytes := make([]byte, 6+request.StreamId.Length+request.TopicId.Length+request.Consumer.Id.Length+13)
	bytes[0] = byte(request.Consumer.Kind)
	position := 7 + request.StreamId.Length + request.TopicId.Length + request.Consumer.Id.Length
	copy(bytes[1:position], SerializeIdentifiers(request.Consumer.Id, request.StreamId, request.TopicId))

	binary.LittleEndian.PutUint32(bytes[position:position+4], *request.PartitionId)
	binary.LittleEndian.PutUint64(bytes[position+4:position+12], uint64(request.Offset))
	return bytes
}

func GetOffset(request iggcon.GetConsumerOffsetRequest) []byte {
	if request.PartitionId == nil {
		request.PartitionId = new(uint32)
	}
	bytes := make([]byte, 6+request.StreamId.Length+request.TopicId.Length+request.Consumer.Id.Length+5)
	bytes[0] = byte(request.Consumer.Kind)
	position := 7 + request.StreamId.Length + request.TopicId.Length + request.Consumer.Id.Length
	copy(bytes[1:position], SerializeIdentifiers(request.Consumer.Id, request.StreamId, request.TopicId))
	binary.LittleEndian.PutUint32(bytes[position:position+4], *request.PartitionId)
	return bytes
}

func DeleteOffset(request iggcon.DeleteConsumerOffsetRequest) []byte {
	consumerBytes := SerializeConsumer(request.Consumer)
	streamIdBytes := SerializeIdentifier(request.StreamId)
	topicIdBytes := SerializeIdentifier(request.TopicId)
	bytes := make([]byte, 0, 12+len(consumerBytes)+len(streamIdBytes)+len(topicIdBytes))
	bytes = append(bytes, consumerBytes...)
	bytes = append(bytes, streamIdBytes...)
	bytes = append(bytes, topicIdBytes...)
	if request.PartitionId != nil {
		bytes = binary.LittleEndian.AppendUint32(bytes, *request.PartitionId)
	} else {
		bytes = binary.LittleEndian.AppendUint32(bytes, 0)
	}

	return bytes
}

func CreatePartitions(request iggcon.CreatePartitionsRequest) []byte {
	bytes := make([]byte, 8+request.StreamId.Length+request.TopicId.Length)
	position := 4 + request.StreamId.Length + request.TopicId.Length
	copy(bytes[0:position], SerializeIdentifiers(request.StreamId, request.TopicId))
	binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(request.PartitionsCount))

	return bytes
}

func DeletePartitions(request iggcon.DeletePartitionsRequest) []byte {
	bytes := make([]byte, 8+request.StreamId.Length+request.TopicId.Length)
	position := 4 + request.StreamId.Length + request.TopicId.Length
	copy(bytes[0:position], SerializeIdentifiers(request.StreamId, request.TopicId))
	binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(request.PartitionsCount))

	return bytes
}

//USERS

func SerializeCreateUserRequest(request iggcon.CreateUserRequest) []byte {
	capacity := 4 + len(request.Username) + len(request.Password)
	if request.Permissions != nil {
		capacity += 1 + 4 + CalculatePermissionsSize(request.Permissions)
	}

	bytes := make([]byte, capacity)
	position := 0

	bytes[position] = byte(len(request.Username))
	position += 1
	copy(bytes[position:position+len(request.Username)], []byte(request.Username))
	position += len(request.Username)

	bytes[position] = byte(len(request.Password))
	position += 1
	copy(bytes[position:position+len(request.Password)], []byte(request.Password))
	position += len(request.Password)

	statusByte := byte(0)
	switch request.Status {
	case iggcon.Active:
		statusByte = byte(1)
	case iggcon.Inactive:
		statusByte = byte(2)
	}
	bytes[position] = statusByte
	position += 1

	if request.Permissions != nil {
		bytes[position] = byte(1)
		position += 1
		permissionsBytes := GetBytesFromPermissions(request.Permissions)
		binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(len(permissionsBytes)))
		position += 4
		copy(bytes[position:position+len(permissionsBytes)], permissionsBytes)
	} else {
		bytes[position] = byte(0)
	}

	return bytes
}

func GetBytesFromPermissions(data *iggcon.Permissions) []byte {
	size := CalculatePermissionsSize(data)
	bytes := make([]byte, size)

	bytes[0] = boolToByte(data.Global.ManageServers)
	bytes[1] = boolToByte(data.Global.ReadServers)
	bytes[2] = boolToByte(data.Global.ManageUsers)
	bytes[3] = boolToByte(data.Global.ReadUsers)
	bytes[4] = boolToByte(data.Global.ManageStreams)
	bytes[5] = boolToByte(data.Global.ReadStreams)
	bytes[6] = boolToByte(data.Global.ManageTopics)
	bytes[7] = boolToByte(data.Global.ReadTopics)
	bytes[8] = boolToByte(data.Global.PollMessages)
	bytes[9] = boolToByte(data.Global.SendMessages)

	position := 10

	if data.Streams != nil {
		bytes[position] = byte(1)
		position += 1

		for streamID, stream := range data.Streams {
			binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(streamID))
			position += 4

			bytes[position] = boolToByte(stream.ManageStream)
			bytes[position+1] = boolToByte(stream.ReadStream)
			bytes[position+2] = boolToByte(stream.ManageTopics)
			bytes[position+3] = boolToByte(stream.ReadTopics)
			bytes[position+4] = boolToByte(stream.PollMessages)
			bytes[position+5] = boolToByte(stream.SendMessages)
			position += 6

			if stream.Topics != nil {
				bytes[position] = byte(1)
				position += 1

				for topicID, topic := range stream.Topics {
					binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(topicID))
					position += 4

					bytes[position] = boolToByte(topic.ManageTopic)
					bytes[position+1] = boolToByte(topic.ReadTopic)
					bytes[position+2] = boolToByte(topic.PollMessages)
					bytes[position+3] = boolToByte(topic.SendMessages)
					position += 4

					bytes[position] = byte(0)
					position += 1
				}
			} else {
				bytes[position] = byte(0)
				position += 1
			}
		}
	} else {
		bytes[0] = byte(0)
	}

	return bytes
}

func CalculatePermissionsSize(data *iggcon.Permissions) int {
	size := 10

	if data.Streams != nil {
		size += 1

		for _, stream := range data.Streams {
			size += 4
			size += 6
			size += 1

			if stream.Topics != nil {
				size += 1
				size += len(stream.Topics) * 9
			} else {
				size += 1
			}
		}
	} else {
		size += 1
	}

	return size
}

func boolToByte(b bool) byte {
	if b {
		return 1
	}
	return 0
}

func SerializeUpdateUser(request iggcon.UpdateUserRequest) []byte {
	length := request.UserID.Length + 2

	if request.Username == nil {
		request.Username = new(string)
	}

	username := *request.Username

	if len(username) != 0 {
		length += 2 + len(username)
	}

	if request.Status != nil {
		length += 2
	}

	bytes := make([]byte, length+1)
	position := 0

	copy(bytes[position:position+request.UserID.Length+2], SerializeIdentifier(request.UserID))
	position += position + request.UserID.Length + 2

	if len(username) != 0 {
		bytes[position] = 1
		position++
		bytes[position] = byte(len(username))
		position++
		copy(bytes[position:position+len(username)], username)
		position += len(username)
	} else {
		bytes[position] = 0
		position++
	}

	if request.Status != nil {
		bytes[position] = 1
		position++
		statusByte := byte(0)
		switch *request.Status {
		case iggcon.Active:
			statusByte = 1
		case iggcon.Inactive:
			statusByte = 2
		}
		bytes[position] = statusByte
	} else {
		bytes[position] = 0
	}

	return bytes
}

func SerializeChangePasswordRequest(request iggcon.ChangePasswordRequest) []byte {
	length := request.UserID.Length + 2 + len(request.CurrentPassword) + len(request.NewPassword) + 2
	bytes := make([]byte, length)
	position := 0

	copy(bytes[position:position+request.UserID.Length+2], SerializeIdentifier(request.UserID))
	position += request.UserID.Length + 2

	bytes[position] = byte(len(request.CurrentPassword))
	position++
	copy(bytes[position:position+len(request.CurrentPassword)], []byte(request.CurrentPassword))
	position += len(request.CurrentPassword)

	bytes[position] = byte(len(request.NewPassword))
	position++
	copy(bytes[position:position+len(request.NewPassword)], []byte(request.NewPassword))

	return bytes
}

func SerializeUpdateUserPermissionsRequest(request iggcon.UpdatePermissionsRequest) []byte {
	length := request.UserID.Length + 2

	if request.Permissions != nil {
		length += 1 + 4 + CalculatePermissionsSize(request.Permissions)
	}

	bytes := make([]byte, length)
	position := 0

	copy(bytes[position:position+request.UserID.Length+2], SerializeIdentifier(request.UserID))
	position += request.UserID.Length + 2

	if request.Permissions != nil {
		bytes[position] = 1
		position++
		permissionsBytes := GetBytesFromPermissions(request.Permissions)
		binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(len(permissionsBytes)))
		position += 4
		copy(bytes[position:position+len(permissionsBytes)], permissionsBytes)
	} else {
		bytes[position] = 0
	}

	return bytes
}

func SerializeUint32(value uint32) []byte {
	bytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(bytes, value)
	return bytes
}

func SerializeLoginWithPersonalAccessToken(request iggcon.LoginWithPersonalAccessTokenRequest) []byte {
	length := 1 + len(request.Token)
	bytes := make([]byte, length)
	bytes[0] = byte(len(request.Token))
	copy(bytes[1:], []byte(request.Token))
	return bytes
}

func SerializeDeletePersonalAccessToken(request iggcon.DeletePersonalAccessTokenRequest) []byte {
	length := 1 + len(request.Name)
	bytes := make([]byte, length)
	bytes[0] = byte(len(request.Name))
	copy(bytes[1:], []byte(request.Name))
	return bytes
}

func SerializeCreatePersonalAccessToken(request iggcon.CreatePersonalAccessTokenRequest) []byte {
	length := 1 + len(request.Name) + 8
	bytes := make([]byte, length)
	bytes[0] = byte(len(request.Name))
	copy(bytes[1:], []byte(request.Name))
	binary.LittleEndian.PutUint32(bytes[len(bytes)-4:], request.Expiry)
	return bytes
}
