package main

import (
	"fmt"
	"sync"
)

func main() {
	DoAsyncOps()
	/*
	 * This will execute three operations in parallel. Here's a sample output:
	 * Starting async operations
	 * third
	 * second
	 * first
	 *
	 * Or in the case of an error:
	 * Starting async operations
	 * first
	 * Issue running async operations
	*/
}

func PrintStuff(stuff string) error {
	_, err := fmt.Println(stuff)

	//Uncomment the following code to test an error case

	/*if stuff == "first" {
		return fmt.Errorf("error happened here: %s", stuff)
	}*/
	return err
}

func DoAsyncOps() error {
	wg := new(sync.WaitGroup)
	wg.Add(3)
	errChannel := make(chan error, 1)
	finished := make(chan bool, 1)

	fmt.Println("Starting async operations")

	/*
	 * Run the operation you want to run in a goroutine which marks the operation as finished when the
	 * operation is done or pushes an error to the error channel
	 */
	go func() {
		defer wg.Done()
		if err := PrintStuff("first"); err != nil {
			errChannel <- err
		}
	}()
	go func() {
		defer wg.Done()
		if err := PrintStuff("second"); err != nil {
			errChannel <- err
		}
	}()
	go func() {
		defer wg.Done()
		if err := PrintStuff("third"); err != nil {
			errChannel <- err
		}
	}()
	/*
	 * Wait for the goroutines to finish in another goroutine which then indicates that we're done using
	 * the finished channel
	 */
	go func() {
		wg.Wait()
		close(finished)
	}()

	/*
	 * Quit if the `finished` channel is or if we receive an error through the error channel
	 */
	select {
	case <-finished:
	case err := <-errChannel:
		if err != nil {
			fmt.Println("Issue running async operations")
			return err
		}
	}

	return nil
}