//! Memory allocation interface validation tests
//!
//! These tests validate that the unified memory allocation
//! interface works correctly.

use jvmrs::memory::{
    MemoryAllocator, 
    ArenaAllocator, 
    TlabAllocator, 
    MemoryType, 
    PrimitiveArrayType,
    MemoryStats
};

#[test]
fn test_arena_allocator_basic() {
    let mut allocator = ArenaAllocator::new(1024);
    
    // Test basic object allocation
    let obj1 = allocator.allocate_object("TestClass").unwrap();
    let obj2 = allocator.allocate_object("TestClass").unwrap();
    
    // Objects should have different addresses
    assert_ne!(obj1, obj2);
    
    // Test basic stats
    let stats = allocator.get_stats();
    assert_eq!(stats.allocation_count, 2);
    assert!(stats.total_allocated >= 32); // At least 2 objects * 16 bytes each
    assert_eq!(stats.deallocation_count, 0);
}

#[test]
fn test_arena_array_allocation() {
    let mut allocator = ArenaAllocator::new(2048);
    
    // Test different primitive array types
    let int_array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Int.clone()),
        10
    ).unwrap();
    
    let byte_array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Byte.clone()),
        20
    ).unwrap();
    
    let long_array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Long.clone()),
        5
    ).unwrap();
    
    // Arrays should have different addresses
    assert_ne!(int_array, byte_array);
    assert_ne!(int_array, long_array);
    assert_ne!(byte_array, long_array);
    
    // Test stats
    let stats = allocator.get_stats();
    assert_eq!(stats.allocation_count, 3); // 3 arrays allocated
    assert!(stats.total_allocated > 0);
}

#[test]
fn test_arena_cleanup() {
    let mut allocator = ArenaAllocator::new(1024);
    
    // Allocate some objects
    let _obj1 = allocator.allocate_object("Test1").unwrap();
    let _obj2 = allocator.allocate_object("Test2").unwrap();
    
    let before_cleanup = allocator.get_stats();
    assert_eq!(before_cleanup.allocation_count, 2);
    assert_eq!(before_cleanup.deallocation_count, 0);
    
    // Cleanup
    allocator.cleanup().unwrap();
    
    let after_cleanup = allocator.get_stats();
    assert_eq!(after_cleanup.allocation_count, 2); // Should still track total allocations
    assert_eq!(after_cleanup.deallocation_count, 1); // But one cleanup operation
}

#[test]
fn test_arena_resize() {
    let mut allocator = ArenaAllocator::new(256); // Small arena
    
    // Allocate objects until we need to resize
    let mut objects = Vec::new();
    for i in 0..20 {
        // Allocate different sized objects
        if i % 2 == 0 {
            let obj = allocator.allocate_object(format!("Object{}", i).as_str()).unwrap();
            objects.push(obj);
        } else {
            let arr = allocator.allocate_array(
                MemoryType::PrimitiveArray(PrimitiveArrayType::Int.clone()),
                5
            ).unwrap();
            objects.push(arr);
        }
    }
    
    // Should have allocated all objects without panic
    assert_eq!(objects.len(), 20);
    
    let stats = allocator.get_stats();
    assert_eq!(stats.allocation_count, 20);
    assert!(stats.total_allocated > 0);
}

#[test]
fn test_tlab_allocator_basic() {
    let mut allocator = TlabAllocator::new(512);
    
    // Test object allocation
    let obj1 = allocator.allocate_object("TestObject").unwrap();
    let obj2 = allocator.allocate_object("TestObject").unwrap();
    
    assert_ne!(obj1, obj2);
    
    // Test array allocation
    let array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Float),
        8
    ).unwrap();
    assert_ne!(array, 0);
    
    let stats = allocator.get_stats();
    assert_eq!(stats.allocation_count, 3); // 2 objects + 1 array
}

