// 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 "MedianFinder.h"
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <climits>
#include <cmath>
#include <random>
#include <sstream>
#include <iomanip>

// Static member initialization
const double MedianFinder::EPSILON = 1e-9;
int MedianTester::mTestCount = 0;
int MedianTester::mPassedTests = 0;
int MedianTester::mFailedTests = 0;

// MedianFinder Implementation
double MedianFinder::FindMedianSortedArrays(const std::vector<int>& nums1, 
                                          const std::vector<int>& nums2) {
    // Validate input arrays are sorted
    if (!IsArraySorted(nums1) || !IsArraySorted(nums2)) {
        throw std::invalid_argument("Input arrays must be sorted in non-decreasing order");
    }
    
    // Ensure nums1 is the smaller array for optimization
    if (nums1.size() > nums2.size()) {
        return FindMedianSortedArrays(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) {
        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 static_cast<double>(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::runtime_error("Unable to find median - this should not happen with valid sorted arrays");
}

double MedianFinder::FindMedianMergeApproach(const std::vector<int>& nums1,
                                           const std::vector<int>& nums2) {
    // Validate input arrays are sorted
    if (!IsArraySorted(nums1) || !IsArraySorted(nums2)) {
        throw std::invalid_argument("Input arrays must be sorted in non-decreasing order");
    }
    
    int m = static_cast<int>(nums1.size());
    int n = static_cast<int>(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 static_cast<double>(current);
    }
}

bool MedianFinder::IsArraySorted(const std::vector<int>& arr) {
    for (size_t i = 1; i < arr.size(); i++) {
        if (arr[i] < arr[i - 1]) {
            return false;
        }
    }
    return true;
}

std::string MedianFinder::GetComplexityInfo(bool use_binary_search) {
    std::ostringstream oss;
    if (use_binary_search) {
        oss << "Binary Search Approach:\n";
        oss << "  Time Complexity: O(log(min(m,n)))\n";
        oss << "  Space Complexity: O(1)\n";
        oss << "  Where m and n are the sizes of the input arrays";
    } else {
        oss << "Merge Approach:\n";
        oss << "  Time Complexity: O(m + n)\n";
        oss << "  Space Complexity: O(1)\n";
        oss << "  Where m and n are the sizes of the input arrays";
    }
    return oss.str();
}

// MedianTester Implementation
void MedianTester::RunTests() {
    std::cout << "=== Median of Two Sorted Arrays - Comprehensive Test Suite ===" << std::endl;
    std::cout << MedianFinder::GetComplexityInfo(true) << std::endl << std::endl;
    
    mTestCount = 0;
    mPassedTests = 0;
    mFailedTests = 0;
    
    // Test Case 1: Basic example
    RunSingleTest({1, 3}, {2}, 2.0, "Basic case with odd total length");
    
    // Test Case 2: Even total length
    RunSingleTest({1, 2}, {3, 4}, 2.5, "Even total length case");
    
    // Test Case 3: One empty array
    RunSingleTest({}, {1}, 1.0, "One empty array");
    RunSingleTest({2}, {}, 2.0, "Other empty array");
    
    // Test Case 4: Different sizes
    RunSingleTest({1, 3, 8}, {7, 9, 10, 11}, 8.0, "Different array sizes");
    
    // Test Case 5: No overlap
    RunSingleTest({1, 2, 3}, {4, 5, 6}, 3.5, "No overlap between arrays");
    
    // Test Case 6: Complete overlap
    RunSingleTest({1, 1, 1}, {1, 1, 1}, 1.0, "Complete overlap");
    
    // Test Case 7: Large difference in values
    RunSingleTest({-1000000}, {1000000}, 0.0, "Large value difference");
    
    // Test Case 8: Negative numbers
    RunSingleTest({-5, -3, -1}, {-4, -2, 0}, -2.5, "Negative numbers");
    
    // Test Case 9: Single elements
    RunSingleTest({1}, {2}, 1.5, "Single elements each");
    
    // Test Case 10: Identical arrays
    RunSingleTest({1, 2, 3}, {1, 2, 3}, 2.0, "Identical arrays");
    
    // Summary
    std::cout << "\n=== Test Summary ===" << std::endl;
    std::cout << "Total Tests: " << mTestCount << std::endl;
    std::cout << "Passed: " << mPassedTests << std::endl;
    std::cout << "Failed: " << mFailedTests << std::endl;
    std::cout << "Success Rate: " << std::fixed << std::setprecision(1) 
              << (100.0 * mPassedTests / mTestCount) << "%" << std::endl;
}

bool MedianTester::RunSingleTest(const std::vector<int>& nums1,
                               const std::vector<int>& nums2,
                               double expected,
                               const std::string& description) {
    mTestCount++;
    
    std::cout << "\nTest " << mTestCount << ": " << description << std::endl;
    std::cout << "Array 1: ";
    PrintArray(nums1);
    std::cout << "Array 2: ";
    PrintArray(nums2);
    
    bool test_passed = true;
    
    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) < MedianFinder::EPSILON;
        bool merge_correct = std::abs(result_merge - expected) < MedianFinder::EPSILON;
        
        if (binary_correct && merge_correct) {
            std::cout << "✓ PASSED" << std::endl;
            mPassedTests++;
        } else {
            std::cout << "✗ FAILED";
            if (!binary_correct) std::cout << " (Binary search incorrect)";
            if (!merge_correct) std::cout << " (Merge approach incorrect)";
            std::cout << std::endl;
            mFailedTests++;
            test_passed = false;
        }
    } catch (const std::exception& e) {
        std::cout << "✗ ERROR: " << e.what() << std::endl;
        mFailedTests++;
        test_passed = false;
    }
    
    return test_passed;
}

void MedianTester::RunRandomTests(int num_tests, int max_size, int max_value) {
    std::cout << "\n=== Random Test Generation ===" << std::endl;
    std::cout << "Generating " << num_tests << " random test cases..." << std::endl;
    
    int random_passed = 0;
    int random_failed = 0;
    
    for (int i = 0; i < num_tests; i++) {
        // Generate random array sizes
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> size_dist(0, max_size);
        
        int size1 = size_dist(gen);
        int size2 = size_dist(gen);
        
        std::vector<int> nums1 = GenerateRandomSortedArray(size1, max_value);
        std::vector<int> nums2 = GenerateRandomSortedArray(size2, max_value);
        
        double expected = CalculateExpectedMedian(nums1, nums2);
        
        try {
            double result = MedianFinder::FindMedianSortedArrays(nums1, nums2);
            
            if (std::abs(result - expected) < MedianFinder::EPSILON) {
                random_passed++;
            } else {
                random_failed++;
                std::cout << "Random test " << (i + 1) << " failed!" << std::endl;
                std::cout << "Expected: " << expected << ", Got: " << result << std::endl;
            }
        } catch (const std::exception& e) {
            random_failed++;
            std::cout << "Random test " << (i + 1) << " error: " << e.what() << std::endl;
        }
    }
    
    std::cout << "Random Tests - Passed: " << random_passed 
              << ", Failed: " << random_failed << std::endl;
}

void MedianTester::PrintArray(const std::vector<int>& arr, int max_elements) {
    std::cout << "[";
    int elements_to_show = std::min(static_cast<int>(arr.size()), max_elements);
    
    for (int i = 0; i < elements_to_show; i++) {
        std::cout << arr[i];
        if (i < elements_to_show - 1) std::cout << ", ";
    }
    
    if (static_cast<int>(arr.size()) > max_elements) {
        std::cout << ", ... (" << arr.size() << " total)";
    }
    
    std::cout << "]" << std::endl;
}

std::vector<int> MedianTester::GenerateRandomSortedArray(int size, int max_value) {
    if (size == 0) return {};
    
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> value_dist(-max_value, max_value);
    
    std::vector<int> arr(size);
    for (int i = 0; i < size; i++) {
        arr[i] = value_dist(gen);
    }
    
    std::sort(arr.begin(), arr.end());
    return arr;
}

double MedianTester::CalculateExpectedMedian(const std::vector<int>& nums1,
                                           const std::vector<int>& nums2) {
    // Brute force approach: merge and find median
    std::vector<int> merged;
    merged.reserve(nums1.size() + nums2.size());
    
    merged.insert(merged.end(), nums1.begin(), nums1.end());
    merged.insert(merged.end(), nums2.begin(), nums2.end());
    
    std::sort(merged.begin(), merged.end());
    
    int total_size = static_cast<int>(merged.size());
    if (total_size % 2 == 0) {
        return (merged[total_size / 2 - 1] + merged[total_size / 2]) / 2.0;
    } else {
        return static_cast<double>(merged[total_size / 2]);
    }
}