// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2025 Google LLC.

use kernel::{
    page::PAGE_SIZE,
    prelude::*,
    rbtree::{RBTree, RBTreeNode, RBTreeNodeReservation},
    seq_file::SeqFile,
    seq_print,
    task::Pid,
};

use crate::range_alloc::{DescriptorState, FreedRange, Range};

/// Keeps track of allocations in a process' mmap.
///
/// Each process has an mmap where the data for incoming transactions will be placed. This struct
/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that
/// has metadata related to the allocation. We also keep track of available free space.
pub(super) struct TreeRangeAllocator<T> {
    /// This collection contains descriptors for *both* ranges containing an allocation, *and* free
    /// ranges between allocations. The free ranges get merged, so there are never two free ranges
    /// next to each other.
    tree: RBTree<usize, Descriptor<T>>,
    /// Contains an entry for every free range in `self.tree`. This tree sorts the ranges by size,
    /// letting us look up the smallest range whose size is at least some lower bound.
    free_tree: RBTree<FreeKey, ()>,
    size: usize,
    free_oneway_space: usize,
}

impl<T> TreeRangeAllocator<T> {
    pub(crate) fn from_array(
        size: usize,
        ranges: &mut KVec<Range<T>>,
        alloc: &mut FromArrayAllocs<T>,
    ) -> Self {
        let mut tree = TreeRangeAllocator {
            tree: RBTree::new(),
            free_tree: RBTree::new(),
            size,
            free_oneway_space: size / 2,
        };

        let mut free_offset = 0;
        for range in ranges.drain_all() {
            let free_size = range.offset - free_offset;
            if free_size > 0 {
                let free_node = alloc.free_tree.pop().unwrap();
                tree.free_tree
                    .insert(free_node.into_node((free_size, free_offset), ()));
                let tree_node = alloc.tree.pop().unwrap();
                tree.tree.insert(
                    tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)),
                );
            }
            free_offset = range.endpoint();

            if range.state.is_oneway() {
                tree.free_oneway_space = tree.free_oneway_space.saturating_sub(range.size);
            }

