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

// Copyright 2009 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.

//go:build !safe
// +build !safe

package iterator

import (
	"unsafe"

	"gonum.org/v1/gonum/graph"
)

// A mapIter is an iterator for ranging over a map.
type mapIter struct {
	m     *emptyInterface
	hiter hiter
}

type emptyInterface struct {
	typ, word unsafe.Pointer
}

// hiter's structure matches runtime.hiter's structure.
// Having a clone here allows us to embed a map iterator
// inside type mapIter so that mapIters can be re-used
// without doing any allocations.
//
//lint:ignore U1000 This is a verbatim copy of the runtime type.
type hiter struct {
	key         unsafe.Pointer
	elem        unsafe.Pointer
	t           unsafe.Pointer
	h           unsafe.Pointer
	buckets     unsafe.Pointer
	bptr        unsafe.Pointer
	overflow    *[]unsafe.Pointer
	oldoverflow *[]unsafe.Pointer
	startBucket uintptr
	offset      uint8
	wrapped     bool
	B           uint8
	i           uint8
	bucket      uintptr
	checkBucket uintptr
}

func (h *hiter) initialized() bool {
	return h.t != nil
}

// newMapIterNodes returns a range iterator for a map of nodes.
// The returned mapIter must not have its line or weightedLine methods called.
func newMapIterNodes(m map[int64]graph.Node) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterEdges returns a range iterator for a map of edges.
// The returned mapIter must not have its node, line or weightedLine methods called.
func newMapIterEdges(m map[int64]graph.Edge) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterLines returns a range iterator for a map of line.
// The returned mapIter must not have its node or weightedLine method called.
func newMapIterLines(m map[int64]graph.Line) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterWeightedLines returns a range iterator for a map of line.
// The returned mapIter must not have its node, line or weightedLine methods called.
func newMapIterWeightedLines(m map[int64]graph.WeightedLine) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterByWeightedEdges returns a range iterator for a map of edges.
// The returned mapIter must not have its node, line or weightedLine methods called.
func newMapIterByWeightedEdges(m map[int64]graph.WeightedEdge) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterByLines returns a range iterator for a map of edges.
// The returned mapIter must not have its node, line or weightedLine methods called.
func newMapIterByLines(m map[int64]map[int64]graph.Line) *mapIter {
	return &mapIter{m: eface(m)}
}

// newMapIterByWeightedLines returns a range iterator for a map of edges.
// The returned mapIter must not have its node, line or weightedLine methods called.
func newMapIterByWeightedLines(m map[int64]map[int64]graph.WeightedLine) *mapIter {
	return &mapIter{m: eface(m)}
}

func eface(i interface{}) *emptyInterface {
	return (*emptyInterface)(unsafe.Pointer(&i))
}

// id returns the key of the iterator's current map entry.
func (it *mapIter) id() int64 {
	if !it.hiter.initialized() {
		panic("mapIter.id called before Next")
	}
	if mapiterkey(&it.hiter) == nil {
		panic("mapIter.id called on exhausted iterator")
	}
	return *(*int64)(mapiterkey(&it.hiter))
}

// node returns the value of the iterator's current map entry.
func (it *mapIter) node() graph.Node {
	if !it.hiter.initialized() {
		panic("mapIter.node called before next")
	}
	if mapiterkey(&it.hiter) == nil {
		panic("mapIter.node called on exhausted iterator")
	}
	return *(*graph.Node)(mapiterelem(&it.hiter))
}

// line returns the value of the iterator's current map entry.
func (it *mapIter) line() graph.Line {
	if !it.hiter.initialized() {
		panic("mapIter.line called before next")
	}
	if mapiterkey(&it.hiter) == nil {
		panic("mapIter.line called on exhausted iterator")
	}
	return *(*graph.Line)(mapiterelem(&it.hiter))
}

// weightedLine returns the value of the iterator's current map entry.
func (it *mapIter) weightedLine() graph.WeightedLine {
	if !it.hiter.initialized() {
		panic("mapIter.weightedLine called before next")
	}
	if mapiterkey(&it.hiter) == nil {
		panic("mapIter.weightedLine called on exhausted iterator")
	}
	return *(*graph.WeightedLine)(mapiterelem(&it.hiter))
}

// next advances the map iterator and reports whether there is another
// entry. It returns false when the iterator is exhausted; subsequent
// calls to Key, Value, or next will panic.
func (it *mapIter) next() bool {
	if !it.hiter.initialized() {
		mapiterinit(it.m.typ, it.m.word, &it.hiter)
	} else {
		if mapiterkey(&it.hiter) == nil {
			panic("mapIter.next called on exhausted iterator")
		}
		mapiternext(&it.hiter)
	}
	return mapiterkey(&it.hiter) != nil
}

//go:linkname mapiterinit runtime.mapiterinit
//go:noescape
func mapiterinit(t, m unsafe.Pointer, it *hiter)

//go:linkname mapiterkey reflect.mapiterkey
//go:noescape
func mapiterkey(it *hiter) (key unsafe.Pointer)

//go:linkname mapiterelem reflect.mapiterelem
//go:noescape
func mapiterelem(it *hiter) (elem unsafe.Pointer)

//go:linkname mapiternext reflect.mapiternext
//go:noescape
func mapiternext(it *hiter)
