package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	// Direktori kerja sementara agar program aman dijalankan berulang.
	dir, err := os.MkdirTemp("", "contoh-besar-*")
	if err != nil {
		fmt.Println("gagal membuat direktori:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Berkas uji: 1000 baris masing-masing 11 byte, total 11000 byte.
	besar := filepath.Join(dir, "data-besar.txt")
	f, err := os.Create(besar)
	if err != nil {
		fmt.Println("gagal membuat berkas:", err)
		return
	}
	for i := 1; i <= 1000; i++ {
		fmt.Fprintf(f, "baris-%04d\n", i)
	}
	if err := f.Close(); err != nil {
		fmt.Println("gagal menutup berkas:", err)
		return
	}
	info, err := os.Stat(besar)
	if err != nil {
		fmt.Println("gagal membaca metadata:", err)
		return
	}
	fmt.Printf("berkas uji     : %d byte\n", info.Size())

	// Scanner menghitung baris dengan memori konstan.
	barisan, err := hitungBaris(besar)
	if err != nil {
		fmt.Println("gagal menghitung baris:", err)
		return
	}
	fmt.Printf("scanner        : %d baris\n", barisan)

	// Loop Read membaca per potongan 4096 byte.
	total, panggilan, err := bacaPerPotongan(besar, 4096)
	if err != nil {
		fmt.Println("gagal membaca potongan:", err)
		return
	}
	fmt.Printf("loop Read 4096 : %d byte, %d panggilan Read\n", total, panggilan)

	// Satu baris raksasa mengalahkan batas bawaan Scanner.
	isiPanjang := strings.Repeat("x", 200000) + "\n"
	panjang := filepath.Join(dir, "baris-panjang.txt")
	if err := os.WriteFile(panjang, []byte(isiPanjang), 0644); err != nil {
		fmt.Println("gagal menulis berkas:", err)
		return
	}
	fmt.Printf("baris panjang  : %d byte\n", len(isiPanjang))

	err = pemindaianBawaan(panjang)
	fmt.Println("scanner bawaan:", err)

	barisanPanjang, ukuran, err := pemindaianBerbuffer(panjang)
	if err != nil {
		fmt.Println("gagal memindai:", err)
		return
	}
	fmt.Printf("scanner+Buffer : %d baris, %d byte\n", barisanPanjang, ukuran)
}

// hitungBaris menghitung jumlah baris berkas dengan bufio.Scanner.
func hitungBaris(berkas string) (int, error) {
	f, err := os.Open(berkas)
	if err != nil {
		return 0, err
	}
	defer f.Close()

	barisan := 0
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		barisan++
	}
	return barisan, scanner.Err()
}

// bacaPerPotongan membaca berkas dengan buffer berukuran tertentu.
func bacaPerPotongan(berkas string, ukuran int) (int, int, error) {
	f, err := os.Open(berkas)
	if err != nil {
		return 0, 0, err
	}
	defer f.Close()

	buf := make([]byte, ukuran)
	total, panggilan := 0, 0
	for {
		n, err := f.Read(buf)
		panggilan++
		if n > 0 {
			total += n
		}
		if err == io.EOF {
			return total, panggilan, nil
		}
		if err != nil {
			return total, panggilan, err
		}
	}
}

// pemindaianBawaan memindai dengan batas bawaan Scanner (64 KB per token).
func pemindaianBawaan(berkas string) error {
	f, err := os.Open(berkas)
	if err != nil {
		return err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
	}
	return scanner.Err()
}

// pemindaianBerbuffer menaikkan batas token Scanner lewat method Buffer.
func pemindaianBerbuffer(berkas string) (int, int, error) {
	f, err := os.Open(berkas)
	if err != nil {
		return 0, 0, err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	scanner.Buffer(make([]byte, 1024), 1<<20)
	barisan, ukuran := 0, 0
	for scanner.Scan() {
		barisan++
		ukuran += len(scanner.Bytes())
	}
	if err := scanner.Err(); err != nil {
		return 0, 0, err
	}
	return barisan, ukuran, nil
}