#[test]
fn test_tlab_cleanup() {
    let mut allocator = TlabAllocator::new(1024);
    
    // Allocate some objects
    let _obj1 = allocator.allocate_object("Test1").unwrap();
    let _obj2 = allocator.allocate_object("Test2").unwrap();
    
    let before_cleanup = allocator.get_stats();
    assert_eq!(before_cleanup.allocation_count, 2);
    
    // Cleanup should reset TLAB usage
    allocator.cleanup().unwrap();
    
    let after_cleanup = allocator.get_stats();
    assert_eq!(after_cleanup.allocation_count, 2); // Total allocations preserved
    assert_eq!(after_cleanup.deallocation_count, 1); // Cleanup count incremented
}

#[test]
fn test_memory_type_enum() {
    // Test all primitive array types
    let types = [
        PrimitiveArrayType::Int,
        PrimitiveArrayType::Byte,
        PrimitiveArrayType::Short,
        PrimitiveArrayType::Char,
        PrimitiveArrayType::Long,
        PrimitiveArrayType::Float,
        PrimitiveArrayType::Double,
        PrimitiveArrayType::Boolean,
    ];
    
    for array_type in types.iter() {
        let memory_type = MemoryType::PrimitiveArray(array_type.clone());
        assert!(matches!(memory_type, MemoryType::PrimitiveArray(_)));
    }
    
    // Test object array type
    let obj_array_type = MemoryType::ObjectArray("java/lang/String".to_string());
    assert!(matches!(obj_array_type, MemoryType::ObjectArray(_)));
    
    // Test multi-dimensional array type
    let multi_type = MemoryType::MultiArray("I".to_string(), vec![2, 3, 4]);
    assert!(matches!(multi_type, MemoryType::MultiArray(_, _)));
}

#[test]
fn test_memory_stats_consistency() {
    let mut allocator = ArenaAllocator::new(1024);
    
    let initial_stats = allocator.get_stats();
    assert_eq!(initial_stats.allocation_count, 0);
    assert_eq!(initial_stats.deallocation_count, 0);
    assert_eq!(initial_stats.current_usage, 0);
    assert_eq!(initial_stats.total_allocated, 1024); // Constructor pre-sets total_allocated
    
    // Allocate some objects
    for i in 0..5 {
        let _ = allocator.allocate_object(format!("Obj{}", i).as_str()).unwrap();
    }
    
    let after_allocation = allocator.get_stats();
    assert_eq!(after_allocation.allocation_count, 5);
    assert_eq!(after_allocation.deallocation_count, 0);
    assert!(after_allocation.current_usage > 0);
    assert!(after_allocation.total_allocated > 0);
    
    // Cleanup
    allocator.cleanup().unwrap();
    
    let after_cleanup = allocator.get_stats();
    assert_eq!(after_cleanup.allocation_count, 5); // Total allocations preserved
    assert_eq!(after_cleanup.deallocation_count, 1);
    assert_eq!(after_cleanup.current_usage, 0); // Usage reset
    assert_eq!(after_cleanup.total_allocated, 1024); // Total allocated remains
}

#[test]
fn test_allocator_capacity() {
    let mut allocator = ArenaAllocator::new(1000);
    
    // Test can_allocate
    assert!(allocator.can_allocate(100));
    assert!(allocator.can_allocate(500));
    assert!(!allocator.can_allocate(2000)); // Larger than arena size
    
    // Allocate objects until near capacity (62 * 16 = 992 bytes in 1000-byte arena)
    for i in 0..62 {
        let _ = allocator.allocate_object("Obj").unwrap();
    }
    
    // Should now not be able to allocate 500 bytes
    assert!(!allocator.can_allocate(500));
    
    // Cleanup should reset capacity
    allocator.cleanup().unwrap();
    assert!(allocator.can_allocate(500));
}

