// Copyright 2016 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 main

import (
	"fmt"
	"strings"
)

// make fake flow graph.

// The blocks of the flow graph are designated with letters A
// through Z, always including A (start block) and Z (exit
// block) The specification of a flow graph is a comma-
// separated list of block successor words, for blocks ordered
// A, B, C etc, where each block except Z has one or two
// successors, and any block except A can be a target. Within
// the generated code, each block with two successors includes
// a conditional testing x & 1 != 0 (x is the input parameter
// to the generated function) and also unconditionally shifts x
// right by one, so that different inputs generate different
// execution paths, including loops. Every block inverts a
// global binary to ensure it is not empty. For a flow graph
// with J words (J+1 blocks), a J-1 bit serial number specifies
// which blocks (not including A and Z) include an increment of
// the return variable y by increasing powers of 10, and a
// different version of the test function is created for each
// of the 2-to-the-(J-1) serial numbers.

// For each generated function a compact summary is also
// created so that the generated funtion can be simulated
// with a simple interpreter to sanity check the behavior of
// the compiled code.

// For example:

// func BC_CD_BE_BZ_CZ101(x int64) int64 {
// 	y := int64(0)
// 	var b int64
// 	_ = b
// 	b = x & 1
// 	x = x >> 1
// 	if b != 0 {
// 		goto C
// 	}
// 	goto B
// B:
// 	glob_ = !glob_
// 	y += 1
// 	b = x & 1
// 	x = x >> 1
// 	if b != 0 {
// 		goto D
// 	}
// 	goto C
// C:
// 	glob_ = !glob_
// 	// no y increment
// 	b = x & 1
// 	x = x >> 1
// 	if b != 0 {
// 		goto E
// 	}
// 	goto B
// D:
// 	glob_ = !glob_
// 	y += 10
// 	b = x & 1
// 	x = x >> 1
// 	if b != 0 {
// 		goto Z
// 	}
// 	goto B
// E:
// 	glob_ = !glob_
// 	// no y increment
// 	b = x & 1
// 	x = x >> 1
// 	if b != 0 {
// 		goto Z
// 	}
// 	goto C
// Z:
// 	return y
// }

// {f:BC_CD_BE_BZ_CZ101,
//  maxin:32, blocks:[]blo{
//  	blo{inc:0, cond:true, succs:[2]int64{1, 2}},
//  	blo{inc:1, cond:true, succs:[2]int64{2, 3}},
//  	blo{inc:0, cond:true, succs:[2]int64{1, 4}},
//  	blo{inc:10, cond:true, succs:[2]int64{1, 25}},
//  	blo{inc:0, cond:true, succs:[2]int64{2, 25}},}},

var labels string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

func blocks(spec string) (blocks []string, fnameBase string) {
	spec = strings.ToUpper(spec)
	blocks = strings.Split(spec, ",")
	fnameBase = strings.Replace(spec, ",", "_", -1)
	return
}

func makeFunctionFromFlowGraph(blocks []blo, fname string) string {
	s := ""

	for j := range blocks {
		// begin block
		if j == 0 {
			// block A, implicit label
			s += `
func ` + fname + `(x int64) int64 {
	y := int64(0)
	var b int64
	_ = b`
		} else {
			// block B,C, etc, explicit label w/ conditional increment
			l := labels[j : j+1]
			yeq := `
	// no y increment`
			if blocks[j].inc != 0 {
				yeq = `
	y += ` + fmt.Sprintf("%d", blocks[j].inc)
			}

			s += `
` + l + `:
	glob = !glob` + yeq
		}

		// edges to successors
		if blocks[j].cond { // conditionally branch to second successor
			s += `
	b = x & 1
	x = x >> 1
	if b != 0 {` + `
		goto ` + string(labels[blocks[j].succs[1]]) + `
	}`

		}
		// branch to first successor
		s += `
	goto ` + string(labels[blocks[j].succs[0]])
	}

	// end block (Z)
	s += `
Z:
	return y
}
`
	return s
}

