// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Three Sum Project
//
// 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.

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>

/**
 * Finds all unique triplets in the array that sum to zero
 * Uses two-pointer technique after sorting for O(n^2) time complexity
 * @param nums Vector of integers to search
 * @return Vector of vectors containing unique triplets that sum to zero
 */
std::vector<std::vector<int>> ThreeSum(std::vector<int>& nums) {
    std::vector<std::vector<int>> result;
    
    // Edge case: need at least 3 elements
    if (nums.size() < 3) {
        return result;
    }
    
    // Sort the array to enable two-pointer technique
    std::sort(nums.begin(), nums.end());
    
    for (int i = 0; i < nums.size() - 2; i++) {
        // Skip duplicate values for first element
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        
        int left = i + 1;
        int right = nums.size() - 1;
        
        while (left < right) {
            int current_sum = nums[i] + nums[left] + nums[right];
            
            if (current_sum == 0) {
                // Found a valid triplet
                result.push_back({nums[i], nums[left], nums[right]});
                
                // Skip duplicates for left pointer
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                
                // Skip duplicates for right pointer
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                
                left++;
                right--;
            } else if (current_sum < 0) {
                // Sum is too small, move left pointer right
                left++;
            } else {
                // Sum is too large, move right pointer left
                right--;
            }
        }
    }
    
    return result;
}

/**
 * Finds all unique triplets in the array that sum to target
 * Generalized version of three sum for any target value
 * @param nums Vector of integers to search
 * @param target Target sum to find
 * @return Vector of vectors containing unique triplets that sum to target
 */
std::vector<std::vector<int>> ThreeSumTarget(std::vector<int>& nums, int target) {
    std::vector<std::vector<int>> result;
    
    if (nums.size() < 3) {
        return result;
    }
    
    std::sort(nums.begin(), nums.end());
    
    for (int i = 0; i < nums.size() - 2; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        
        int left = i + 1;
        int right = nums.size() - 1;
        
        while (left < right) {
            int current_sum = nums[i] + nums[left] + nums[right];
            
            if (current_sum == target) {
                result.push_back({nums[i], nums[left], nums[right]});
                
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                
                left++;
                right--;
            } else if (current_sum < target) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}

/**
 * Prints a vector of triplets
 * @param triplets Vector of triplets to print
 */
void PrintTriplets(const std::vector<std::vector<int>>& triplets) {
    if (triplets.empty()) {
        std::cout << "No triplets found" << std::endl;
        return;
    }
    
    std::cout << "[";
    for (int i = 0; i < triplets.size(); i++) {
        std::cout << "[";
        for (int j = 0; j < triplets[i].size(); j++) {
            std::cout << triplets[i][j];
            if (j < triplets[i].size() - 1) std::cout << ", ";
        }
        std::cout << "]";
        if (i < triplets.size() - 1) std::cout << ", ";
    }
    std::cout << "]" << std::endl;
}

/**
 * Prints the array elements
 * @param nums Vector to print
 */
void PrintArray(const std::vector<int>& nums) {
    std::cout << "[";
    for (int i = 0; i < nums.size(); i++) {
        std::cout << nums[i];
        if (i < nums.size() - 1) std::cout << ", ";
    }
    std::cout << "]";
}

int main() {
    // Test case 1: Classic three sum (sum to zero)
    std::vector<int> nums1 = {-1, 0, 1, 2, -1, -4};
    std::cout << "Test Case 1 - Three Sum (target = 0):" << std::endl;
    std::cout << "Array: ";
    PrintArray(nums1);
    std::cout << std::endl;
    
    std::vector<std::vector<int>> result1 = ThreeSum(nums1);
    std::cout << "Result: ";
    PrintTriplets(result1);
    std::cout << std::endl;
    
    // Test case 2: Three sum with different target
    std::vector<int> nums2 = {1, 2, -2, -1};
    int target2 = 0;
    std::cout << "Test Case 2 - Three Sum Target (target = " << target2 << "):" << std::endl;
    std::cout << "Array: ";
    PrintArray(nums2);
    std::cout << std::endl;
    
    std::vector<std::vector<int>> result2 = ThreeSumTarget(nums2, target2);
    std::cout << "Result: ";
    PrintTriplets(result2);
    std::cout << std::endl;
    
    // Test case 3: No solution case
    std::vector<int> nums3 = {0, 1, 1};
    std::cout << "Test Case 3 - No Solution:" << std::endl;
    std::cout << "Array: ";
    PrintArray(nums3);
    std::cout << std::endl;
    
    std::vector<std::vector<int>> result3 = ThreeSum(nums3);
    std::cout << "Result: ";
    PrintTriplets(result3);
    std::cout << std::endl;
    
    // Test case 4: All zeros
    std::vector<int> nums4 = {0, 0, 0};
    std::cout << "Test Case 4 - All Zeros:" << std::endl;
    std::cout << "Array: ";
    PrintArray(nums4);
    std::cout << std::endl;
    
    std::vector<std::vector<int>> result4 = ThreeSum(nums4);
    std::cout << "Result: ";
    PrintTriplets(result4);
    std::cout << std::endl;
    
    // Test case 5: Larger array with multiple solutions
    std::vector<int> nums5 = {-1, 0, 1, 2, -1, -4, -2, -3, 3, 0, 4};
    std::cout << "Test Case 5 - Multiple Solutions:" << std::endl;
    std::cout << "Array: ";
    PrintArray(nums5);
    std::cout << std::endl;
    
    std::vector<std::vector<int>> result5 = ThreeSum(nums5);
    std::cout << "Result: ";
    PrintTriplets(result5);
    
    return 0;
}