// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Median of Two Sorted Arrays Example
//
// 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 <stdexcept>
#include <climits>

/**
 * MedianFinder class provides efficient algorithms to find the median
 * of two sorted arrays using binary search approach.
 */
class MedianFinder {
public:
    /**
     * Finds the median of two sorted arrays using binary search.
     * Time Complexity: O(log(min(m,n))) where m and n are array sizes
     * Space Complexity: O(1)
     * 
     * @param nums1 First sorted array
     * @param nums2 Second sorted array
     * @return The median value as double
     */
    static double FindMedianSortedArrays(const std::vector<int>& nums1, 
                                       const std::vector<int>& nums2) {
        // Ensure nums1 is the smaller array for optimization
        if (nums1.size() > nums2.size()) {
            return FindMedianSortedArrays(nums2, nums1);
        }
        
        int m = nums1.size();
        int n = nums2.size();
        int total_length = m + n;
        int half_length = (total_length + 1) / 2;
        
        int left = 0;
        int right = m;
        
        while (left <= right) {
            int partition_x = (left + right) / 2;
            int partition_y = half_length - partition_x;
            
            // Handle edge cases for partition boundaries
            int max_left_x = (partition_x == 0) ? INT_MIN : nums1[partition_x - 1];
            int min_right_x = (partition_x == m) ? INT_MAX : nums1[partition_x];
            
            int max_left_y = (partition_y == 0) ? INT_MIN : nums2[partition_y - 1];
            int min_right_y = (partition_y == n) ? INT_MAX : nums2[partition_y];
            
            // Check if we found the correct partition
            if (max_left_x <= min_right_y && max_left_y <= min_right_x) {
                // Found the correct partition
                if (total_length % 2 == 0) {
                    // Even total length - median is average of two middle elements
                    return (std::max(max_left_x, max_left_y) + 
                           std::min(min_right_x, min_right_y)) / 2.0;
                } else {
                    // Odd total length - median is the maximum of left partition
                    return std::max(max_left_x, max_left_y);
                }
            }
            else if (max_left_x > min_right_y) {
                // Move partition_x to the left
                right = partition_x - 1;
            }
            else {
                // Move partition_x to the right
                left = partition_x + 1;
            }
        }
        
        throw std::invalid_argument("Input arrays are not sorted properly");
    }
    
    /**
     * Alternative approach using merge technique (less efficient but easier to understand)
     * Time Complexity: O(m + n)
     * Space Complexity: O(1)
     */
    static double FindMedianMergeApproach(const std::vector<int>& nums1,
                                        const std::vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int total_length = m + n;
        int target_index = total_length / 2;
        
        int i = 0, j = 0;
        int current = 0, previous = 0;
        
        // Merge arrays until we reach the median position(s)
        for (int count = 0; count <= target_index; count++) {
            previous = current;
            
            if (i < m && (j >= n || nums1[i] <= nums2[j])) {
                current = nums1[i];
                i++;
            } else {
                current = nums2[j];
                j++;
            }
        }
        
        if (total_length % 2 == 0) {
            return (previous + current) / 2.0;
        } else {
            return current;
        }
    }
    
private:
    // Private constructor to prevent instantiation (utility class)
    MedianFinder() = default;
};

/**
 * Utility class for testing and demonstration
 */
class MedianTester {
public:
    /**
     * Runs comprehensive tests for the median finding algorithms
     */
    static void RunTests() {
        std::cout << "=== Median of Two Sorted Arrays - Test Cases ===" << std::endl;
        
        // Test Case 1: Basic example
        TestCase({1, 3}, {2}, 2.0, "Basic case with odd total length");
        
        // Test Case 2: Even total length
        TestCase({1, 2}, {3, 4}, 2.5, "Even total length case");
        
        // Test Case 3: One empty array
        TestCase({}, {1}, 1.0, "One empty array");
        
        // Test Case 4: Different sizes
        TestCase({1, 3, 8}, {7, 9, 10, 11}, 8.0, "Different array sizes");
        
        // Test Case 5: No overlap
        TestCase({1, 2, 3}, {4, 5, 6}, 3.5, "No overlap between arrays");
        
        // Test Case 6: Complete overlap
        TestCase({1, 1, 1}, {1, 1, 1}, 1.0, "Complete overlap");
        
        // Test Case 7: Large difference in values
        TestCase({-1000000}, {1000000}, 0.0, "Large value difference");
        
        std::cout << "=== All tests completed ===" << std::endl;
    }
    
private:
    static void TestCase(const std::vector<int>& nums1, 
                        const std::vector<int>& nums2,
                        double expected,
                        const std::string& description) {
        std::cout << "\nTest: " << description << std::endl;
        std::cout << "Array 1: ";
        PrintArray(nums1);
        std::cout << "Array 2: ";
        PrintArray(nums2);
        
        try {
            double result_binary = MedianFinder::FindMedianSortedArrays(nums1, nums2);
            double result_merge = MedianFinder::FindMedianMergeApproach(nums1, nums2);
            
            std::cout << "Expected: " << expected << std::endl;
            std::cout << "Binary Search Result: " << result_binary << std::endl;
            std::cout << "Merge Approach Result: " << result_merge << std::endl;
            
            bool binary_correct = std::abs(result_binary - expected) < 1e-9;
            bool merge_correct = std::abs(result_merge - expected) < 1e-9;
            
            if (binary_correct && merge_correct) {
                std::cout << "✓ PASSED" << std::endl;
            } else {
                std::cout << "✗ FAILED" << std::endl;
            }
        } catch (const std::exception& e) {
            std::cout << "✗ ERROR: " << e.what() << std::endl;
        }
    }
    
    static void PrintArray(const std::vector<int>& arr) {
        std::cout << "[";
        for (size_t i = 0; i < arr.size(); i++) {
            std::cout << arr[i];
            if (i < arr.size() - 1) std::cout << ", ";
        }
        std::cout << "]" << std::endl;
    }
};

/**
 * Interactive demo function
 */
void RunInteractiveDemo() {
    std::cout << "\n=== Interactive Median Finder Demo ===" << std::endl;
    std::cout << "Enter two sorted arrays to find their median." << std::endl;
    
    // Get first array
    std::cout << "\nEnter size of first array: ";
    int size1;
    std::cin >> size1;
    
    std::vector<int> nums1(size1);
    if (size1 > 0) {
        std::cout << "Enter " << size1 << " sorted elements for first array: ";
        for (int i = 0; i < size1; i++) {
            std::cin >> nums1[i];
        }
    }
    
    // Get second array
    std::cout << "Enter size of second array: ";
    int size2;
    std::cin >> size2;
    
    std::vector<int> nums2(size2);
    if (size2 > 0) {
        std::cout << "Enter " << size2 << " sorted elements for second array: ";
        for (int i = 0; i < size2; i++) {
            std::cin >> nums2[i];
        }
    }
    
    try {
        double median = MedianFinder::FindMedianSortedArrays(nums1, nums2);
        std::cout << "\nMedian of the two sorted arrays: " << median << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }
}

/**
 * Main function demonstrating the median finding algorithms
 */
int main() {
    std::cout << "Median of Two Sorted Arrays - C++ Implementation" << std::endl;
    std::cout << "=================================================" << std::endl;
    
    // Run automated tests
    MedianTester::RunTests();
    
    // Ask user if they want to try interactive demo
    std::cout << "\nWould you like to try the interactive demo? (y/n): ";
    char choice;
    std::cin >> choice;
    
    if (choice == 'y' || choice == 'Y') {
        RunInteractiveDemo();
    }
    
    std::cout << "\nProgram completed successfully!" << std::endl;
    return 0;
}