import (
    "context"
    "flag"
    "fmt"
    "time"
)

const (
    FIZZBUZZ = iota
    FIZZ
    BUZZ
    NOTFIZZBUZZ
)

type msg struct {
    Q int
    A int
}

type ring struct {
    next *ring
    f    ringer
}

type ringer interface {
    Do(ctx context.Context, out chan<- msg) error
}

type fizzbuzz struct{}

func (fizzbuzz) Do(ctx context.Context, out chan<- msg) error {
    return do(ctx, 15, FIZZBUZZ, out)
}

type fizz struct{}

func (f fizz) Do(ctx context.Context, out chan<- msg) error {
    return do(ctx, 3, FIZZ, out)
}

type buzz struct{}

func (b buzz) Do(ctx context.Context, out chan<- msg) error {
    return do(ctx, 5, BUZZ, out)
}

func do(ctx context.Context, d, a int, out chan<- msg) error {
    n := ctx.Value("n")
    if n == nil {
        return fmt.Errorf("fizzbuzz-request-value is empty")
    }
    if n.(int)%d == 0 {
        select {
        case out <- msg{Q: n.(int), A: a}:
        case <-ctx.Done():
        }
    }
    return nil
}

type notFizzbuzz struct{}

func (nfb notFizzbuzz) Do(ctx context.Context, out chan<- msg) error {
    n := ctx.Value("n")
    if n == nil {
        return fmt.Errorf("fizzbuzz-request-value is empty")
    }
    if n.(int)%3 != 0 && n.(int)%5 != 0 {
        select {
        case out <- msg{Q: n.(int), A: NOTFIZZBUZZ}:
        case <-ctx.Done():
        }
    }
    return nil
}

func set() (*ring, int) {
    rings := []ring{
        {nil, fizzbuzz{}},
        {nil, fizz{}},
        {nil, buzz{}},
        {nil, notFizzbuzz{}},
    }

    for i := 0; i < len(rings); i++ {
        j := i + 1
        if j >= len(rings) {
            j = 0
        }
        rings[i].next = &rings[j]
    }

    return &rings[0], cap(rings)
}

var req chan int = make(chan int)

func circle(cur *ring, m int) {
    ctx := context.Background()
    for {
        i := <-req
        ctx := context.WithValue(ctx, "n", i)
        go func() {
            ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
            defer cancel()

            res := make(chan msg)
            go circleline(ctx, cur, m, res)

            fmt.Println(recv(ctx, res))
        }()
    }
}

func circleline(ctx context.Context, cur *ring, m int, out chan<- msg) {
    for ; m > 0; m-- {
        go cur.f.Do(ctx, out)
        cur = cur.next
    }
}

func recv(ctx context.Context, res <-chan msg) (buf struct {
    Q int
    A [4]string
    T string
}) {
    for {
        select {
        case x := <-res:
            defer func() { buf.Q = x.Q }()
            switch x.A {
            case FIZZBUZZ:
                defer func() { buf.A[FIZZBUZZ] = "fizzbuzz" }()
                return

            case FIZZ:
                defer func() { buf.A[FIZZ] = "fizz" }()

            case BUZZ:
                defer func() { buf.A[BUZZ] = "buzz" }()

            case NOTFIZZBUZZ:
                defer func() { buf.A[NOTFIZZBUZZ] = "-" }()
                return
            }

        case <-ctx.Done():
            defer func() { buf.T = time.Now().Format(time.StampNano) }()
            return
        }
    }
}

var max = flag.Int("m", 100000, "send numbers")

func main() {
    flag.Parse()

    term, n := set()
    go circle(term, n)

    for i := 0; i < *max; i++ {
        req <- i
    }

    time.Sleep(time.Second)
}