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

package eval

import (
	"go/token"
	"log"
)

/*
 * Blocks and scopes
 */

// A definition can be a *Variable, *Constant, or Type.
type Def interface {
	Pos() token.Position
}

type Variable struct {
	token.Position
	// Index of this variable in the Frame structure
	Index int
	// Static type of this variable
	Type Type
	// Value of this variable.  This is only used by Scope.NewFrame;
	// therefore, it is useful for global scopes but cannot be used
	// in function scopes.
	Init Value
}

type Constant struct {
	token.Position
	Type  Type
	Value Value
}

// A block represents a definition block in which a name may not be
// defined more than once.
type block struct {
	// The block enclosing this one, including blocks in other
	// scopes.
	outer *block
	// The nested block currently being compiled, or nil.
	inner *block
	// The Scope containing this block.
	scope *Scope
	// The Variables, Constants, and Types defined in this block.
	defs map[string]Def
	// The index of the first variable defined in this block.
	// This must be greater than the index of any variable defined
	// in any parent of this block within the same Scope at the
	// time this block is entered.
	offset int
	// The number of Variables defined in this block.
	numVars int
	// If global, do not allocate new vars and consts in
	// the frame; assume that the refs will be compiled in
	// using defs[name].Init.
	global bool
}

// A Scope is the compile-time analogue of a Frame, which captures
// some subtree of blocks.
type Scope struct {
	// The root block of this scope.
	*block
	// The maximum number of variables required at any point in
	// this Scope.  This determines the number of slots needed in
	// Frame's created from this Scope at run-time.
	maxVars int
}

func (b *block) enterChild() *block {
	if b.inner != nil && b.inner.scope == b.scope {
		log.Crash("Failed to exit child block before entering another child")
	}
	sub := &block{
		outer:  b,
		scope:  b.scope,
		defs:   make(map[string]Def),
		offset: b.offset + b.numVars,
	}
	b.inner = sub
	return sub
}

func (b *block) exit() {
	if b.outer == nil {
		log.Crash("Cannot exit top-level block")
	}
	if b.outer.scope == b.scope {
		if b.outer.inner != b {
			log.Crash("Already exited block")
		}
		if b.inner != nil && b.inner.scope == b.scope {
			log.Crash("Exit of parent block without exit of child block")
		}
	}
	b.outer.inner = nil
}

func (b *block) ChildScope() *Scope {
	if b.inner != nil && b.inner.scope == b.scope {
		log.Crash("Failed to exit child block before entering a child scope")
	}
	sub := b.enterChild()
	sub.offset = 0
	sub.scope = &Scope{sub, 0}
	return sub.scope
}

func (b *block) DefineVar(name string, pos token.Position, t Type) (*Variable, Def) {
	if prev, ok := b.defs[name]; ok {
		return nil, prev
	}
	v := b.defineSlot(t, false)
	v.Position = pos
	b.defs[name] = v
	return v, nil
}

func (b *block) DefineTemp(t Type) *Variable { return b.defineSlot(t, true) }

func (b *block) defineSlot(t Type, temp bool) *Variable {
	if b.inner != nil && b.inner.scope == b.scope {
		log.Crash("Failed to exit child block before defining variable")
	}
	index := -1
	if !b.global || temp {
		index = b.offset + b.numVars
		b.numVars++
		if index >= b.scope.maxVars {
			b.scope.maxVars = index + 1
		}
	}
	v := &Variable{token.Position{}, index, t, nil}
	return v
}

func (b *block) DefineConst(name string, pos token.Position, t Type, v Value) (*Constant, Def) {
	if prev, ok := b.defs[name]; ok {
		return nil, prev
	}
	c := &Constant{pos, t, v}
	b.defs[name] = c
	return c, nil
}

func (b *block) DefineType(name string, pos token.Position, t Type) Type {
	if _, ok := b.defs[name]; ok {
		return nil
	}
	nt := &NamedType{pos, name, nil, true, make(map[string]Method)}
	if t != nil {
		nt.Complete(t)
	}
	b.defs[name] = nt
	return nt
}

func (b *block) Lookup(name string) (bl *block, level int, def Def) {
	for b != nil {
		if d, ok := b.defs[name]; ok {
			return b, level, d
		}
		if b.outer != nil && b.scope != b.outer.scope {
			level++
		}
		b = b.outer
	}
	return nil, 0, nil
}

func (s *Scope) NewFrame(outer *Frame) *Frame { return outer.child(s.maxVars) }

/*
 * Frames
 */

type Frame struct {
	Outer *Frame
	Vars  []Value
}

func (f *Frame) Get(level int, index int) Value {
	for ; level > 0; level-- {
		f = f.Outer
	}
	return f.Vars[index]
}

func (f *Frame) child(numVars int) *Frame {
	// TODO(austin) This is probably rather expensive.  All values
	// require heap allocation and zeroing them when we execute a
	// definition typically requires some computation.
	return &Frame{f, make([]Value, numVars)}
}
