// Copyright 2015 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 ssa

import (
	"cmd/internal/obj"
	"crypto/sha1"
	"fmt"
	"os"
	"strings"
)

type Config struct {
	arch         string                     // "amd64", etc.
	IntSize      int64                      // 4 or 8
	PtrSize      int64                      // 4 or 8
	lowerBlock   func(*Block) bool          // lowering function
	lowerValue   func(*Value, *Config) bool // lowering function
	fe           Frontend                   // callbacks into compiler frontend
	HTML         *HTMLWriter                // html writer, for debugging
	ctxt         *obj.Link                  // Generic arch information
	optimize     bool                       // Do optimization
	noDuffDevice bool                       // Don't use Duff's device
	curFunc      *Func

	// TODO: more stuff. Compiler flags of interest, ...

	// Given an environment variable used for debug hash match,
	// what file (if any) receives the yes/no logging?
	logfiles map[string]*os.File

	// Storage for low-numbered values and blocks.
	values [2000]Value
	blocks [200]Block

	domblockstore []ID         // scratch space for computing dominators
	scrSparse     []*sparseSet // scratch sparse sets to be re-used.
}

type TypeSource interface {
	TypeBool() Type
	TypeInt8() Type
	TypeInt16() Type
	TypeInt32() Type
	TypeInt64() Type
	TypeUInt8() Type
	TypeUInt16() Type
	TypeUInt32() Type
	TypeUInt64() Type
	TypeInt() Type
	TypeFloat32() Type
	TypeFloat64() Type
	TypeUintptr() Type
	TypeString() Type
	TypeBytePtr() Type // TODO: use unsafe.Pointer instead?

	CanSSA(t Type) bool
}

type Logger interface {
	// Logf logs a message from the compiler.
	Logf(string, ...interface{})

	// Log returns true if logging is not a no-op
	// some logging calls account for more than a few heap allocations.
	Log() bool

	// Fatal reports a compiler error and exits.
	Fatalf(line int32, msg string, args ...interface{})

	// Unimplemented reports that the function cannot be compiled.
	// It will be removed once SSA work is complete.
	Unimplementedf(line int32, msg string, args ...interface{})

	// Warnl writes compiler messages in the form expected by "errorcheck" tests
	Warnl(line int32, fmt_ string, args ...interface{})

	// Fowards the Debug_checknil flag from gc
	Debug_checknil() bool
}

type Frontend interface {
	TypeSource
	Logger

	// StringData returns a symbol pointing to the given string's contents.
	StringData(string) interface{} // returns *gc.Sym

	// Auto returns a Node for an auto variable of the given type.
	// The SSA compiler uses this function to allocate space for spills.
	Auto(Type) GCNode

	// Line returns a string describing the given line number.
	Line(int32) string
}

// interface used to hold *gc.Node. We'd use *gc.Node directly but
// that would lead to an import cycle.
type GCNode interface {
	Typ() Type
	String() string
}

// NewConfig returns a new configuration object for the given architecture.
func NewConfig(arch string, fe Frontend, ctxt *obj.Link, optimize bool) *Config {
	c := &Config{arch: arch, fe: fe}
	switch arch {
	case "amd64":
		c.IntSize = 8
		c.PtrSize = 8
		c.lowerBlock = rewriteBlockAMD64
		c.lowerValue = rewriteValueAMD64
	case "386":
		c.IntSize = 4
		c.PtrSize = 4
		c.lowerBlock = rewriteBlockAMD64
		c.lowerValue = rewriteValueAMD64 // TODO(khr): full 32-bit support
	default:
		fe.Unimplementedf(0, "arch %s not implemented", arch)
	}
	c.ctxt = ctxt
	c.optimize = optimize

	// Don't use Duff's device on Plan 9, because floating
	// point operations are not allowed in note handler.
	if obj.Getgoos() == "plan9" {
		c.noDuffDevice = true
	}

	// Assign IDs to preallocated values/blocks.
	for i := range c.values {
		c.values[i].ID = ID(i)
	}
	for i := range c.blocks {
		c.blocks[i].ID = ID(i)
	}

	c.logfiles = make(map[string]*os.File)

	return c
}

func (c *Config) Frontend() Frontend { return c.fe }

// NewFunc returns a new, empty function object.
// Caller must call f.Free() before calling NewFunc again.
func (c *Config) NewFunc() *Func {
	// TODO(khr): should this function take name, type, etc. as arguments?
	if c.curFunc != nil {
		c.Fatalf(0, "NewFunc called without previous Free")
	}
	f := &Func{Config: c, NamedValues: map[LocalSlot][]*Value{}}
	c.curFunc = f
	return f
}

func (c *Config) Logf(msg string, args ...interface{})               { c.fe.Logf(msg, args...) }
func (c *Config) Log() bool                                          { return c.fe.Log() }
func (c *Config) Fatalf(line int32, msg string, args ...interface{}) { c.fe.Fatalf(line, msg, args...) }
func (c *Config) Unimplementedf(line int32, msg string, args ...interface{}) {
	c.fe.Unimplementedf(line, msg, args...)
}
func (c *Config) Warnl(line int32, msg string, args ...interface{}) { c.fe.Warnl(line, msg, args...) }
func (c *Config) Debug_checknil() bool                              { return c.fe.Debug_checknil() }

func (c *Config) logDebugHashMatch(evname, name string) {
	var file *os.File
	file = c.logfiles[evname]
	if file == nil {
		file = os.Stdout
		tmpfile := os.Getenv("GSHS_LOGFILE")
		if tmpfile != "" {
			var ok error
			file, ok = os.Create(tmpfile)
			if ok != nil {
				c.Fatalf(0, "Could not open hash-testing logfile %s", tmpfile)
			}
		}
		c.logfiles[evname] = file
	}
	s := fmt.Sprintf("%s triggered %s\n", evname, name)
	file.WriteString(s)
	file.Sync()
}

// DebugHashMatch returns true if environment variable evname
// 1) is empty (this is a special more-quickly implemented case of 3)
// 2) is "y" or "Y"
// 3) is a suffix of the sha1 hash of name
// 4) is a suffix of the environment variable
//    fmt.Sprintf("%s%d", evname, n)
//    provided that all such variables are nonempty for 0 <= i <= n
// Otherwise it returns false.
// When true is returned the message
//  "%s triggered %s\n", evname, name
// is printed on the file named in environment variable
//  GSHS_LOGFILE
// or standard out if that is empty or there is an error
// opening the file.

func (c *Config) DebugHashMatch(evname, name string) bool {
	evhash := os.Getenv(evname)
	if evhash == "" {
		return true // default behavior with no EV is "on"
	}
	if evhash == "y" || evhash == "Y" {
		c.logDebugHashMatch(evname, name)
		return true
	}
	if evhash == "n" || evhash == "N" {
		return false
	}
	// Check the hash of the name against a partial input hash.
	// We use this feature to do a binary search to
	// find a function that is incorrectly compiled.
	hstr := ""
	for _, b := range sha1.Sum([]byte(name)) {
		hstr += fmt.Sprintf("%08b", b)
	}

	if strings.HasSuffix(hstr, evhash) {
		c.logDebugHashMatch(evname, name)
		return true
	}

	// Iteratively try additional hashes to allow tests for multi-point
	// failure.
	for i := 0; true; i++ {
		ev := fmt.Sprintf("%s%d", evname, i)
		evv := os.Getenv(ev)
		if evv == "" {
			break
		}
		if strings.HasSuffix(hstr, evv) {
			c.logDebugHashMatch(ev, name)
			return true
		}
	}
	return false
}
