// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package slicereader

import (
	"encoding/binary"
	"fmt"
	"io"
	"unsafe"
)

// This file contains the helper "SliceReader", a utility for
// reading values from a byte slice that may or may not be backed
// by a read-only mmap'd region.

type Reader struct {
	b        []byte
	readonly bool
	off      int64
}

func NewReader(b []byte, readonly bool) *Reader {
	r := Reader{
		b:        b,
		readonly: readonly,
	}
	return &r
}

func (r *Reader) Read(b []byte) (int, error) {
	amt := len(b)
	toread := r.b[r.off:]
	if len(toread) < amt {
		amt = len(toread)
	}
	copy(b, toread)
	r.off += int64(amt)
	return amt, nil
}

func (r *Reader) Seek(offset int64, whence int) (ret int64, err error) {
	switch whence {
	case io.SeekStart:
		if offset < 0 || offset > int64(len(r.b)) {
			return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", offset, len(r.b))
		}
		r.off = offset
		return offset, nil
	case io.SeekCurrent:
		newoff := r.off + offset
		if newoff < 0 || newoff > int64(len(r.b)) {
			return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(r.b))
		}
		r.off = newoff
		return r.off, nil
	case io.SeekEnd:
		newoff := int64(len(r.b)) + offset
		if newoff < 0 || newoff > int64(len(r.b)) {
			return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(r.b))
		}
		r.off = newoff
		return r.off, nil
	}
	// other modes are not supported
	return 0, fmt.Errorf("unsupported seek mode %d", whence)
}

func (r *Reader) Offset() int64 {
	return r.off
}

func (r *Reader) ReadUint8() uint8 {
	rv := uint8(r.b[int(r.off)])
	r.off += 1
	return rv
}

func (r *Reader) ReadUint32() uint32 {
	end := int(r.off) + 4
	rv := binary.LittleEndian.Uint32(r.b[int(r.off):end:end])
	r.off += 4
	return rv
}

func (r *Reader) ReadUint64() uint64 {
	end := int(r.off) + 8
	rv := binary.LittleEndian.Uint64(r.b[int(r.off):end:end])
	r.off += 8
	return rv
}

func (r *Reader) ReadULEB128() (value uint64) {
	var shift uint

	for {
		b := r.b[r.off]
		r.off++
		value |= (uint64(b&0x7F) << shift)
		if b&0x80 == 0 {
			break
		}
		shift += 7
	}
	return
}

func (r *Reader) ReadString(len int64) string {
	b := r.b[r.off : r.off+len]
	r.off += len
	if r.readonly {
		return toString(b) // backed by RO memory, ok to make unsafe string
	}
	return string(b)
}

func toString(b []byte) string {
	if len(b) == 0 {
		return ""
	}
	return unsafe.String(&b[0], len(b))
}
