package cmdext

import (
	"bufio"
	"gogs.mikescher.com/BlackForestBytes/goext/syncext"
	"io"
	"sync"
)

type pipeReader struct {
	lineBufferSize *int
	stdout         io.ReadCloser
	stderr         io.ReadCloser
}

// Read ready stdout and stdin until finished
// also splits both pipes into lines and calld the listener
func (pr *pipeReader) Read(listener []CommandListener) (string, string, string, error) {
	type combevt struct {
		line string
		stop bool
	}

	errch := make(chan error, 8)

	wg := sync.WaitGroup{}

	// [1] read raw stdout

	wg.Add(1)
	stdoutBufferReader, stdoutBufferWriter := io.Pipe()
	stdout := ""
	go func() {
		buf := make([]byte, 128)
		for {
			n, err := pr.stdout.Read(buf)
			if n > 0 {
				txt := string(buf[:n])
				stdout += txt
				_, _ = stdoutBufferWriter.Write(buf[:n])
				for _, lstr := range listener {
					lstr.ReadRawStdout(buf[:n])
				}
			}
			if err == io.EOF {
				break
			}
			if err != nil {
				errch <- err
				break
			}
		}
		_ = stdoutBufferWriter.Close()
		wg.Done()
	}()

	// [2] read raw stderr

	wg.Add(1)
	stderrBufferReader, stderrBufferWriter := io.Pipe()
	stderr := ""
	go func() {
		buf := make([]byte, 128)
		for {
			n, err := pr.stderr.Read(buf)

			if n > 0 {
				txt := string(buf[:n])
				stderr += txt
				_, _ = stderrBufferWriter.Write(buf[:n])
				for _, lstr := range listener {
					lstr.ReadRawStderr(buf[:n])
				}
			}
			if err == io.EOF {
				break
			}
			if err != nil {
				errch <- err
				break
			}
		}
		_ = stderrBufferWriter.Close()
		wg.Done()
	}()

	combch := make(chan combevt, 32)

	// [3] collect stdout line-by-line

	wg.Add(1)
	go func() {
		scanner := bufio.NewScanner(stdoutBufferReader)
		if pr.lineBufferSize != nil {
			scanner.Buffer([]byte{}, *pr.lineBufferSize)
		}
		for scanner.Scan() {
			txt := scanner.Text()
			for _, lstr := range listener {
				lstr.ReadStdoutLine(txt)
			}
			combch <- combevt{txt, false}
		}
		if err := scanner.Err(); err != nil {
			errch <- err
		}
		combch <- combevt{"", true}
		wg.Done()
	}()

	// [4] collect stderr line-by-line

	wg.Add(1)
	go func() {
		scanner := bufio.NewScanner(stderrBufferReader)
		if pr.lineBufferSize != nil {
			scanner.Buffer([]byte{}, *pr.lineBufferSize)
		}
		for scanner.Scan() {
			txt := scanner.Text()
			for _, lstr := range listener {
				lstr.ReadStderrLine(txt)
			}
			combch <- combevt{txt, false}
		}
		if err := scanner.Err(); err != nil {
			errch <- err
		}
		combch <- combevt{"", true}
		wg.Done()
	}()

	// [5] combine stdcombined

	wg.Add(1)
	stdcombined := ""
	go func() {
		stopctr := 0
		for stopctr < 2 {
			vvv := <-combch
			if vvv.stop {
				stopctr++
			} else {
				stdcombined += vvv.line + "\n" // this comes from bufio.Scanner and has no newlines...
			}
		}
		wg.Done()
	}()

	// wait for all (5) goroutines to finish
	wg.Wait()

	if err, ok := syncext.ReadNonBlocking(errch); ok {
		return "", "", "", err
	}

	return stdout, stderr, stdcombined, nil
}