// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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.

use std::ops::{Deref, DerefMut};

use unchecked_index::UncheckedIndex;

pub trait ComparableForLoserTree {
    fn lt(&self, other: &Self) -> bool;
}

/// An implementation of the tournament loser tree data structure.
pub struct LoserTree<T> {
    losers: UncheckedIndex<Vec<usize>>,
    values: UncheckedIndex<Vec<T>>,
}

#[allow(clippy::len_without_is_empty)]
impl<T: ComparableForLoserTree> LoserTree<T> {
    pub fn new(values: Vec<T>) -> Self {
        let mut tree = unsafe {
            // safety:
            // this component is performance critical,  use unchecked index
            // to avoid boundary checking.
            Self {
                losers: unchecked_index::unchecked_index(vec![]),
                values: unchecked_index::unchecked_index(values),
            }
        };
        tree.init_tree();
        tree
    }

    pub fn values(&self) -> &[T] {
        &self.values
    }

    pub fn values_mut(&mut self) -> &mut [T] {
        &mut self.values
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn peek(&self) -> &T {
        &self.values[0]
    }

    pub fn peek_mut(&mut self) -> LoserTreePeekMut<'_, T> {
        LoserTreePeekMut {
            tree: self,
            dirty: false,
        }
    }

    fn init_tree(&mut self) {
        self.losers.resize(self.values.len(), usize::MAX);
        for i in 0..self.values.len() {
            let mut winner = i;
            let mut cmp_node = (self.values.len() + i) / 2;
            while cmp_node != 0 && self.losers[cmp_node] != usize::MAX {
                let challenger = self.losers[cmp_node];
                if self.values[challenger].lt(&self.values[winner]) {
                    self.losers[cmp_node] = winner;
                    winner = challenger;
                } else {
                    self.losers[cmp_node] = challenger;
                }
                cmp_node /= 2;
            }
            self.losers[cmp_node] = winner;
        }
    }

    fn adjust_tree(&mut self) {
        let mut winner = self.losers[0];
        let mut cmp_node = (self.values.len() + winner) / 2;
        while cmp_node != 0 {
            let challenger = self.losers[cmp_node];
            if self.values[challenger].lt(&self.values[winner]) {
                self.losers[cmp_node] = winner;
                winner = challenger;
            }
            cmp_node /= 2;
        }
        self.losers[0] = winner;
    }
}

/// A PeekMut structure to the loser tree, used to get smallest value and auto
/// adjusting after dropped.
pub struct LoserTreePeekMut<'a, T: ComparableForLoserTree> {
    tree: &'a mut LoserTree<T>,
    dirty: bool,
}

impl<T: ComparableForLoserTree> LoserTreePeekMut<'_, T> {
    pub fn adjust(&mut self) {
        if self.dirty {
            self.tree.adjust_tree();
            self.dirty = false;
        }
    }
}

impl<T: ComparableForLoserTree> Deref for LoserTreePeekMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.tree.values[self.tree.losers[0]]
    }
}

impl<T: ComparableForLoserTree> DerefMut for LoserTreePeekMut<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.dirty = true;
        &mut self.tree.values[self.tree.losers[0]]
    }
}

impl<T: ComparableForLoserTree> Drop for LoserTreePeekMut<'_, T> {
    fn drop(&mut self) {
        if self.dirty {
            self.tree.adjust_tree();
        }
    }
}

#[cfg(test)]
mod test {
    use itertools::Itertools;
    use rand::Rng;

    use crate::algorithm::loser_tree::{ComparableForLoserTree, LoserTree};

    #[test]
    fn fuzztest() {
        for _ in 0..10 {
            let num_nodes = rand::rng().random_range(1..=999);
            let mut nodes = vec![];
            for _ in 0..num_nodes {
                let node_len = rand::rng().random_range(1..=999);
                let mut node = vec![];
                for _ in 0..node_len {
                    node.push(rand::rng().random_range(1000..=9999));
                }
                nodes.push(node);
            }

            // expected
            let expected = nodes
                .clone()
                .into_iter()
                .flatten()
                .sorted_unstable()
                .collect_vec();

            // actual
            struct Cursor {
                row_idx: usize,
                values: Vec<u64>,
            }
            impl ComparableForLoserTree for Cursor {
                fn lt(&self, other: &Self) -> bool {
                    match (
                        self.values.get(self.row_idx),
                        other.values.get(other.row_idx),
                    ) {
                        (Some(v1), Some(v2)) => v1 < v2,
                        (None, _) => false,
                        (_, None) => true,
                    }
                }
            }
            let mut loser_tree = LoserTree::new(
                nodes
                    .into_iter()
                    .map(|node| Cursor {
                        row_idx: 0,
                        values: node.into_iter().sorted_unstable().collect_vec(),
                    })
                    .collect_vec(),
            );

            let mut actual = vec![];
            loop {
                let mut min = loser_tree.peek_mut();
                if let Some(v) = min.values.get(min.row_idx) {
                    actual.push(*v);
                    min.row_idx += 1;
                } else {
                    break;
                }
            }

            for cursor in loser_tree.values() {
                assert_eq!(cursor.row_idx, cursor.values.len());
            }
            assert_eq!(actual, expected);
        }
    }
}
