package capnp

// A Ptr is a reference to a Cap'n Proto struct, list, or interface.
// The zero value is a null pointer.
type Ptr struct {
	seg        *Segment
	off        Address
	lenOrCap   uint32
	size       ObjectSize
	depthLimit uint
	flags      ptrFlags
}

func toPtr(p Pointer) Ptr {
	if p == nil {
		return Ptr{}
	}
	switch p := p.underlying().(type) {
	case Struct:
		return p.ToPtr()
	case List:
		return p.ToPtr()
	case Interface:
		return p.ToPtr()
	}
	return Ptr{}
}

// Struct converts p to a Struct. If p does not hold a Struct pointer,
// the zero value is returned.
func (p Ptr) Struct() Struct {
	if p.flags.ptrType() != structPtrType {
		return Struct{}
	}
	return Struct{
		seg:        p.seg,
		off:        p.off,
		size:       p.size,
		flags:      p.flags.structFlags(),
		depthLimit: p.depthLimit,
	}
}

// StructDefault attempts to convert p into a struct, reading the
// default value from def if p is not a struct.
func (p Ptr) StructDefault(def []byte) (Struct, error) {
	s := p.Struct()
	if s.seg == nil {
		if def == nil {
			return Struct{}, nil
		}
		defp, err := unmarshalDefault(def)
		if err != nil {
			return Struct{}, err
		}
		return defp.Struct(), nil
	}
	return s, nil
}

// List converts p to a List. If p does not hold a List pointer,
// the zero value is returned.
func (p Ptr) List() List {
	if p.flags.ptrType() != listPtrType {
		return List{}
	}
	return List{
		seg:        p.seg,
		off:        p.off,
		length:     int32(p.lenOrCap),
		size:       p.size,
		flags:      p.flags.listFlags(),
		depthLimit: p.depthLimit,
	}
}

// ListDefault attempts to convert p into a list, reading the default
// value from def if p is not a list.
func (p Ptr) ListDefault(def []byte) (List, error) {
	l := p.List()
	if l.seg == nil {
		if def == nil {
			return List{}, nil
		}
		defp, err := unmarshalDefault(def)
		if err != nil {
			return List{}, err
		}
		return defp.List(), nil
	}
	return l, nil
}

// Interface converts p to an Interface. If p does not hold a List
// pointer, the zero value is returned.
func (p Ptr) Interface() Interface {
	if p.flags.ptrType() != interfacePtrType {
		return Interface{}
	}
	return Interface{
		seg: p.seg,
		cap: CapabilityID(p.lenOrCap),
	}
}

// Text attempts to convert p into Text, returning an empty string if
// p is not a valid 1-byte list pointer.
func (p Ptr) Text() string {
	b, ok := p.text()
	if !ok {
		return ""
	}
	return string(b)
}

// TextDefault attempts to convert p into Text, returning def if p is
// not a valid 1-byte list pointer.
func (p Ptr) TextDefault(def string) string {
	b, ok := p.text()
	if !ok {
		return def
	}
	return string(b)
}

// TextBytes attempts to convert p into Text, returning nil if p is not
// a valid 1-byte list pointer.  It returns a slice directly into the
// segment.
func (p Ptr) TextBytes() []byte {
	b, ok := p.text()
	if !ok {
		return nil
	}
	return b
}

// TextBytesDefault attempts to convert p into Text, returning def if p
// is not a valid 1-byte list pointer.  It returns a slice directly into
// the segment.
func (p Ptr) TextBytesDefault(def string) []byte {
	b, ok := p.text()
	if !ok {
		return []byte(def)
	}
	return b
}

func (p Ptr) text() (b []byte, ok bool) {
	if !isOneByteList(p) {
		return nil, false
	}
	l := p.List()
	b = l.seg.slice(l.off, Size(l.length))
	if len(b) == 0 || b[len(b)-1] != 0 {
		// Text must be null-terminated.
		return nil, false
	}
	return b[: len(b)-1 : len(b)], true
}

