package subreaper

import (
	"os"
	"os/signal"
	"sync"
	"syscall"

	"github.com/Sirupsen/logrus"
	"github.com/docker/containerd/osutils"
)

var (
	subscriptions = map[int]*Subscription{}
	subLock       = sync.Mutex{}
	counter       = 0
	once          = sync.Once{}
)

type Subscription struct {
	id   int
	exit osutils.Exit
	c    chan osutils.Exit
	wg   sync.WaitGroup
}

func (s *Subscription) SetPid(pid int) {
	go func() {
		for exit := range s.c {
			if exit.Pid == pid {
				s.exit = exit
				s.wg.Done()
				Unsubscribe(s)
				break
			}
		}
	}()
}

func (s *Subscription) Wait() int {
	s.wg.Wait()
	return s.exit.Status
}

func Subscribe() *Subscription {
	subLock.Lock()
	defer subLock.Unlock()

	Start()

	counter++
	s := &Subscription{
		id: counter,
		c:  make(chan osutils.Exit, 1024),
	}
	s.wg.Add(1)
	subscriptions[s.id] = s
	return s
}

func Unsubscribe(sub *Subscription) {
	subLock.Lock()
	defer subLock.Unlock()

	if _, ok := subscriptions[sub.id]; ok {
		close(sub.c)
		delete(subscriptions, sub.id)
	}
}

func Start() error {
	var err error
	once.Do(func() {
		err = osutils.SetSubreaper(1)
		if err != nil {
			return
		}

		s := make(chan os.Signal, 2048)
		signal.Notify(s, syscall.SIGCHLD)
		go childReaper(s)
	})

	return err
}

func childReaper(s chan os.Signal) {
	for range s {
		exits, err := osutils.Reap()
		if err == nil {
			notify(exits)
		} else {
			logrus.WithField("error", err).Warn("containerd: reap child processes")
		}
	}
}

func notify(exits []osutils.Exit) {
	subLock.Lock()
	for _, exit := range exits {
		for _, sub := range subscriptions {
			sub.c <- exit
		}
	}
	subLock.Unlock()
}
