package main

import (
	"fmt"
	"runtime"
	"slices"
	"strconv"
)

// BarisPesanan struct besar: salinan per iterasi range mahal secara CPU
// karena runtime menyalin seluruh 280 byte pada tiap putaran.
type BarisPesanan struct {
	ID         int64
	Pembeli    [8]string
	Jumlah     [8]int
	Alamat     [4]string
	Keterangan string
}

var sink []int

// ukurMallocs menjalankan f di antara dua pembacaan MemStats dan
// mengembalikan jumlah alokasi yang terjadi di dalam f.
func ukurMallocs(f func()) int64 {
	var sebelum, sesudah runtime.MemStats
	runtime.ReadMemStats(&sebelum)
	f()
	runtime.ReadMemStats(&sesudah)
	return int64(sesudah.Mallocs - sebelum.Mallocs)
}

// filterInPlace menyaring elemen tanpa alokasi baru: backing array lama
// dipakai ulang untuk menampung hasil, isi asal tertimpa.
func filterInPlace(asal []int) []int {
	hasil := asal[:0]
	for _, v := range asal {
		if v%3 != 0 {
			hasil = append(hasil, v)
		}
	}
	return hasil
}

func main() {
	jumlah := 1000

	// Pola 1: append tanpa kapasitas menumbuhkan backing array berulang kali.
	tanpaKapasitas := ukurMallocs(func() {
		var xs []int
		for i := 0; i < jumlah; i++ {
			xs = append(xs, i)
		}
		sink = xs
	})
	denganKapasitas := ukurMallocs(func() {
		xs := make([]int, 0, jumlah)
		for i := 0; i < jumlah; i++ {
			xs = append(xs, i)
		}
		sink = xs
	})
	fmt.Printf("append %d elemen tanpa kapasitas : %d alokasi\n", jumlah, tanpaKapasitas)
	fmt.Printf("append %d elemen dengan make(cap): %d alokasi\n", jumlah, denganKapasitas)

	// Pola 2: filter in-place memakai ulang backing array: nol alokasi.
	asal := make([]int, 0, jumlah)
	for i := 0; i < jumlah; i++ {
		asal = append(asal, i)
	}
	hasilFilter := ukurMallocs(func() {
		asal = filterInPlace(asal)
	})
	fmt.Printf("filter in-place %d elemen        : %d alokasi, %d elemen tersisa\n",
		jumlah, hasilFilter, len(asal))

	// Pola 3: ukuran map yang diketahui di muka menghemat rehash.
	kunci := buatKunci(500)
	tanpaHint := ukurMallocs(func() {
		m := make(map[string]int)
		for i, k := range kunci {
			m[k] = i
		}
	})
	denganHint := ukurMallocs(func() {
		m := make(map[string]int, len(kunci))
		for i, k := range kunci {
			m[k] = i
		}
	})
	fmt.Printf("map 500 kunci tanpa hint        : %d alokasi\n", tanpaHint)
	fmt.Printf("map 500 kunci dengan hint       : %d alokasi\n", denganHint)

	// Pola 4: binary search pada slice terurut, tanpa alokasi.
	terurut := []int{10, 20, 30, 40, 50, 60}
	posisi, ketemu := slices.BinarySearch(terurut, 40)
	fmt.Println("BinarySearch 40 pada 6 elemen   : indeks", posisi, "ditemukan", ketemu)

	// Pola 5: range menyalin struct besar; akses lewat indeks menghindari salinan.
	baris := buatBaris()
	total := 0
	for i := range baris { // tanpa salinan BarisPesanan per iterasi
		if baris[i].ID != 0 {
			total++
		}
	}
	fmt.Println("Baris terisi lewat indeks       :", total, "dari", len(baris))
}

func buatKunci(n int) []string {
	ks := make([]string, 0, n)
	for i := 0; i < n; i++ {
		ks = append(ks, "kunci-"+strconv.Itoa(i))
	}
	return ks
}

func buatBaris() []BarisPesanan {
	b := make([]BarisPesanan, 3)
	b[0].ID = 101
	b[1].ID = 102
	b[2].ID = 103
	return b
}