#[test]
fn test_array_size_validation() {
    let mut allocator = ArenaAllocator::new(1024);
    
    // Test negative array length
    let result = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Int),
        -1
    );
    assert!(result.is_err());
    
    // Test zero-length array (should be allowed)
    let zero_array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Int),
        0
    );
    assert!(zero_array.is_ok());
    
    // Test large array (might fail if too large)
    let large_array = allocator.allocate_array(
        MemoryType::PrimitiveArray(PrimitiveArrayType::Int),
        100000
    );
    // This might succeed or fail depending on memory, but shouldn't crash
    assert!(large_array.is_ok() || large_array.is_err());
}

#[test]
fn test_allocator_memory_safety() {
    let mut allocator = ArenaAllocator::new(512);
    
    // Allocate objects
    let obj1 = allocator.allocate_object("SafetyTest1").unwrap();
    let obj2 = allocator.allocate_object("SafetyTest2").unwrap();
    
    // Get stats
    let stats1 = allocator.get_stats();
    assert_eq!(stats1.allocation_count, 2);
    
    // Cleanup
    allocator.cleanup().unwrap();
    
    // Get stats after cleanup
    let stats2 = allocator.get_stats();
    assert_eq!(stats2.allocation_count, 2); // Total allocations preserved
    assert_eq!(stats2.deallocation_count, 1);
    assert_eq!(stats2.current_usage, 0);
    
    // Allocate more objects after cleanup
    let obj3 = allocator.allocate_object("SafetyTest3").unwrap();
    // Addresses may be reused after cleanup; just verify allocation succeeds
    assert_ne!(obj3, 0);
    
    let stats3 = allocator.get_stats();
    assert_eq!(stats3.allocation_count, 3);
}

#[test]
fn test_primitive_array_element_sizes() {
    let mut allocator = ArenaAllocator::new(1024);
    
    // Test arrays of different sizes
let test_cases = vec![
        (PrimitiveArrayType::Int, 4),
        (PrimitiveArrayType::Byte, 1),
        (PrimitiveArrayType::Short, 2),
        (PrimitiveArrayType::Char, 2),
        (PrimitiveArrayType::Long, 8),
        (PrimitiveArrayType::Float, 4),
        (PrimitiveArrayType::Double, 8),
        (PrimitiveArrayType::Boolean, 1),
    ];
    
    for (array_type, _expected_element_size) in &test_cases {
        let array = allocator.allocate_array(
            MemoryType::PrimitiveArray(array_type.clone()),
            10
        ).unwrap();
        assert_ne!(array, 0);
        
        // Check that allocation succeeded and stats updated
        let stats = allocator.get_stats();
        assert!(stats.total_allocated > 0);
    }
    
    let final_stats = allocator.get_stats();
    assert_eq!(final_stats.allocation_count, test_cases.len() as u64);
}

#[test]
fn test_allocator_drop_safety() {
    // Test that allocators can be dropped safely
    {
        let mut _allocator = ArenaAllocator::new(256);
        let _obj = _allocator.allocate_object("Test").unwrap();
    } // Allocator goes out of scope here
    
    // Should not panic
    let mut _another = ArenaAllocator::new(512);
    let _result = _another.allocate_object("AnotherTest");
    
    // Test TLAB drop safety too
    {
        let mut _tlab = TlabAllocator::new(128);
        let _obj = _tlab.allocate_object("TlabTest").unwrap();
    }
    
    // Should not panic
    let _final_tlab = TlabAllocator::new(256);
}

#[test]
fn test_memory_type_debug_format() {
    // Test that MemoryType implements Debug trait
    let debug_cases = vec![
        format!("{:?}", MemoryType::PrimitiveArray(PrimitiveArrayType::Int)),
        format!("{:?}", MemoryType::ObjectArray("java/lang/String".to_string())),
        format!("{:?}", MemoryType::MultiArray("I".to_string(), vec![2, 3])),
    ];
    
    for debug_str in debug_cases {
        assert!(!debug_str.is_empty());
        assert!(debug_str.contains("PrimitiveArray") || 
                debug_str.contains("ObjectArray") || 
                debug_str.contains("MultiArray"));
    }
}