fizzbuzz(6)

package main

import (
    "flag"
    "fmt"
    "sync"
)

var (
    vol = flag.Int("v", 100, "volume")
    max = flag.Int("m", 10000, "send numbers")
)

type msg struct {
    Q int
    A int
}

type comm struct {
    Sch chan msg
    Rch chan msg
}

type reply struct {
    Ch chan *comm
}

var (
    poolSub chan reply = make(chan reply)
    poolAdd chan *comm = make(chan *comm)
)

var (
    standbyPub chan reply = make(chan reply)
    standbyOk  chan *comm = make(chan *comm)
)

func save() {
    var pool []*comm = make([]*comm, 0, *vol)
    var standby []*comm = make([]*comm, 0, *vol)
    for {
        select {
        case x := <-poolSub:
            last := len(pool) - 1
            if last > 0 {
                x.Ch <- pool[last]
                pool = pool[:last]
            } else {
                x.Ch <- nil
            }

        case x := <-poolAdd:
            standby = append(standby, x)

        case y := <-standbyPub:
            last := len(standby) - 1
            if last > 0 {
                y.Ch <- standby[last]
                standby = standby[:last]
            } else {
                y.Ch <- nil
            }

        case y := <-standbyOk:
            pool = append(pool, y)
        }
    }
}

func set() func(func(msg) msg) {
    var comms []comm
    return func(g func(x msg) msg) {
        for i := 0; i < *vol; i++ {
            comms = append(comms, comm{})
            comms[i].Sch = make(chan msg)
            comms[i].Rch = make(chan msg)
        }

        for i := 0; i < *vol; i++ {
            i := i
            rch := comms[i].Sch
            sch := comms[i].Rch
            poolAdd <- &comms[i]

            go func() {
                for {
                    x := <-rch
                    sch <- g(x)
                    poolAdd <- &comms[i]
                }
            }()
        }
    }
}

const (
    FIZZBUZZ = iota
    FIZZ
    BUZZ
    NOTFIZZBUZZ
)

func init() {
    flag.Parse()
    go save()

    f := set()
    g := func(m msg) msg {
        x := m.Q
        switch {
        case x%15 == 0:
            m.A = FIZZBUZZ
        case x%3 == 0:
            m.A = FIZZ
        case x%5 == 0:
            m.A = BUZZ
        default:
            m.A = NOTFIZZBUZZ
        }
        return m
    }
    f(g)
}

var ctrl chan struct{} = make(chan struct{})

func let() {
    close(ctrl)
}

func send(g func() msg) {
    for i := 0; i < *max; i++ {
        go func() {
            res := make(chan *comm)
            <-ctrl
            for {
                poolSub <- reply{res}
                c := <-res

                if c != nil {
                    c.Sch <- g()
                    break
                }
            }
            close(res)
        }()
    }
}

// done is for waiting all-received-timing
var done chan struct{} = make(chan struct{})

func wait() {
    <-done
}

func recv(f func(chan msg) error) {
    var wg sync.WaitGroup
    wg.Add(*max)
    go func() {
        for {
            res := make(chan *comm)
            standbyPub <- reply{res}
            c := <-res
            if c != nil {
                go func() {
                    f(c.Rch)
                    wg.Done()
                }()
                standbyOk <- c
            }
            close(res)
        }
    }()
    wg.Wait()
    close(done)
}

func main() {
    // send and g sends numbers to setted goroutines
    f := func() func() msg {
        var n int
        return func() msg {
            n++
            return msg{Q: n}
        }
    }
    g := f()
    go send(g)

    // recv and h processes the results from setted goroutines
    h := func(ch chan msg) error {
        m := <-ch

        var ans string
        switch m.A {
        case FIZZBUZZ:
            ans = "fizzbuzz"
        case FIZZ:
            ans = "fizz"
        case BUZZ:
            ans = "buzz"
        case NOTFIZZBUZZ:
            ans = "-"
        }

        fmt.Println(m.Q, ":", ans)
        return nil
    }
    go recv(h)

    let()  // start
    wait() // wait
}