package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"strings"
)

// hitungWriter menghitung total byte dan jumlah panggilan Write yang diterima.
type hitungWriter struct {
	total     int64
	panggilan int
}

func (h *hitungWriter) Write(p []byte) (int, error) {
	h.total += int64(len(p))
	h.panggilan++
	return len(p), nil
}

// pembacaPolos membungkus io.Reader tanpa method WriteTo
// sehingga io.CopyBuffer benar-benar memakai buffer yang kita sediakan.
type pembacaPolos struct {
	r io.Reader
}

func (p *pembacaPolos) Read(b []byte) (int, error) {
	return p.r.Read(b)
}

func main() {
	// Bagian 1: io.Copy menyalin seluruh aliran dengan buffer internalnya.
	sumber := strings.NewReader(strings.Repeat("ab", 50)) // 100 byte
	var w1 hitungWriter
	n, err := io.Copy(&w1, sumber)
	if err != nil {
		fmt.Println("gagal menyalin:", err)
		return
	}
	fmt.Printf("io.Copy      : %d byte, %d panggilan Write\n", n, w1.panggilan)

	// Bagian 2: io.CopyBuffer memakai buffer yang kita sediakan sendiri.
	var w2 hitungWriter
	buf := make([]byte, 8)
	if _, err := io.CopyBuffer(&w2, &pembacaPolos{strings.NewReader(strings.Repeat("ab", 50))}, buf); err != nil {
		fmt.Println("gagal menyalin:", err)
		return
	}
	fmt.Printf("CopyBuffer 8 : %d byte, %d panggilan Write\n", w2.total, w2.panggilan)

	// Bagian 3: io.LimitReader hanya mengizinkan N byte pertama lolos.
	terbatas := io.LimitReader(strings.NewReader("abcdefghij"), 4)
	data, err := io.ReadAll(terbatas)
	if err != nil {
		fmt.Println("gagal membaca:", err)
		return
	}
	fmt.Printf("LimitReader  : %q (%d byte)\n", data, len(data))

	// Bagian 4: io.TeeReader menyalin ke log sambil aliran tetap dibaca.
	var log bytes.Buffer
	asli := io.TeeReader(strings.NewReader("transaksi-001 OK"), &log)
	data, err = io.ReadAll(asli)
	if err != nil {
		fmt.Println("gagal membaca:", err)
		return
	}
	fmt.Printf("TeeReader    : dibaca %d byte, log %d byte\n", len(data), log.Len())

	// Bagian 5: io.Pipe menghubungkan goroutine penulis dan pembaca.
	pembaca, penulis := io.Pipe()
	go func() {
		for _, pesan := range []string{"alpha\n", "beta\n", "gamma\n"} {
			if _, err := penulis.Write([]byte(pesan)); err != nil {
				penulis.CloseWithError(err)
				return
			}
		}
		penulis.Close()
	}()
	data, err = io.ReadAll(pembaca)
	if err != nil {
		fmt.Println("gagal membaca pipa:", err)
		return
	}
	fmt.Printf("io.Pipe      : %d byte (%q)\n", len(data), data)

	// Bagian 6: gzip round-trip, kompres lalu dekompres kembali.
	teks := "data yang dikompresi dan kembali utuh"
	var terkompres bytes.Buffer
	zw := gzip.NewWriter(&terkompres)
	if _, err := zw.Write([]byte(teks)); err != nil {
		fmt.Println("gagal mengompresi:", err)
		return
	}
	if err := zw.Close(); err != nil {
		fmt.Println("gagal menutup kompresor:", err)
		return
	}
	ukuran := terkompres.Len()
	zr, err := gzip.NewReader(&terkompres)
	if err != nil {
		fmt.Println("gagal membuka dekompresor:", err)
		return
	}
	data, err = io.ReadAll(zr)
	if err != nil {
		fmt.Println("gagal mendekompresi:", err)
		return
	}
	zr.Close()
	fmt.Printf("gzip         : %d byte -> %d byte -> %q\n", len(teks), ukuran, data)
}
