//  Copyright (c) 2020 Cisco and/or its affiliates.
//
//  Licensed 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 codec

import (
	"bytes"
	"encoding/binary"
	"math"
)

// Buffer provides buffer for encoding and decoding data on wire.
type Buffer struct {
	buf []byte
	pos int
}

// NewBuffer creates new buffer using b as data.
func NewBuffer(b []byte) *Buffer {
	return &Buffer{
		buf: b,
	}
}

// Bytes returns buffer data up to current position.
func (b *Buffer) Bytes() []byte {
	return b.buf[:b.pos]
}

func (b *Buffer) EncodeBytes(v []byte, length int) {
	if length == 0 {
		length = len(v)
	}
	copy(b.buf[b.pos:b.pos+length], v)
	b.pos += length
}

func (b *Buffer) DecodeBytes(length int) []byte {
	v := b.buf[b.pos : b.pos+length]
	b.pos += length
	return v
}

func (b *Buffer) EncodeBool(v bool) {
	if v {
		b.buf[b.pos] = 1
	} else {
		b.buf[b.pos] = 0
	}
	b.pos += 1
}

func (b *Buffer) DecodeBool() bool {
	v := b.buf[b.pos] != 0
	b.pos += 1
	return v
}

func (b *Buffer) EncodeUint8(v uint8) {
	b.buf[b.pos] = byte(v)
	b.pos += 1
}

func (b *Buffer) DecodeUint8() uint8 {
	v := uint8(b.buf[b.pos])
	b.pos += 1
	return v
}

func (b *Buffer) EncodeUint16(v uint16) {
	binary.BigEndian.PutUint16(b.buf[b.pos:b.pos+2], v)
	b.pos += 2
}

func (b *Buffer) DecodeUint16() uint16 {
	v := binary.BigEndian.Uint16(b.buf[b.pos : b.pos+2])
	b.pos += 2
	return v
}

func (b *Buffer) EncodeUint32(v uint32) {
	binary.BigEndian.PutUint32(b.buf[b.pos:b.pos+4], v)
	b.pos += 4
}

func (b *Buffer) DecodeUint32() uint32 {
	v := binary.BigEndian.Uint32(b.buf[b.pos : b.pos+4])
	b.pos += 4
	return v
}

func (b *Buffer) EncodeUint64(v uint64) {
	binary.BigEndian.PutUint64(b.buf[b.pos:b.pos+8], v)
	b.pos += 8
}

func (b *Buffer) DecodeUint64() uint64 {
	v := binary.BigEndian.Uint64(b.buf[b.pos : b.pos+8])
	b.pos += 8
	return v
}

func (b *Buffer) EncodeInt8(v int8) {
	b.buf[b.pos] = byte(v)
	b.pos += 1
}

func (b *Buffer) DecodeInt8() int8 {
	v := int8(b.buf[b.pos])
	b.pos += 1
	return v
}

func (b *Buffer) EncodeInt16(v int16) {
	binary.BigEndian.PutUint16(b.buf[b.pos:b.pos+2], uint16(v))
	b.pos += 2
}

func (b *Buffer) DecodeInt16() int16 {
	v := int16(binary.BigEndian.Uint16(b.buf[b.pos : b.pos+2]))
	b.pos += 2
	return v
}

func (b *Buffer) EncodeInt32(v int32) {
	binary.BigEndian.PutUint32(b.buf[b.pos:b.pos+4], uint32(v))
	b.pos += 4
}

func (b *Buffer) DecodeInt32() int32 {
	v := int32(binary.BigEndian.Uint32(b.buf[b.pos : b.pos+4]))
	b.pos += 4
	return v
}

func (b *Buffer) EncodeInt64(v int64) {
	binary.BigEndian.PutUint64(b.buf[b.pos:b.pos+8], uint64(v))
	b.pos += 8
}

func (b *Buffer) DecodeInt64() int64 {
	v := int64(binary.BigEndian.Uint64(b.buf[b.pos : b.pos+8]))
	b.pos += 8
	return v
}

func (b *Buffer) EncodeFloat64(v float64) {
	binary.LittleEndian.PutUint64(b.buf[b.pos:b.pos+8], math.Float64bits(v))
	b.pos += 8
}

func (b *Buffer) DecodeFloat64() float64 {
	v := math.Float64frombits(binary.LittleEndian.Uint64(b.buf[b.pos : b.pos+8]))
	b.pos += 8
	return v
}

func (b *Buffer) EncodeString(v string, length int) {
	if length == 0 {
		b.EncodeUint32(uint32(len(v)))
		length = len(v)
	}
	copy(b.buf[b.pos:b.pos+length], v)
	b.pos += length
}

func (b *Buffer) DecodeString(length int) string {
	var v []byte
	if length == 0 {
		length = int(b.DecodeUint32())
		v = b.buf[b.pos : b.pos+length]
	} else {
		v = b.buf[b.pos : b.pos+length]
		if nul := bytes.Index(v, []byte{0x00}); nul >= 0 {
			v = v[:nul]
		}
	}
	b.pos += length
	return string(v)
}