// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Three Sum Problem Solution
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

// threeSumBruteForce solves the 3Sum problem using brute force approach
// Time Complexity: O(n³), Space Complexity: O(1)
// Given an array nums, return all unique triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
func threeSumBruteForce(nums []int) [][]int {
	var result [][]int
	n := len(nums)
	
	// Use a map to track unique triplets
	seen := make(map[string]bool)
	
	for i := 0; i < n-2; i++ {
		for j := i + 1; j < n-1; j++ {
			for k := j + 1; k < n; k++ {
				if nums[i]+nums[j]+nums[k] == 0 {
					// Sort the triplet to ensure uniqueness
					triplet := []int{nums[i], nums[j], nums[k]}
					sort.Ints(triplet)
					key := fmt.Sprintf("%d,%d,%d", triplet[0], triplet[1], triplet[2])
					
					if !seen[key] {
						result = append(result, triplet)
						seen[key] = true
					}
				}
			}
		}
	}
	
	return result
}

// threeSumOptimal solves the 3Sum problem using sorting and two pointers
// Time Complexity: O(n²), Space Complexity: O(1) excluding output
// This is the optimal solution for the 3Sum problem
func threeSumOptimal(nums []int) [][]int {
	var result [][]int
	n := len(nums)
	
	if n < 3 {
		return result
	}
	
	// Sort the array first
	sort.Ints(nums)
	
	for i := 0; i < n-2; i++ {
		// Skip duplicate values for the first element
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		
		left, right := i+1, n-1
		target := -nums[i] // We want nums[i] + nums[left] + nums[right] = 0
		
		for left < right {
			sum := nums[left] + nums[right]
			
			if sum == target {
				result = append(result, []int{nums[i], nums[left], nums[right]})
				
				// Skip duplicates for left pointer
				for left < right && nums[left] == nums[left+1] {
					left++
				}
				// Skip duplicates for right pointer
				for left < right && nums[right] == nums[right-1] {
					right--
				}
				
				left++
				right--
			} else if sum < target {
				left++
			} else {
				right--
			}
		}
	}
	
	return result
}

// threeSumTarget solves the 3Sum problem for a specific target (not just 0)
// Time Complexity: O(n²), Space Complexity: O(1) excluding output
func threeSumTarget(nums []int, target int) [][]int {
	var result [][]int
	n := len(nums)
	
	if n < 3 {
		return result
	}
	
	// Sort the array first
	sort.Ints(nums)
	
	for i := 0; i < n-2; i++ {
		// Skip duplicate values for the first element
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		
		left, right := i+1, n-1
		remainingTarget := target - nums[i]
		
		for left < right {
			sum := nums[left] + nums[right]
			
			if sum == remainingTarget {
				result = append(result, []int{nums[i], nums[left], nums[right]})
				
				// Skip duplicates for left pointer
				for left < right && nums[left] == nums[left+1] {
					left++
				}
				// Skip duplicates for right pointer
				for left < right && nums[right] == nums[right-1] {
					right--
				}
				
				left++
				right--
			} else if sum < remainingTarget {
				left++
			} else {
				right--
			}
		}
	}
	
	return result
}

// threeSumClosest finds the triplet whose sum is closest to the target
// Time Complexity: O(n²), Space Complexity: O(1)
func threeSumClosest(nums []int, target int) int {
	n := len(nums)
	if n < 3 {
		return 0
	}
	
	sort.Ints(nums)
	closestSum := nums[0] + nums[1] + nums[2]
	
	for i := 0; i < n-2; i++ {
		left, right := i+1, n-1
		
		for left < right {
			currentSum := nums[i] + nums[left] + nums[right]
			
			// Update closest sum if current sum is closer to target
			if abs(currentSum-target) < abs(closestSum-target) {
				closestSum = currentSum
			}
			
			if currentSum < target {
				left++
			} else if currentSum > target {
				right--
			} else {
				// Exact match found
				return currentSum
			}
		}
	}
	
	return closestSum
}

// abs returns the absolute value of an integer
func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

// benchmarkThreeSum compares the performance of different 3Sum implementations
func benchmarkThreeSum(nums []int) {
	fmt.Printf("Benchmarking 3Sum algorithms for array size %d:\n", len(nums))
	
	// Make copies for each test to avoid side effects
	nums1 := make([]int, len(nums))
	nums2 := make([]int, len(nums))
	copy(nums1, nums)
	copy(nums2, nums)
	
	// Brute Force approach (only for smaller arrays due to O(n³) complexity)
	if len(nums) <= 100 {
		start := time.Now()
		result1 := threeSumBruteForce(nums1)
		duration1 := time.Since(start)
		fmt.Printf("Brute Force: Found %d triplets (took %v)\n", len(result1), duration1)
	} else {
		fmt.Println("Brute Force: Skipped (array too large)")
	}
	
	// Optimal approach
	start := time.Now()
	result2 := threeSumOptimal(nums2)
	duration2 := time.Since(start)
	fmt.Printf("Optimal (Sort + Two Pointers): Found %d triplets (took %v)\n", len(result2), duration2)
	
	fmt.Println()
}

// parseIntArray parses a string representation of an array into []int
func parseIntArray(input string) ([]int, error) {
	input = strings.Trim(input, "[]")
	if input == "" {
		return []int{}, nil
	}
	
	parts := strings.Split(input, ",")
	nums := make([]int, len(parts))
	
	for i, part := range parts {
		part = strings.TrimSpace(part)
		num, err := strconv.Atoi(part)
		if err != nil {
			return nil, err
		}
		nums[i] = num
	}
	
	return nums, nil
}

