package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strings"
)

func WriteCount(w io.Writer, n int) error {
	_, err := fmt.Fprintf(w, "count=%d\n", n)
	return err
}

type WriterFunc func([]byte) (int, error)

func (f WriterFunc) Write(p []byte) (int, error) { return f(p) }

var _ io.Writer = WriterFunc(nil)

func writers() {
	var buf bytes.Buffer
	var text strings.Builder
	err1 := WriteCount(&buf, 3)
	err2 := WriteCount(&text, 3)
	fmt.Printf("%q %q %v %v\n", buf.String(), text.String(), err1, err2)
	var w io.Writer = &buf
	var x any = w
	fmt.Printf("%T %T\n", w, x)
	sentinel := errors.New("blocked")
	fail := WriterFunc(func([]byte) (int, error) { return 0, sentinel })
	fmt.Println("write failure:", errors.Is(WriteCount(fail, 3), sentinel))
}

type UserID int64

func assertions() {
	var x any = 42
	n, ok := x.(int)
	fmt.Println(n, ok)
	s, ok := x.(string)
	fmt.Printf("%q %t\n", s, ok)
	var id any = UserID(7)
	_, plain := id.(int64)
	typed, correct := id.(UserID)
	fmt.Println(plain, typed, correct)
	var buf bytes.Buffer
	var a any = &buf
	_, writable := a.(io.Writer)
	fmt.Println(writable)
}

func anySlices() {
	ints := []int{1, 2}
	var whole any = ints
	values := make([]any, len(ints))
	for i, n := range ints {
		values[i] = n
	}
	ints[0] = 9
	fmt.Printf("%T %v | %T %v\n", whole, whole, values, values)
}

type Problem struct{ Message string }

func (p *Problem) Error() string {
	if p == nil {
		return "<nil *Problem>"
	}
	return p.Message
}
func badResult(ok bool) error {
	var p *Problem
	if !ok {
		p = &Problem{Message: "failed"}
	}
	return p
}
func goodResult(ok bool) error {
	if !ok {
		return &Problem{Message: "failed"}
	}
	return nil
}
func nilInterfaces() {
	var p *Problem
	var err error = p
	var x any = err
	fmt.Println(p == nil, err == nil, x == nil)
	fmt.Printf("%T %T\n", err, x)
	fmt.Println(err.Error())
	fmt.Println(badResult(true) == nil, goodResult(true) == nil)
	var empty error
	fmt.Println(empty == nil)
}

func describe(x any) string {
	switch v := x.(type) {
	case nil:
		return "nil interface"
	case int:
		return fmt.Sprintf("int %d", v)
	case string:
		return fmt.Sprintf("string %q", v)
	case *Problem:
		if v == nil {
			return "nil *Problem"
		}
		return "problem " + v.Message
	default:
		return fmt.Sprintf("other %T", v)
	}
}
func switches() {
	for _, v := range []any{nil, 42, "go", (*Problem)(nil), UserID(7)} {
		fmt.Println(describe(v))
	}
}
func comparisons() {
	var a any = int(1)
	var b any = int64(1)
	var c any = int(1)
	fmt.Println(a == b, a == c)
	var s any = []int{1}
	fmt.Println(s == nil)
}

func main() {
	for _, demo := range []struct {
		name string
		run  func()
	}{
		{"writers", writers}, {"assertions", assertions}, {"any slices", anySlices},
		{"nil interfaces", nilInterfaces}, {"switches", switches},
		{"comparisons", comparisons},
	} {
		fmt.Println("==", demo.name, "==")
		demo.run()
	}
}
