// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Median Two Arrays 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.

#include <iostream>
#include <vector>
#include <algorithm>
#include <stdexcept>
#include <climits>

/**
 * Solution class for finding median of two sorted arrays
 * Implements optimal binary search algorithm with O(log(min(m,n))) complexity
 */
class MedianSolution {
public:
    /**
     * Finds the median of two sorted arrays using binary search approach.
     * 
     * Algorithm:
     * 1. Ensure first array is smaller for optimization
     * 2. Use binary search to find correct partition point
     * 3. Partition both arrays such that left half has same size as right half
     * 4. Ensure all elements in left half <= all elements in right half
     * 5. Calculate median based on total length (odd/even)
     *
     * @param nums1 First sorted array (const reference)
     * @param nums2 Second sorted array (const reference)
     * @return The median value as double
     * @throws std::invalid_argument if arrays are not sorted
     *
     * Time Complexity: O(log(min(m,n))) where m and n are array sizes
     * Space Complexity: O(1)
     */
    static double FindMedian(const std::vector<int>& nums1, 
                           const std::vector<int>& nums2) {
        // Validate input arrays are sorted
        if (!IsSorted(nums1) || !IsSorted(nums2)) {
            throw std::invalid_argument("Input arrays must be sorted");
        }
        
        // Ensure nums1 is the smaller array for optimization
        if (nums1.size() > nums2.size()) {
            return FindMedian(nums2, nums1);
        }
        
        int m = static_cast<int>(nums1.size());
        int n = static_cast<int>(nums2.size());
        int total_length = m + n;
        int half_length = (total_length + 1) / 2;
        
        int left = 0;
        int right = m;
        
        while (left <= right) {
            // Partition point for nums1
            int partition_x = (left + right) / 2;
            // Partition point for nums2
            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 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);
                }
            }
            // Adjust search range based on partition validity
            else if (max_left_x > min_right_y) {
                // Too far right in nums1, move left
                right = partition_x - 1;
            } else {
                // Too far left in nums1, move right
                left = partition_x + 1;
            }
        }
        
        // Should never reach here with valid sorted arrays
        throw std::runtime_error("Unable to find median - invalid input");
    }
    
    /**
     * Alternative merge-based approach for educational purposes.
     * Less efficient but easier to understand.
     *
     * @param nums1 First sorted array
     * @param nums2 Second sorted array
     * @return The median value as double
     *
     * Time Complexity: O(m + n)
     * Space Complexity: O(1) - only stores elements needed for median
     */
    static double FindMedianMerge(const std::vector<int>& nums1,
                                const std::vector<int>& nums2) {
        int m = static_cast<int>(nums1.size());
        int n = static_cast<int>(nums2.size());
        int total_length = m + n;
        int median_pos = total_length / 2;
        
        int i = 0, j = 0;
        int current = 0, previous = 0;
        
        // Merge arrays until we reach median position(s)
        for (int count = 0; count <= median_pos; ++count) {
            previous = current;
            
            if (i < m && (j >= n || nums1[i] <= nums2[j])) {
                current = nums1[i++];
            } else {
                current = nums2[j++];
            }
        }
        
        // Calculate median based on total length
        if (total_length % 2 == 0) {
            return (previous + current) / 2.0;
        } else {
            return current;
        }
    }

private:
    /**
     * Validates if an array is sorted in non-decreasing order.
     *
     * @param arr Array to validate
     * @return true if array is sorted, false otherwise
     */
    static bool IsSorted(const std::vector<int>& arr) {
        for (size_t i = 1; i < arr.size(); ++i) {
            if (arr[i] < arr[i - 1]) {
                return false;
            }
        }
        return true;
    }
};

/**
 * Test runner class for validating the median finding algorithms
 */