// printTriplets prints triplets in a formatted way
func printTriplets(triplets [][]int) {
	if len(triplets) == 0 {
		fmt.Println("No triplets found.")
		return
	}
	
	fmt.Printf("Found %d unique triplet(s):\n", len(triplets))
	for i, triplet := range triplets {
		fmt.Printf("  %d: [%d, %d, %d] (sum = %d)\n", 
			i+1, triplet[0], triplet[1], triplet[2], 
			triplet[0]+triplet[1]+triplet[2])
	}
}

// printArray prints an array in a formatted way
func printArray(nums []int) {
	fmt.Print("[")
	for i, num := range nums {
		if i > 0 {
			fmt.Print(", ")
		}
		fmt.Print(num)
	}
	fmt.Print("]")
}

func main() {
	fmt.Println("=== 3Sum Problem Solver ===\n")
	
	// Example 1: Basic test case from LeetCode
	fmt.Println("Example 1: nums = [-1,0,1,2,-1,-4]")
	nums1 := []int{-1, 0, 1, 2, -1, -4}
	result1 := threeSumOptimal(nums1)
	fmt.Print("Array: ")
	printArray(nums1)
	fmt.Println()
	printTriplets(result1)
	fmt.Println()
	
	// Example 2: No solution case
	fmt.Println("Example 2: nums = [0,1,1]")
	nums2 := []int{0, 1, 1}
	result2 := threeSumOptimal(nums2)
	fmt.Print("Array: ")
	printArray(nums2)
	fmt.Println()
	printTriplets(result2)
	fmt.Println()
	
	// Example 3: All zeros
	fmt.Println("Example 3: nums = [0,0,0]")
	nums3 := []int{0, 0, 0}
	result3 := threeSumOptimal(nums3)
	fmt.Print("Array: ")
	printArray(nums3)
	fmt.Println()
	printTriplets(result3)
	fmt.Println()
	
	// Example 4: 3Sum with custom target
	fmt.Println("Example 4: 3Sum with target = 6")
	nums4 := []int{1, 2, 3, 4, 5, 6}
	target4 := 6
	result4 := threeSumTarget(nums4, target4)
	fmt.Print("Array: ")
	printArray(nums4)
	fmt.Printf(", Target: %d\n", target4)
	fmt.Printf("Found %d triplet(s) with sum = %d:\n", len(result4), target4)
	for i, triplet := range result4 {
		fmt.Printf("  %d: [%d, %d, %d]\n", i+1, triplet[0], triplet[1], triplet[2])
	}
	fmt.Println()
	
	// Example 5: 3Sum Closest
	fmt.Println("Example 5: 3Sum Closest")
	nums5 := []int{-1, 2, 1, -4}
	target5 := 1
	closest := threeSumClosest(nums5, target5)
	fmt.Print("Array: ")
	printArray(nums5)
	fmt.Printf(", Target: %d\n", target5)
	fmt.Printf("Closest sum to target: %d\n", closest)
	fmt.Println()
	
	// Example 6: Performance comparison
	fmt.Println("Performance Comparison:")
	largeNums := make([]int, 200)
	for i := 0; i < 200; i++ {
		largeNums[i] = i - 100 // Range from -100 to 99
	}
	benchmarkThreeSum(largeNums)
	
	// Example 7: Interactive mode
	fmt.Println("=== Interactive Mode ===")
	fmt.Println("Enter an array to solve 3Sum problem (or 'quit' to exit)")
	fmt.Println("Format: array as [1,2,3,4] - finds triplets that sum to 0")
	fmt.Println("Or use 'target' mode: array as [1,2,3,4] target 6")
	
	var input string
	for {
		fmt.Print("Enter array (e.g., [-1,0,1,2,-1,-4]) or 'quit': ")
		fmt.Scanln(&input)
		
		if input == "quit" || input == "exit" {
			fmt.Println("Goodbye!")
			break
		}
		
		// Check if input contains target
		parts := strings.Fields(input)
		var nums []int
		var target int
		var err error
		var useTarget bool
		
		if len(parts) >= 2 && parts[len(parts)-2] == "target" {
			// Target mode: array target value
			arrayStr := strings.Join(parts[:len(parts)-2], " ")
			nums, err = parseIntArray(arrayStr)
			if err != nil {
				fmt.Printf("Invalid array format: %v\n\n", err)
				continue
			}
			
			target, err = strconv.Atoi(parts[len(parts)-1])
			if err != nil {
				fmt.Printf("Invalid target: %v\n\n", err)
				continue
			}
			useTarget = true
		} else {
			// Standard mode: find triplets that sum to 0
			nums, err = parseIntArray(input)
			if err != nil {
				fmt.Printf("Invalid array format: %v\n\n", err)
				continue
			}
			target = 0
			useTarget = false
		}
		
		if len(nums) < 3 {
			fmt.Println("Array must have at least 3 elements.\n")
			continue
		}
		
		// Solve the problem
		var result [][]int
		if useTarget {
			result = threeSumTarget(nums, target)
			fmt.Print("Array: ")
			printArray(nums)
			fmt.Printf(", Target: %d\n", target)
		} else {
			result = threeSumOptimal(nums)
			fmt.Print("Array: ")
			printArray(nums)
			fmt.Println(" (finding triplets that sum to 0)")
		}
		
		printTriplets(result)
		fmt.Println()
	}
}