            let free_res = alloc.free_tree.pop().unwrap();
            let tree_node = alloc.tree.pop().unwrap();
            let mut desc = Descriptor::new(range.offset, range.size);
            desc.state = Some((range.state, free_res));
            tree.tree.insert(tree_node.into_node(range.offset, desc));
        }

        // After the last range, we may need a free range.
        if free_offset < size {
            let free_size = size - free_offset;
            let free_node = alloc.free_tree.pop().unwrap();
            tree.free_tree
                .insert(free_node.into_node((free_size, free_offset), ()));
            let tree_node = alloc.tree.pop().unwrap();
            tree.tree
                .insert(tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)));
        }

        tree
    }

    pub(crate) fn is_empty(&self) -> bool {
        let mut tree_iter = self.tree.values();
        // There's always at least one range, because index zero is either the start of a free or
        // allocated range.
        let first_value = tree_iter.next().unwrap();
        if tree_iter.next().is_some() {
            // There are never two free ranges next to each other, so if there is more than one
            // descriptor, then at least one of them must hold an allocated range.
            return false;
        }
        // There is only one descriptor. Return true if it is for a free range.
        first_value.state.is_none()
    }

    pub(crate) fn total_size(&self) -> usize {
        self.size
    }

    pub(crate) fn free_oneway_space(&self) -> usize {
        self.free_oneway_space
    }

    pub(crate) fn count_buffers(&self) -> usize {
        self.tree
            .values()
            .filter(|desc| desc.state.is_some())
            .count()
    }

    pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
        for desc in self.tree.values() {
            let state = match &desc.state {
                Some(state) => &state.0,
                None => continue,
            };
            seq_print!(
                m,
                "  buffer: {} size {} pid {}",
                desc.offset,
                desc.size,
                state.pid(),
            );
            if state.is_oneway() {
                seq_print!(m, " oneway");
            }
            match state {
                DescriptorState::Reserved(_res) => {
                    seq_print!(m, " reserved\n");
                }
                DescriptorState::Allocated(_alloc) => {
                    seq_print!(m, " allocated\n");
                }
            }
        }
        Ok(())
    }

    fn find_best_match(&mut self, size: usize) -> Option<&mut Descriptor<T>> {
        let free_cursor = self.free_tree.cursor_lower_bound(&(size, 0))?;
        let ((_, offset), ()) = free_cursor.current();
        self.tree.get_mut(offset)
    }

    /// Try to reserve a new buffer, using the provided allocation if necessary.
    pub(crate) fn reserve_new(
        &mut self,
        debug_id: usize,
        size: usize,
        is_oneway: bool,
        pid: Pid,
        alloc: ReserveNewTreeAlloc<T>,
    ) -> Result<(usize, bool)> {
        // Compute new value of free_oneway_space, which is set only on success.
        let new_oneway_space = if is_oneway {
            match self.free_oneway_space.checked_sub(size) {
                Some(new_oneway_space) => new_oneway_space,
                None => return Err(ENOSPC),
            }
        } else {
            self.free_oneway_space
        };

        // Start detecting spammers once we have less than 20%
        // of async space left (which is less than 10% of total
        // buffer size).
        //
        // (This will short-circut, so `low_oneway_space` is
        // only called when necessary.)
        let oneway_spam_detected =
            is_oneway && new_oneway_space < self.size / 10 && self.low_oneway_space(pid);

        let (found_size, found_off, tree_node, free_tree_node) = match self.find_best_match(size) {
            None => {
                pr_warn!("ENOSPC from range_alloc.reserve_new - size: {}", size);
                return Err(ENOSPC);
            }
            Some(desc) => {
                let found_size = desc.size;
                let found_offset = desc.offset;

                // In case we need to break up the descriptor
                let new_desc = Descriptor::new(found_offset + size, found_size - size);
                let (tree_node, free_tree_node, desc_node_res) = alloc.initialize(new_desc);

                desc.state = Some((
                    DescriptorState::new(is_oneway, debug_id, pid),
                    desc_node_res,
                ));
                desc.size = size;

                (found_size, found_offset, tree_node, free_tree_node)
            }
        };
        self.free_oneway_space = new_oneway_space;
        self.free_tree.remove(&(found_size, found_off));

        if found_size != size {
            self.tree.insert(tree_node);
            self.free_tree.insert(free_tree_node);
        }

        Ok((found_off, oneway_spam_detected))
    }

    pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> {
        let mut cursor = self.tree.cursor_lower_bound_mut(&offset).ok_or_else(|| {
            pr_warn!(
                "EINVAL from range_alloc.reservation_abort - offset: {}",
                offset
            );
            EINVAL
        })?;

        let (_, desc) = cursor.current_mut();

        if desc.offset != offset {
            pr_warn!(
                "EINVAL from range_alloc.reservation_abort - offset: {}",
                offset
            );
            return Err(EINVAL);
        }

        let (reservation, free_node_res) = desc.try_change_state(|state| match state {
            Some((DescriptorState::Reserved(reservation), free_node_res)) => {
                (None, Ok((reservation, free_node_res)))
            }
            None => {
                pr_warn!(
                    "EINVAL from range_alloc.reservation_abort - offset: {}",
                    offset
                );
                (None, Err(EINVAL))
            }
            allocated => {
                pr_warn!(
                    "EPERM from range_alloc.reservation_abort - offset: {}",
                    offset
                );
                (allocated, Err(EPERM))
            }
        })?;

        let mut size = desc.size;
        let mut offset = desc.offset;
        let free_oneway_space_add = if reservation.is_oneway { size } else { 0 };

        self.free_oneway_space += free_oneway_space_add;

        let mut freed_range = FreedRange::interior_pages(offset, size);
        // Compute how large the next free region needs to be to include one more page in
        // the newly freed range.
        let add_next_page_needed = match (offset + size) % PAGE_SIZE {
            0 => usize::MAX,
            unalign => PAGE_SIZE - unalign,
        };
        // Compute how large the previous free region needs to be to include one more page
        // in the newly freed range.
        let add_prev_page_needed = match offset % PAGE_SIZE {
            0 => usize::MAX,
            unalign => unalign,
        };

        // Merge next into current if next is free
        let remove_next = match cursor.peek_next() {
            Some((_, next)) if next.state.is_none() => {
                if next.size >= add_next_page_needed {
                    freed_range.end_page_idx += 1;
                }
                self.free_tree.remove(&(next.size, next.offset));
                size += next.size;
                true
            }
            _ => false,
        };

        if remove_next {
            let (_, desc) = cursor.current_mut();
            desc.size = size;
            cursor.remove_next();
        }

        // Merge current into prev if prev is free
        match cursor.peek_prev_mut() {
            Some((_, prev)) if prev.state.is_none() => {
                if prev.size >= add_prev_page_needed {
                    freed_range.start_page_idx -= 1;
                }
                // merge previous with current, remove current
                self.free_tree.remove(&(prev.size, prev.offset));
                offset = prev.offset;
                size += prev.size;
                prev.size = size;
                cursor.remove_current();
            }
            _ => {}
        };

        self.free_tree
            .insert(free_node_res.into_node((size, offset), ()));

        Ok(freed_range)
    }

    pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result {
        let desc = self.tree.get_mut(&offset).ok_or(ENOENT)?;

        desc.try_change_state(|state| match state {
            Some((DescriptorState::Reserved(reservation), free_node_res)) => (
                Some((
                    DescriptorState::Allocated(reservation.allocate(data.take())),
                    free_node_res,
                )),
                Ok(()),
            ),
            other => (other, Err(ENOENT)),
        })
    }

    /// Takes an entry at the given offset from [`DescriptorState::Allocated`] to
    /// [`DescriptorState::Reserved`].
    ///
    /// Returns the size of the existing entry and the data associated with it.
    pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> {
        let desc = self.tree.get_mut(&offset).ok_or_else(|| {
            pr_warn!(
                "ENOENT from range_alloc.reserve_existing - offset: {}",
                offset
            );
            ENOENT
        })?;

        let (debug_id, data) = desc.try_change_state(|state| match state {
            Some((DescriptorState::Allocated(allocation), free_node_res)) => {
                let (reservation, data) = allocation.deallocate();
                let debug_id = reservation.debug_id;
                (
                    Some((DescriptorState::Reserved(reservation), free_node_res)),
                    Ok((debug_id, data)),
                )
            }
            other => {
                pr_warn!(
                    "ENOENT from range_alloc.reserve_existing - offset: {}",
                    offset
                );
                (other, Err(ENOENT))
            }
        })?;

        Ok((desc.size, debug_id, data))
    }

    /// Call the provided callback at every allocated region.
    ///
    /// This destroys the range allocator. Used only during shutdown.
    pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) {
        for (_, desc) in self.tree.iter_mut() {
            if let Some((DescriptorState::Allocated(allocation), _)) = &mut desc.state {
                callback(
                    desc.offset,
                    desc.size,
                    allocation.debug_id(),
                    allocation.take(),
                );
            }
        }
    }

    /// Find the amount and size of buffers allocated by the current caller.
    ///
    /// The idea is that once we cross the threshold, whoever is responsible
    /// for the low async space is likely to try to send another async transaction,
    /// and at some point we'll catch them in the act.  This is more efficient
    /// than keeping a map per pid.
    fn low_oneway_space(&self, calling_pid: Pid) -> bool {
        let mut total_alloc_size = 0;
        let mut num_buffers = 0;
        for (_, desc) in self.tree.iter() {
            if let Some((state, _)) = &desc.state {
                if state.is_oneway() && state.pid() == calling_pid {
                    total_alloc_size += desc.size;
                    num_buffers += 1;
                }
            }
        }

        // Warn if this pid has more than 50 transactions, or more than 50% of
        // async space (which is 25% of total buffer size). Oneway spam is only
        // detected when the threshold is exceeded.
        num_buffers > 50 || total_alloc_size > self.size / 4
    }
}