class MedianTester {
public:
    /**
     * Runs comprehensive tests on the median finding algorithms
     */
    static void RunTests() {
        std::cout << "=== Median of Two Sorted Arrays - Test Suite ===\n\n";
        
        int test_count = 0;
        int passed_tests = 0;
        
        // Test Case 1: Basic example
        {
            std::vector<int> nums1 = {1, 3};
            std::vector<int> nums2 = {2};
            double expected = 2.0;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Test Case 2: Even total length
        {
            std::vector<int> nums1 = {1, 2};
            std::vector<int> nums2 = {3, 4};
            double expected = 2.5;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Test Case 3: Empty first array
        {
            std::vector<int> nums1 = {};
            std::vector<int> nums2 = {1};
            double expected = 1.0;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Test Case 4: Single element arrays
        {
            std::vector<int> nums1 = {2};
            std::vector<int> nums2 = {1};
            double expected = 1.5;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Test Case 5: Large difference in sizes
        {
            std::vector<int> nums1 = {1};
            std::vector<int> nums2 = {2, 3, 4, 5, 6};
            double expected = 3.5;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Test Case 6: Negative numbers
        {
            std::vector<int> nums1 = {-5, -3, -1};
            std::vector<int> nums2 = {-2, 0, 2};
            double expected = -1.5;
            
            if (TestCase(nums1, nums2, expected, ++test_count)) {
                ++passed_tests;
            }
        }
        
        // Print test results
        std::cout << "\n=== Test Results ===\n";
        std::cout << "Total Tests: " << test_count << "\n";
        std::cout << "Passed: " << passed_tests << "\n";
        std::cout << "Failed: " << (test_count - passed_tests) << "\n";
        std::cout << "Success Rate: " << (100.0 * passed_tests / test_count) << "%\n";
    }

private:
    static bool TestCase(const std::vector<int>& nums1, 
                        const std::vector<int>& nums2,
                        double expected, 
                        int test_num) {
        try {
            double result_binary = MedianSolution::FindMedian(nums1, nums2);
            double result_merge = MedianSolution::FindMedianMerge(nums1, nums2);
            
            bool binary_correct = std::abs(result_binary - expected) < 1e-9;
            bool merge_correct = std::abs(result_merge - expected) < 1e-9;
            
            std::cout << "Test " << test_num << ": ";
            PrintArray(nums1);
            std::cout << " + ";
            PrintArray(nums2);
            std::cout << " => Expected: " << expected;
            std::cout << ", Binary: " << result_binary;
            std::cout << ", Merge: " << result_merge;
            
            if (binary_correct && merge_correct) {
                std::cout << " [PASS]\n";
                return true;
            } else {
                std::cout << " [FAIL]\n";
                return false;
            }
        } catch (const std::exception& e) {
            std::cout << "Test " << test_num << ": Exception - " << e.what() << " [FAIL]\n";
            return false;
        }
    }
    
    static void PrintArray(const std::vector<int>& arr) {
        std::cout << "[";
        for (size_t i = 0; i < arr.size(); ++i) {
            if (i > 0) std::cout << ",";
            std::cout << arr[i];
        }
        std::cout << "]";
    }
};

/**
 * Interactive demo for user input testing
 */
class InteractiveDemo {
public:
    static void Run() {
        std::cout << "\n=== Interactive Median Finder Demo ===\n";
        std::cout << "Enter two sorted arrays to find their median.\n\n";
        
        while (true) {
            try {
                std::vector<int> nums1 = GetArrayInput("first");
                std::vector<int> nums2 = GetArrayInput("second");
                
                double median = MedianSolution::FindMedian(nums1, nums2);
                
                std::cout << "\nResult:\n";
                std::cout << "Array 1: ";
                PrintArray(nums1);
                std::cout << "\nArray 2: ";
                PrintArray(nums2);
                std::cout << "\nMedian: " << median << "\n\n";
                
                std::cout << "Try another? (y/n): ";
                char choice;
                std::cin >> choice;
                if (choice != 'y' && choice != 'Y') {
                    break;
                }
                
            } catch (const std::exception& e) {
                std::cout << "Error: " << e.what() << "\n";
                std::cout << "Please try again with valid sorted arrays.\n\n";
            }
        }
    }

private:
    static std::vector<int> GetArrayInput(const std::string& array_name) {
        std::cout << "Enter " << array_name << " array size: ";
        int size;
        std::cin >> size;
        
        if (size < 0) {
            throw std::invalid_argument("Array size cannot be negative");
        }
        
        std::vector<int> arr(size);
        if (size > 0) {
            std::cout << "Enter " << size << " sorted elements: ";
            for (int i = 0; i < size; ++i) {
                std::cin >> arr[i];
            }
        }
        
        return arr;
    }
    
    static void PrintArray(const std::vector<int>& arr) {
        std::cout << "[";
        for (size_t i = 0; i < arr.size(); ++i) {
            if (i > 0) std::cout << ", ";
            std::cout << arr[i];
        }
        std::cout << "]";
    }
};

/**
 * Main function demonstrating the median finding algorithms
 */
int main() {
    std::cout << "Median of Two Sorted Arrays - C++ Implementation\n";
    std::cout << "================================================\n\n";
    
    try {
        // Run automated tests
        MedianTester::RunTests();
        
        // Run interactive demo
        std::cout << "\nWould you like to try the interactive demo? (y/n): ";
        char choice;
        std::cin >> choice;
        
        if (choice == 'y' || choice == 'Y') {
            InteractiveDemo::Run();
        }
        
        std::cout << "\nThank you for using the Median Finder!\n";
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}