var graphs []string = []string{
	"Z", "BZ,Z", "B,BZ", "BZ,BZ",
	"ZB,Z", "B,ZB", "ZB,BZ", "ZB,ZB",

	"BC,C,Z", "BC,BC,Z", "BC,BC,BZ",
	"BC,Z,Z", "BC,ZC,Z", "BC,ZC,BZ",
	"BZ,C,Z", "BZ,BC,Z", "BZ,CZ,Z",
	"BZ,C,BZ", "BZ,BC,BZ", "BZ,CZ,BZ",
	"BZ,C,CZ", "BZ,BC,CZ", "BZ,CZ,CZ",

	"BC,CD,BE,BZ,CZ",
	"BC,BD,CE,CZ,BZ",
	"BC,BD,CE,FZ,GZ,F,G",
	"BC,BD,CE,FZ,GZ,G,F",

	"BC,DE,BE,FZ,FZ,Z",
	"BC,DE,BE,FZ,ZF,Z",
	"BC,DE,BE,ZF,FZ,Z",
	"BC,DE,EB,FZ,FZ,Z",
	"BC,ED,BE,FZ,FZ,Z",
	"CB,DE,BE,FZ,FZ,Z",

	"CB,ED,BE,FZ,FZ,Z",
	"BC,ED,EB,FZ,ZF,Z",
	"CB,DE,EB,ZF,FZ,Z",
	"CB,ED,EB,FZ,FZ,Z",

	"BZ,CD,CD,CE,BZ",
	"EC,DF,FG,ZC,GB,BE,FD",
	"BH,CF,DG,HE,BF,CG,DH,BZ",
}

// blo describes a block in the generated/interpreted code
type blo struct {
	inc   int64 // increment amount
	cond  bool  // block ends in conditional
	succs [2]int64
}

// strings2blocks converts a slice of strings specifying
// successors into a slice of blo encoding the blocks in a
// common form easy to execute or interpret.
func strings2blocks(blocks []string, fname string, i int) (bs []blo, cond uint) {
	bs = make([]blo, len(blocks))
	edge := int64(1)
	cond = 0
	k := uint(0)
	for j, s := range blocks {
		if j == 0 {
		} else {
			if (i>>k)&1 != 0 {
				bs[j].inc = edge
				edge *= 10
			}
			k++
		}
		if len(s) > 1 {
			bs[j].succs[1] = int64(blocks[j][1] - 'A')
			bs[j].cond = true
			cond++
		}
		bs[j].succs[0] = int64(blocks[j][0] - 'A')
	}
	return bs, cond
}

// fmtBlocks writes out the blocks for consumption in the generated test
func fmtBlocks(bs []blo) string {
	s := "[]blo{"
	for _, b := range bs {
		s += fmt.Sprintf("blo{inc:%d, cond:%v, succs:[2]int64{%d, %d}},", b.inc, b.cond, b.succs[0], b.succs[1])
	}
	s += "}"
	return s
}

func main() {
	fmt.Printf(`// This is a machine-generated test file from flowgraph_generator1.go.
package main
import "fmt"
var glob bool
`)
	s := "var funs []fun = []fun{"
	for _, g := range graphs {
		split, fnameBase := blocks(g)
		nconfigs := 1 << uint(len(split)-1)

		for i := 0; i < nconfigs; i++ {
			fname := fnameBase + fmt.Sprintf("%b", i)
			bs, k := strings2blocks(split, fname, i)
			fmt.Printf("%s", makeFunctionFromFlowGraph(bs, fname))
			s += `
		{f:` + fname + `, maxin:` + fmt.Sprintf("%d", 1<<k) + `, blocks:` + fmtBlocks(bs) + `},`
		}

	}
	s += `}
`
	// write types for name+array tables.
	fmt.Printf("%s",
		`
type blo struct {
	inc   int64
	cond  bool
	succs [2]int64
}
type fun struct {
	f      func(int64) int64
	maxin  int64
	blocks []blo
}
`)
	// write table of function names and blo arrays.
	fmt.Printf("%s", s)

	// write interpreter and main/test
	fmt.Printf("%s", `
func interpret(blocks []blo, x int64) (int64, bool) {
	y := int64(0)
	last := int64(25) // 'Z'-'A'
	j := int64(0)
	for i := 0; i < 4*len(blocks); i++ {
		b := blocks[j]
		y += b.inc
		next := b.succs[0]
		if b.cond {
			c := x&1 != 0
			x = x>>1
			if c {
				next = b.succs[1]
			}
		}
		if next == last {
			return y, true
		}
		j = next
	}
	return -1, false
}

func main() {
	sum := int64(0)
	for i, f := range funs {
		for x := int64(0); x < 16*f.maxin; x++ {
			y, ok := interpret(f.blocks, x)
			if ok {
				yy := f.f(x)
				if y != yy {
					fmt.Printf("y(%d) != yy(%d), x=%b, i=%d, blocks=%v\n", y, yy, x, i, f.blocks)
					return
				}
				sum += y
			}
		}
	}
//	fmt.Printf("Sum of all returns over all terminating inputs is %d\n", sum)
}
`)
}
