/*
 * Note: This program is written to demonstrate the ideas behind channels and synronization.
 *       It isn't written to be efficient code in term of memory use (I use copy by value, not by reference)
 *       or in terms of runtime (I use a waitgroup where reading from channels could provides synronization instead).
 *
 *       Using slice.Sort in mysort() followed by a 4 way merge in main() is particularly ugly.
 *
 *       Main() is split into 6 main steps:
 *       Step 1: Get the list of ints from the user
 *       Step 2: Split the list into four partitions of approximately equal size
 *       Step 3: Pass each quater to a goroutine to sort
 *       Step 4: Fetch results from channels
 *       Step 5: Merge the results. N.B. I'm taking "merge the 4 sorted subarrays into one large sorted array." literally here.
 *       Step 6: Print the output
 *
 *       Some shuffled sequences to test this on:
 *       2 5 3 6 1 7 10 4 8 9
 *       3 13 1 11 19 17 16 7 10 14 9 4 6 8 5 2 12 15 18 20
 *       24 18 5 15 4 12 28 30 3 27 29 14 20 6 23 21 10 8 19 9 17 16 7 25 11 2 22 1 26 13
 *
 */

package main

import (
	"fmt"
	"os"
	"bufio"
	"strings"
	"strconv"
	"sync"
	"slices"
)

func mysort(wg *sync.WaitGroup, c chan []int, goroutineNum int, numbers []int) {
	defer wg.Done()

	fmt.Printf("mysort%d got: %v\n", goroutineNum, numbers)

	slices.Sort(numbers)  // Using the sort in slices as this isn't an exercise in writing a sort()
	c <- numbers
	fmt.Printf("mysort%d returned: %v\n", goroutineNum, numbers)
}

func main() {
	numbers := make([]int, 0)

	// Step 1: Get the list of ints from the user
	scanner := bufio.NewScanner(os.Stdin)

	fmt.Printf("\nPlease enter a list of integers separated by spaces ( e.g. \"1 3 5 2\" ):\n")

	scanner.Scan()
	line := scanner.Text()
	fields := strings.Fields(line)

	for _, v := range(fields) {
		n, err := strconv.Atoi(v)
		if err != nil {
			fmt.Println("Ignoring string: " + v)
			continue
		}
		numbers = append(numbers, n)
	}

	// Step 2: Split the list into four partitions of approximately equal size
	quarter1 := numbers[:len(numbers)/4]
	quarter2 := numbers[len(numbers)/4 : len(numbers)/2]
	quarter3 := numbers[len(numbers)/2 : len(numbers)/4+len(numbers)/2]
	quarter4 := numbers[len(numbers)/4+len(numbers)/2:]

	// Step 3: Pass each quater to a goroutine to sort
	// N.B. the waitgroups are not needed for syncronization here as reading from unbuffered channels in Step 4
	// would perform the same function. I'm using buffered channels just so the waitgroup is needed.
        // This is to demonstrate the idea of waitgroups.
	c1 := make(chan []int, 1)
	c2 := make(chan []int, 1)
	c3 := make(chan []int, 1)
	c4 := make(chan []int, 1)
	var wg sync.WaitGroup
	wg.Add(4)
	go mysort(&wg, c1, 1, quarter1)
	go mysort(&wg, c2, 2, quarter2)
	go mysort(&wg, c3, 3, quarter3)
	go mysort(&wg, c4, 4, quarter4)
	wg.Wait()

	// Step 4: Fetch results from channels
	nums1 := <- c1
	nums2 := <- c2
	nums3 := <- c3
	nums4 := <- c4

	// Step 5: Merge the results. N.B. I'm taking "merge the 4 sorted subarrays into one large sorted array." literally here.
	//         It would be far easier, and more readable, to merge nums1 and nums2 into nums5, merge nums3 and nums4 into nums6,
	//         and then merge nums5 and nums6 into the final output.
	sorted := []int{}
	lowest_list := 0
	lowest_number := 0
	for len(sorted) < len(numbers) {
	// Step 5a: Find which of nums1 to nums4 starts with the lowest number
		if len(nums1) > 0 {
			lowest_list = 1;
			lowest_number = nums1[0];
		}
		if len(nums2) > 0 {
			if (len(nums1) == 0) || nums2[0] < lowest_number {
				lowest_list = 2
				lowest_number = nums2[0]
			}
		}
		if len(nums3) > 0 {
			if (len(nums1) == 0 && len(nums2) == 0) ||  nums3[0] < lowest_number {
				lowest_list = 3
				lowest_number = nums3[0]
			}
		}
		if len(nums4) > 0 {
			if (len(nums1) == 0 && len(nums2) == 0 && len(nums3) == 0) || nums4[0] < lowest_number {
				lowest_list = 4
				lowest_number = nums4[0]
			}
		}

	// Step 5b: Update sorted with the lowest found and offset the []int it was found in
		sorted = append(sorted, lowest_number)
		switch lowest_list {
		case 1:
			nums1 = nums1[1:]
		case 2:
			nums2 = nums2[1:]
		case 3:
			nums3 = nums3[1:]
		case 4:
			nums4 = nums4[1:]
		}
	}

	// Step 6: Print the output

	fmt.Println("Final merged output:\n")
	fmt.Println(sorted)
}