type TreeDescriptorState<T> = (DescriptorState<T>, FreeNodeRes);
struct Descriptor<T> {
    size: usize,
    offset: usize,
    state: Option<TreeDescriptorState<T>>,
}

impl<T> Descriptor<T> {
    fn new(offset: usize, size: usize) -> Self {
        Self {
            size,
            offset,
            state: None,
        }
    }

    fn try_change_state<F, Data>(&mut self, f: F) -> Result<Data>
    where
        F: FnOnce(Option<TreeDescriptorState<T>>) -> (Option<TreeDescriptorState<T>>, Result<Data>),
    {
        let (new_state, result) = f(self.state.take());
        self.state = new_state;
        result
    }
}

// (Descriptor.size, Descriptor.offset)
type FreeKey = (usize, usize);
type FreeNodeRes = RBTreeNodeReservation<FreeKey, ()>;

/// An allocation for use by `reserve_new`.
pub(crate) struct ReserveNewTreeAlloc<T> {
    tree_node_res: RBTreeNodeReservation<usize, Descriptor<T>>,
    free_tree_node_res: FreeNodeRes,
    desc_node_res: FreeNodeRes,
}

impl<T> ReserveNewTreeAlloc<T> {
    pub(crate) fn try_new() -> Result<Self> {
        let tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
        let free_tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
        let desc_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
        Ok(Self {
            tree_node_res,
            free_tree_node_res,
            desc_node_res,
        })
    }

    fn initialize(
        self,
        desc: Descriptor<T>,
    ) -> (
        RBTreeNode<usize, Descriptor<T>>,
        RBTreeNode<FreeKey, ()>,
        FreeNodeRes,
    ) {
        let size = desc.size;
        let offset = desc.offset;
        (
            self.tree_node_res.into_node(offset, desc),
            self.free_tree_node_res.into_node((size, offset), ()),
            self.desc_node_res,
        )
    }
}

/// An allocation for creating a tree from an `ArrayRangeAllocator`.
pub(crate) struct FromArrayAllocs<T> {
    tree: KVec<RBTreeNodeReservation<usize, Descriptor<T>>>,
    free_tree: KVec<RBTreeNodeReservation<FreeKey, ()>>,
}

impl<T> FromArrayAllocs<T> {
    pub(crate) fn try_new(len: usize) -> Result<Self> {
        let num_descriptors = 2 * len + 1;

        let mut tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?;
        for _ in 0..num_descriptors {
            tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?;
        }

        let mut free_tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?;
        for _ in 0..num_descriptors {
            free_tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?;
        }

        Ok(Self { tree, free_tree })
    }
}
