package d2common

import (
	"io"
)

// StreamReader allows you to read data from a byte array in various formats
type StreamReader struct {
	data     []byte
	position uint64
}

// CreateStreamReader creates an instance of the stream reader
func CreateStreamReader(source []byte) *StreamReader {
	result := &StreamReader{
		data:     source,
		position: 0,
	}

	return result
}

// GetPosition returns the current stream position
func (v *StreamReader) GetPosition() uint64 {
	return v.position
}

// GetSize returns the total size of the stream in bytes
func (v *StreamReader) GetSize() uint64 {
	return uint64(len(v.data))
}

// GetByte returns a byte from the stream
func (v *StreamReader) GetByte() byte {
	result := v.data[v.position]
	v.position++

	return result
}

// GetUInt16 returns a uint16 word from the stream
func (v *StreamReader) GetUInt16() uint16 {
	result := uint16(v.data[v.position])
	result += uint16(v.data[v.position+1]) << 8
	v.position += 2

	return result
}

// GetInt16 returns a int16 word from the stream
func (v *StreamReader) GetInt16() int16 {
	result := (int16(v.data[v.position+1]) << uint(8)) + int16(v.data[v.position])
	v.position += 2

	return result
}

// SetPosition sets the stream position with the given position
func (v *StreamReader) SetPosition(newPosition uint64) {
	v.position = newPosition
}

// GetUInt32 returns a uint32 dword from the stream
func (v *StreamReader) GetUInt32() uint32 {
	result := (uint32(v.data[v.position+3]) << uint(24)) +
		(uint32(v.data[v.position+2]) << uint(16)) +
		(uint32(v.data[v.position+1]) << uint(8)) +
		uint32(v.data[v.position])
	v.position += 4

	return result
}

// GetInt32 returns an int32 dword from the stream
func (v *StreamReader) GetInt32() int32 {
	result := (int32(v.data[v.position+3]) << uint(24)) +
		(int32(v.data[v.position+2]) << uint(16)) +
		(int32(v.data[v.position+1]) << uint(8)) +
		int32(v.data[v.position])
	v.position += 4

	return result
}

// GetUint64 returns a uint64 qword from the stream
func (v *StreamReader) GetUint64() uint64 {
	result := (uint64(v.data[v.position+7]) << uint(56)) +
		(uint64(v.data[v.position+6]) << uint(48)) +
		(uint64(v.data[v.position+5]) << uint(40)) +
		(uint64(v.data[v.position+4]) << uint(32)) +
		(uint64(v.data[v.position+3]) << uint(24)) +
		(uint64(v.data[v.position+2]) << uint(16)) +
		(uint64(v.data[v.position+1]) << uint(8)) +
		uint64(v.data[v.position])
	v.position += 8

	return result
}

// GetInt64 returns a uint64 qword from the stream
func (v *StreamReader) GetInt64() int64 {
	return int64(v.GetUint64())
}

// ReadByte implements io.ByteReader
func (v *StreamReader) ReadByte() (byte, error) {
	return v.GetByte(), nil
}

// ReadBytes reads multiple bytes
func (v *StreamReader) ReadBytes(count int) []byte {
	result := v.data[v.position : v.position+uint64(count)]
	v.position += uint64(count)

	return result
}

// SkipBytes moves the stream position forward by the given amount
func (v *StreamReader) SkipBytes(count int) {
	v.position += uint64(count)
}

// Read implements io.Reader
func (v *StreamReader) Read(p []byte) (n int, err error) {
	streamLength := v.GetSize()

	for i := 0; ; i++ {
		if v.GetPosition() >= streamLength {
			return i, io.EOF
		}

		if i >= len(p) {
			return i, nil
		}

		p[i] = v.GetByte()
	}
}

// EOF returns if the stream position is reached to the end of the data, or not
func (v *StreamReader) EOF() bool {
	return v.position >= uint64(len(v.data))
}
