package cio

import (
	"fmt"
	"io"
	"net"
	"sync"

	winio "github.com/Microsoft/go-winio"
	"github.com/containerd/containerd/log"
	"github.com/pkg/errors"
)

const pipeRoot = `\\.\pipe`

// NewFifos returns a new set of fifos for the task
func NewFifos(id string) (*FIFOSet, error) {
	return &FIFOSet{
		In:  fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
		Out: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
		Err: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id),
	}, nil
}

func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) {
	var (
		wg  sync.WaitGroup
		set []io.Closer
	)

	if fifos.In != "" {
		l, err := winio.ListenPipe(fifos.In, nil)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.In)
		}
		defer func(l net.Listener) {
			if err != nil {
				l.Close()
			}
		}(l)
		set = append(set, l)

		go func() {
			c, err := l.Accept()
			if err != nil {
				log.L.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.In)
				return
			}
			io.Copy(c, ioset.in)
			c.Close()
			l.Close()
		}()
	}

	if fifos.Out != "" {
		l, err := winio.ListenPipe(fifos.Out, nil)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Out)
		}
		defer func(l net.Listener) {
			if err != nil {
				l.Close()
			}
		}(l)
		set = append(set, l)

		wg.Add(1)
		go func() {
			defer wg.Done()
			c, err := l.Accept()
			if err != nil {
				log.L.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Out)
				return
			}
			io.Copy(ioset.out, c)
			c.Close()
			l.Close()
		}()
	}

	if !tty && fifos.Err != "" {
		l, err := winio.ListenPipe(fifos.Err, nil)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Err)
		}
		defer func(l net.Listener) {
			if err != nil {
				l.Close()
			}
		}(l)
		set = append(set, l)

		wg.Add(1)
		go func() {
			defer wg.Done()
			c, err := l.Accept()
			if err != nil {
				log.L.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Err)
				return
			}
			io.Copy(ioset.err, c)
			c.Close()
			l.Close()
		}()
	}

	return &wgCloser{
		wg:  &wg,
		dir: fifos.Dir,
		set: set,
		cancel: func() {
			for _, l := range set {
				l.Close()
			}
		},
	}, nil
}
