use std::alloc::GlobalAlloc;
use std::alloc::Layout;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;

use crate::AllocProfile;
use crate::AllocProfileGuard;
use crate::AllocProfiler;
// use crate::histogram::AtomicLogHistogram;

#[derive(Debug, Default)]
struct AtomicLogHistogram {}

impl AtomicLogHistogram {
    const fn new() -> AtomicLogHistogram {
        AtomicLogHistogram {}
    }
}

#[derive(Debug, Default)]
pub(crate) struct AtomicProfile {
    alloc: AtomicLogHistogram,
    alloc_calls: AtomicU64,
    dealloc_calls: AtomicU64,
    alloc_zeroed_calls: AtomicU64,
    realloc_calls: AtomicU64,
}

impl AtomicProfile {
    pub(crate) const fn new() -> Self {
        Self {
            alloc: AtomicLogHistogram::new(),
            alloc_calls: AtomicU64::new(0),
            dealloc_calls: AtomicU64::new(0),
            alloc_zeroed_calls: AtomicU64::new(0),
            realloc_calls: AtomicU64::new(0),
        }
    }

    fn load(&self) -> AllocProfile {
        AllocProfile {
            iterations_count: 0,
            alloc_calls: self.alloc_calls.load(Ordering::Relaxed),
            dealloc_calls: self.dealloc_calls.load(Ordering::Relaxed),
            alloc_zeroed_calls: self.alloc_zeroed_calls.load(Ordering::Relaxed),
            realloc_calls: self.realloc_calls.load(Ordering::Relaxed),
        }
    }

    // fn store(&self, profile: &AllocProfile) {
    //     let &AllocProfile {
    //         iterations_count,
    //         alloc_calls,
    //         dealloc_calls,
    //         alloc_zeroed_calls,
    //         realloc_calls,
    //     } = profile;
    //     self.iterations_count
    //         .store(iterations_count, Ordering::Relaxed);
    //     self.alloc_calls.store(alloc_calls, Ordering::Relaxed);
    //     self.dealloc_calls.store(dealloc_calls, Ordering::Relaxed);
    //     self.alloc_zeroed_calls
    //         .store(alloc_zeroed_calls, Ordering::Relaxed);
    //     self.realloc_calls.store(realloc_calls, Ordering::Release);
    // }

    // fn fetch_add(&self, profile: &AllocProfile) {
    //     let &AllocProfile {
    //         iterations_count,
    //         alloc_calls,
    //         dealloc_calls,
    //         alloc_zeroed_calls,
    //         realloc_calls,
    //     } = profile;
    //     self.iterations_count
    //         .fetch_add(iterations_count, Ordering::Acquire);
    //     self.alloc_calls.fetch_add(alloc_calls, Ordering::Relaxed);
    //     self.dealloc_calls
    //         .fetch_add(dealloc_calls, Ordering::Relaxed);
    //     self.alloc_zeroed_calls
    //         .fetch_add(alloc_zeroed_calls, Ordering::Relaxed);
    //     self.realloc_calls
    //         .fetch_add(realloc_calls, Ordering::Release);
    // }
}

pub struct GlobalAllocProfiler<A: GlobalAlloc> {
    profiled_allocator: A,
    cur_atomic_profile: AtomicProfile,
}

impl<A: GlobalAlloc> GlobalAllocProfiler<A> {
    pub const fn new(profiled_allocator: A) -> Self {
        Self {
            profiled_allocator,
            cur_atomic_profile: AtomicProfile::new(),
        }
    }
}

#[inherent::inherent]
impl<A: GlobalAlloc> AllocProfiler for GlobalAllocProfiler<A> {
    pub fn get_current_profile(&self) -> AllocProfile {
        self.cur_atomic_profile.load()
    }

    pub fn profile_guard<'a>(&'a self, total: &'a mut AllocProfile) -> AllocProfileGuard<'a, Self> {
        AllocProfileGuard::new(self, total)
    }
}

unsafe impl<A: GlobalAlloc> GlobalAlloc for GlobalAllocProfiler<A> {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        self.cur_atomic_profile
            .alloc_calls
            .fetch_add(1, Ordering::Relaxed);
        unsafe { self.profiled_allocator.alloc(layout) }
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        self.cur_atomic_profile
            .dealloc_calls
            .fetch_add(1, Ordering::Relaxed);
        unsafe { self.profiled_allocator.dealloc(ptr, layout) }
    }

    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
        self.cur_atomic_profile
            .alloc_zeroed_calls
            .fetch_add(1, Ordering::Relaxed);
        unsafe { self.profiled_allocator.alloc_zeroed(layout) }
    }

    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
        self.cur_atomic_profile
            .realloc_calls
            .fetch_add(1, Ordering::Relaxed);
        unsafe { self.profiled_allocator.realloc(ptr, layout, new_size) }
    }
}
