package main

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

// Notifier adalah interface yang dipertahankan oleh dekorator.
// Dekorator membungkus interface yang sama sehingga bisa bertumpuk.
type Notifier interface {
	Send(msg string) error
}

// emailFlaky adalah notifikasi tiruan yang gagal dua kali pertama
// untuk mensimulasikan jaringan tidak stabil.
type emailFlaky struct {
	gagalTersisa int
}

func (n *emailFlaky) Send(msg string) error {
	if n.gagalTersisa > 0 {
		n.gagalTersisa--
		return errors.New("jaringan sibuk")
	}
	fmt.Println("terkirim :", msg)
	return nil
}

// RetryNotifier adalah dekorator: mengulang pengiriman sampai maks kali.
type RetryNotifier struct {
	berikut Notifier
	maks    int
}

func (r *RetryNotifier) Send(msg string) error {
	var err error
	for i := 1; i <= r.maks; i++ {
		if err = r.berikut.Send(msg); err == nil {
			return nil
		}
		fmt.Printf("coba %d    : gagal (%v)\n", i, err)
	}
	return fmt.Errorf("gagal setelah %d percobaan: %w", r.maks, err)
}

// LogNotifier adalah dekorator kedua: mencatat sebelum dan sesudah.
type LogNotifier struct {
	berikut Notifier
}

func (l *LogNotifier) Send(msg string) error {
	fmt.Printf("log       : mengirim %q\n", msg)
	err := l.berikut.Send(msg)
	if err != nil {
		fmt.Printf("log       : gagal (%v)\n", err)
		return err
	}
	fmt.Printf("log       : %q terkirim\n", msg)
	return nil
}

func main() {
	// Susunan dekorator: Log(Retry(Email)).
	// emailFlaky gagal dua kali, Retry mengulang, Log mencatat buktinya.
	notifier := &LogNotifier{
		berikut: &RetryNotifier{
			berikut: &emailFlaky{gagalTersisa: 2},
			maks:    3,
		},
	}
	if err := notifier.Send("pesanan #1234 sudah dikirim"); err != nil {
		fmt.Println("error:", err)
	}

	// Dekorator io.Reader di standard library:
	// bytes.Reader -> gzip.Reader -> hasil identik dengan aslinya.
	asli := []byte(strings.Repeat("belajar pola desain di Go. ", 6))
	var buf bytes.Buffer
	zw := gzip.NewWriter(&buf)
	if _, err := zw.Write(asli); err != nil {
		fmt.Println("error:", err)
		return
	}
	zw.Close()
	kompres := buf.Bytes()
	fmt.Printf("asli %d byte -> terkompresi %d byte\n", len(asli), len(kompres))

	zr, err := gzip.NewReader(bytes.NewReader(kompres))
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	hasil, err := io.ReadAll(zr)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("hasil dekompresi identik :", bytes.Equal(hasil, asli))
	fmt.Println("jumlah kata              :", len(strings.Fields(string(hasil))))
}