// Data attempts to convert p into Data, returning nil if p is not a
// valid 1-byte list pointer.
func (p Ptr) Data() []byte {
	return p.DataDefault(nil)
}

// DataDefault attempts to convert p into Data, returning def if p is
// not a valid 1-byte list pointer.
func (p Ptr) DataDefault(def []byte) []byte {
	if !isOneByteList(p) {
		return def
	}
	l := p.List()
	b := l.seg.slice(l.off, Size(l.length))
	if b == nil {
		return def
	}
	return b
}

func (p Ptr) toPointer() Pointer {
	if p.seg == nil {
		return nil
	}
	switch p.flags.ptrType() {
	case structPtrType:
		return p.Struct()
	case listPtrType:
		return p.List()
	case interfacePtrType:
		return p.Interface()
	}
	return nil
}

// IsValid reports whether p is valid.
func (p Ptr) IsValid() bool {
	return p.seg != nil
}

// Segment returns the segment this pointer points into.
// If nil, then this is an invalid pointer.
func (p Ptr) Segment() *Segment {
	return p.seg
}

// Default returns p if it is valid, otherwise it unmarshals def.
func (p Ptr) Default(def []byte) (Ptr, error) {
	if !p.IsValid() {
		return unmarshalDefault(def)
	}
	return p, nil
}

// SamePtr reports whether p and q refer to the same object.
func SamePtr(p, q Ptr) bool {
	return p.seg == q.seg && p.off == q.off
}

// A value that implements Pointer is a reference to a Cap'n Proto object.
//
// Deprecated: Using this type introduces an unnecessary allocation.
// Use Ptr instead.
type Pointer interface {
	// Segment returns the segment this pointer points into.
	// If nil, then this is an invalid pointer.
	Segment() *Segment

	// HasData reports whether the object referenced by the pointer has
	// non-zero size.
	HasData() bool

	// underlying returns a Pointer that is one of a Struct, a List, or an
	// Interface.
	underlying() Pointer
}

// IsValid reports whether p is valid.
//
// Deprecated: Use Ptr.IsValid instead.
func IsValid(p Pointer) bool {
	return p != nil && p.Segment() != nil
}

// HasData reports whether p has non-zero size.
//
// Deprecated: There are usually better ways to determine this
// information: length of a list, checking fields, or using HasFoo
// accessors.
func HasData(p Pointer) bool {
	return IsValid(p) && p.HasData()
}

// PointerDefault returns p if it is valid, otherwise it unmarshals def.
//
// Deprecated: Use Ptr.Default.
func PointerDefault(p Pointer, def []byte) (Pointer, error) {
	pp, err := toPtr(p).Default(def)
	return pp.toPointer(), err
}

func unmarshalDefault(def []byte) (Ptr, error) {
	msg, err := Unmarshal(def)
	if err != nil {
		return Ptr{}, err
	}
	p, err := msg.RootPtr()
	if err != nil {
		return Ptr{}, err
	}
	return p, nil
}

type ptrFlags uint8

const interfacePtrFlag ptrFlags = interfacePtrType << 6

func structPtrFlag(f structFlags) ptrFlags {
	return structPtrType<<6 | ptrFlags(f)&ptrLowerMask
}

func listPtrFlag(f listFlags) ptrFlags {
	return listPtrType<<6 | ptrFlags(f)&ptrLowerMask
}

const (
	structPtrType = iota
	listPtrType
	interfacePtrType
)

func (f ptrFlags) ptrType() int {
	return int(f >> 6)
}

const ptrLowerMask ptrFlags = 0x3f

func (f ptrFlags) listFlags() listFlags {
	return listFlags(f & ptrLowerMask)
}

func (f ptrFlags) structFlags() structFlags {
	return structFlags(f & ptrLowerMask)
}
