package main

import "fmt"

type Transform func(int) int

func applyTwice(n int, f Transform) int { return f(f(n)) }
func divmod(a, b int) (int, int)        { return a / b, a % b }
func counter(start int) func() int {
	n := start
	return func() int { n++; return n }
}
func total(xs ...int) int {
	n := 0
	for _, x := range xs {
		n += x
	}
	return n
}
func replaceFirst(xs ...int) {
	if len(xs) > 0 {
		xs[0] = 99
	}
}
func deferred() (result int) {
	n := 1
	defer fmt.Println("argument:", n)
	defer func() { fmt.Println("closure:", n); result++ }()
	n = 2
	return 10
}
func unnamed() int {
	n := 10
	defer func() { n++ }()
	return n
}
func savedFunction() {
	f := func() { fmt.Println("function: old") }
	defer f()
	f = func() { fmt.Println("function: new") }
	_ = f
}
func loopValues() (fresh, shared []int) {
	var a, b []func() int
	for i := 0; i < 3; i++ {
		a = append(a, func() int { return i })
	}
	var i int
	for i = 0; i < 3; i++ {
		b = append(b, func() int { return i })
	}
	for _, f := range a {
		fresh = append(fresh, f())
	}
	for _, f := range b {
		shared = append(shared, f())
	}
	return
}
func recoverHelper() any { return recover() }
func recoverNamed(events *[]string) {
	*events = append(*events, "named: "+fmt.Sprint(recover()))
}
func indirect() (events []string) {
	defer func() {
		if p := recover(); p != nil {
			events = append(events, "outer: "+fmt.Sprint(p))
		}
	}()
	defer func() {
		events = append(events, "helper: "+fmt.Sprint(recoverHelper()))
	}()
	panic("boom")
}
func recovery(kind string) (events []string) {
	defer func() {
		if p := recover(); p != nil {
			events = append(events, "outer: "+fmt.Sprint(p))
		}
	}()
	switch kind {
	case "named":
		defer recoverNamed(&events)
	case "direct":
		defer func() { events = append(events, "direct: "+fmt.Sprint(recover())) }()
	case "helper":
		defer func() { events = append(events, "helper: "+fmt.Sprint(recoverHelper())) }()
	case "builtin":
		defer recover()
	}
	panic("boom")
}
func deferScope() (events []string) {
	for i := 0; i < 3; i++ {
		defer func(n int) { events = append(events, fmt.Sprint("close ", n)) }(i)
		events = append(events, fmt.Sprint("use ", i))
	}
	events = append(events, "loop done")
	return
}
func deferPerItem() (events []string) {
	for i := 0; i < 3; i++ {
		func(n int) {
			defer func() { events = append(events, fmt.Sprint("close ", n)) }()
			events = append(events, fmt.Sprint("use ", n))
		}(i)
	}
	return
}
func main() {
	fmt.Println("twice:", applyTwice(3, func(n int) int { return n + 2 }))
	q, r := divmod(17, 5)
	fmt.Println("divmod:", q, r)
	var absent Transform
	fmt.Println("nil function:", absent == nil)
	a, b := counter(10), counter(10)
	fmt.Println("counters:", a(), a(), b())
	xs := []int{1, 2, 3}
	fmt.Println("variadic:", total(), total(1, 2, 3), total(xs...))
	replaceFirst(xs...)
	fmt.Println("shared slice:", xs)
	fmt.Println("named result:", deferred())
	fmt.Println("unnamed result:", unnamed())
	savedFunction()
	fresh, shared := loopValues()
	fmt.Println("loop declared:", fresh)
	fmt.Println("loop reused:", shared)
	fmt.Println("direct recovery:", recovery("direct"))
	fmt.Println("helper recovery:", recovery("helper"))
	fmt.Println("builtin recovery:", recovery("builtin"))
	fmt.Println("function scope:", deferScope())
	fmt.Println("per item:", deferPerItem())
}
