// 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::borrow::Cow;
use std::sync::Arc;

use crate::metrics::ExpressionEvaluatorMetrics;
use crate::physical_expr::PhysicalExpr;
use crate::tree_node::ExprContext;

use arrow::array::cast::AsArray;
use arrow::array::{
    Array, ArrayDataBuilder, ArrayRef, BooleanArray, BooleanBufferBuilder,
    DictionaryArray, FixedSizeBinaryArray, GenericByteArray, GenericByteViewArray,
    MutableArrayData, PrimitiveArray, make_array, new_null_array,
};
use arrow::buffer::{BooleanBuffer, Buffer, NullBuffer, ScalarBuffer};
use arrow::compute::{SlicesIterator, prep_null_mask_filter};
use arrow::datatypes::{
    ArrowDictionaryKeyType, ArrowNativeType, ArrowPrimitiveType, ByteArrayType,
    ByteViewType, DataType,
};
use arrow::record_batch::RecordBatch;
use arrow::{downcast_dictionary_array, downcast_primitive_array};
use datafusion_common::Result;
use datafusion_expr_common::sort_properties::ExprProperties;

/// Represents a [`PhysicalExpr`] node with associated properties (order and
/// range) in a context where properties are tracked.
pub type ExprPropertiesNode = ExprContext<ExprProperties>;

impl ExprPropertiesNode {
    /// Constructs a new `ExprPropertiesNode` with unknown properties for a
    /// given physical expression. This node initializes with default properties
    /// and recursively applies this to all child expressions.
    pub fn new_unknown(expr: Arc<dyn PhysicalExpr>) -> Self {
        let children = expr
            .children()
            .into_iter()
            .cloned()
            .map(Self::new_unknown)
            .collect();
        Self {
            expr,
            data: ExprProperties::new_unknown(),
            children,
        }
    }
}

/// If the mask selects more than this fraction of rows, use
/// `set_slices()` to copy contiguous ranges. Otherwise iterate
/// over individual positions using `set_indices()`.
const SCATTER_SLICES_SELECTIVITY_THRESHOLD: f64 = 0.8;

/// Scatter `truthy` array by boolean mask. When the mask evaluates `true`, next values of `truthy`
/// are taken, when the mask evaluates `false` values null values are filled.
///
/// # Arguments
/// * `mask` - Boolean values used to determine where to put the `truthy` values
/// * `truthy` - All values of this array are to scatter according to `mask` into final result.
pub fn scatter(mask: &BooleanArray, truthy: &dyn Array) -> Result<ArrayRef> {
    let mask = match mask.null_count() {
        0 => Cow::Borrowed(mask),
        n if n == mask.len() => {
            return Ok(new_null_array(truthy.data_type(), mask.len()));
        }
        _ => Cow::Owned(prep_null_mask_filter(mask)),
    };

    let output_len = mask.len();

    // Fast path: no true values mean all-null object
    if !mask.has_true() {
        return Ok(new_null_array(truthy.data_type(), output_len));
    }

    // Fast path: all true means output = truthy
    if mask.null_count() == 0 && !mask.has_false() {
        return Ok(truthy.slice(0, truthy.len()));
    }

    let count = mask.true_count();
    let selectivity = count as f64 / output_len as f64;
    let mask_buffer = mask.values();

    scatter_array(truthy, mask_buffer, output_len, selectivity)
}

fn scatter_array(
    truthy: &dyn Array,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> Result<ArrayRef> {
    downcast_primitive_array! {
        truthy => Ok(Arc::new(scatter_primitive(truthy, mask, output_len, selectivity))),
        DataType::Boolean => {
            Ok(Arc::new(scatter_boolean(truthy.as_boolean(), mask, output_len, selectivity)))
        }
        DataType::Utf8 => {
            Ok(Arc::new(scatter_bytes(truthy.as_string::<i32>(), mask, output_len, selectivity)))
        }
        DataType::LargeUtf8 => {
            Ok(Arc::new(scatter_bytes(truthy.as_string::<i64>(), mask, output_len, selectivity)))
        }
        DataType::Utf8View => {
            Ok(Arc::new(scatter_byte_view(truthy.as_string_view(), mask, output_len, selectivity)))
        }
        DataType::Binary => {
            Ok(Arc::new(scatter_bytes(truthy.as_binary::<i32>(), mask, output_len, selectivity)))
        }
        DataType::LargeBinary => {
            Ok(Arc::new(scatter_bytes(truthy.as_binary::<i64>(), mask, output_len, selectivity)))
        }
        DataType::BinaryView => {
            Ok(Arc::new(scatter_byte_view(truthy.as_binary_view(), mask, output_len, selectivity)))
        }
        DataType::FixedSizeBinary(_) => {
            Ok(Arc::new(scatter_fixed_size_binary(
                truthy.as_fixed_size_binary(), mask, output_len, selectivity,
            )))
        }
        DataType::Dictionary(_, _) => {
            downcast_dictionary_array! {
                truthy => Ok(Arc::new(scatter_dict(truthy, mask, output_len, selectivity))),
                _t => scatter_fallback(truthy, mask, output_len)
            }
        }
        _ => scatter_fallback(truthy, mask, output_len)
    }
}

#[inline(never)]
fn scatter_native<T: ArrowNativeType>(
    src: &[T],
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> ScalarBuffer<T> {
    let mut output = vec![T::default(); output_len];
    let mut src_offset = 0;

    if selectivity > SCATTER_SLICES_SELECTIVITY_THRESHOLD {
        for (start, end) in mask.set_slices() {
            let len = end - start;
            output[start..end].copy_from_slice(&src[src_offset..src_offset + len]);
            src_offset += len;
        }
    } else {
        for dst_idx in mask.set_indices() {
            output[dst_idx] = src[src_offset];
            src_offset += 1;
        }
    }

    ScalarBuffer::from(output)
}

fn scatter_bits(
    src: &BooleanBuffer,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> BooleanBuffer {
    let mut builder = BooleanBufferBuilder::new(output_len);
    builder.advance(output_len);
    let mut src_offset = 0;

    if selectivity > SCATTER_SLICES_SELECTIVITY_THRESHOLD {
        for (start, end) in mask.set_slices() {
            for i in start..end {
                if src.value(src_offset) {
                    builder.set_bit(i, true);
                }
                src_offset += 1;
            }
        }
    } else {
        for dst_idx in mask.set_indices() {
            if src.value(src_offset) {
                builder.set_bit(dst_idx, true);
            }
            src_offset += 1;
        }
    }

    builder.finish()
}

fn scatter_null_mask(
    src_nulls: Option<&NullBuffer>,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> Option<NullBuffer> {
    let false_count = output_len - mask.count_set_bits();
    let src_null_count = src_nulls.map(|n| n.null_count()).unwrap_or(0);

    if src_null_count == 0 {
        if false_count == 0 {
            None
        } else {
            Some(NullBuffer::new(mask.clone()))
        }
    } else {
        let src_nulls = src_nulls.unwrap();
        let scattered = scatter_bits(src_nulls.inner(), mask, output_len, selectivity);
        Some(NullBuffer::new(scattered)).filter(|n| n.null_count() > 0)
    }
}

fn scatter_primitive<T: ArrowPrimitiveType>(
    truthy: &PrimitiveArray<T>,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> PrimitiveArray<T> {
    let values = scatter_native(truthy.values(), mask, output_len, selectivity);
    let nulls = scatter_null_mask(truthy.nulls(), mask, output_len, selectivity);

    PrimitiveArray::new(values, nulls).with_data_type(truthy.data_type().clone())
}

fn scatter_boolean(
    truthy: &BooleanArray,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> BooleanArray {
    let values = scatter_bits(truthy.values(), mask, output_len, selectivity);
    let nulls = scatter_null_mask(truthy.nulls(), mask, output_len, selectivity);

    BooleanArray::new(values, nulls)
}

fn scatter_bytes<T: ByteArrayType>(
    truthy: &GenericByteArray<T>,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> GenericByteArray<T> {
    let src_offsets = truthy.value_offsets();
    let src_data = truthy.value_data();

    // Build output offsets: false positions get zero-length (offset stays same)
    let mut dst_offsets: Vec<T::Offset> = Vec::with_capacity(output_len + 1);
    let mut cur_offset = T::Offset::default();
    dst_offsets.push(cur_offset);

    let mut src_idx = 0;
    for i in 0..output_len {
        if mask.value(i) {
            let len =
                src_offsets[src_idx + 1].as_usize() - src_offsets[src_idx].as_usize();
            cur_offset += T::Offset::from_usize(len).unwrap();
            src_idx += 1;
        }
        dst_offsets.push(cur_offset);
    }

    let byte_start = src_offsets[0].as_usize();
    let byte_end = src_offsets[src_idx].as_usize();
    let dst_data: Buffer = src_data[byte_start..byte_end].into();

    let nulls = scatter_null_mask(truthy.nulls(), mask, output_len, selectivity);

    let offsets_buffer: Buffer = dst_offsets.into();
    // SAFETY: offsets and data buffers are constructed consistently for `output_len`
    let data = unsafe {
        ArrayDataBuilder::new(truthy.data_type().clone())
            .len(output_len)
            .add_buffer(offsets_buffer)
            .add_buffer(dst_data)
            .nulls(nulls)
            .build_unchecked()
    };
    GenericByteArray::from(data)
}

fn scatter_byte_view<T: ByteViewType>(
    truthy: &GenericByteViewArray<T>,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> GenericByteViewArray<T> {
    let new_views = scatter_native(truthy.views(), mask, output_len, selectivity);
    let nulls = scatter_null_mask(truthy.nulls(), mask, output_len, selectivity);

    // SAFETY: views and data buffers are copied from a valid source array
    unsafe {
        GenericByteViewArray::new_unchecked(
            new_views,
            truthy.data_buffers().to_vec(),
            nulls,
        )
    }
}

fn scatter_fixed_size_binary(
    truthy: &FixedSizeBinaryArray,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> FixedSizeBinaryArray {
    let value_length = truthy.value_length() as usize;
    let mut output = vec![0u8; output_len * value_length];
    let mut src_idx = 0;

    if selectivity > SCATTER_SLICES_SELECTIVITY_THRESHOLD {
        for (start, end) in mask.set_slices() {
            for dst_idx in start..end {
                let src_bytes = truthy.value(src_idx);
                let dst_start = dst_idx * value_length;
                output[dst_start..dst_start + value_length].copy_from_slice(src_bytes);
                src_idx += 1;
            }
        }
    } else {
        for dst_idx in mask.set_indices() {
            let src_bytes = truthy.value(src_idx);
            let dst_start = dst_idx * value_length;
            output[dst_start..dst_start + value_length].copy_from_slice(src_bytes);
            src_idx += 1;
        }
    }
    let nulls = scatter_null_mask(truthy.nulls(), mask, output_len, selectivity);

    FixedSizeBinaryArray::new(truthy.value_length(), Buffer::from(output), nulls)
}

fn scatter_dict<K: ArrowDictionaryKeyType>(
    truthy: &DictionaryArray<K>,
    mask: &BooleanBuffer,
    output_len: usize,
    selectivity: f64,
) -> DictionaryArray<K> {
    let scattered_keys = scatter_primitive(truthy.keys(), mask, output_len, selectivity);
    DictionaryArray::new(scattered_keys, Arc::clone(truthy.values()))
}

fn scatter_fallback(
    truthy: &dyn Array,
    mask: &BooleanBuffer,
    output_len: usize,
) -> Result<ArrayRef> {
    let truthy_data = truthy.to_data();
    let mut mutable = MutableArrayData::new(vec![&truthy_data], true, output_len);

    // the SlicesIterator slices only the true values. So the gaps left by
    // this iterator we need to fill with falsy values

    // keep track of how much is filled
    let mut filled = 0;
    // keep track of current position we have in truthy array
    let mut true_pos = 0;

    let mask_array = BooleanArray::new(mask.clone(), None);
    SlicesIterator::new(&mask_array).for_each(|(start, end)| {
        // the gap needs to be filled with nulls
        if start > filled {
            mutable.extend_nulls(start - filled);
        }
        // fill with truthy values
        let len = end - start;
        mutable.extend(0, true_pos, true_pos + len);
        true_pos += len;
        filled = end;
    });

    // the remaining part is falsy
    if filled < output_len {
        mutable.extend_nulls(output_len - filled);
    }

    let data = mutable.freeze();
    Ok(make_array(data))
}

/// Evaluates expressions against a record batch.
/// This will convert the resulting ColumnarValues to ArrayRefs,
/// duplicating any ScalarValues that may have been returned,
/// and validating that the returned arrays all have the same
/// number of rows as the input batch.
#[inline]
pub fn evaluate_expressions_to_arrays<'a>(
    exprs: impl IntoIterator<Item = &'a Arc<dyn PhysicalExpr>>,
    batch: &RecordBatch,
) -> Result<Vec<ArrayRef>> {
    evaluate_expressions_to_arrays_with_metrics(exprs, batch, None)
}

/// Same as [`evaluate_expressions_to_arrays`] but records optional per-expression metrics.
///
/// For metrics tracking, see [`ExpressionEvaluatorMetrics`] for details.
#[inline]
pub fn evaluate_expressions_to_arrays_with_metrics<'a>(
    exprs: impl IntoIterator<Item = &'a Arc<dyn PhysicalExpr>>,
    batch: &RecordBatch,
    metrics: Option<&ExpressionEvaluatorMetrics>,
) -> Result<Vec<ArrayRef>> {
    let num_rows = batch.num_rows();
    exprs
        .into_iter()
        .enumerate()
        .map(|(idx, e)| {
            let _timer = metrics.and_then(|m| m.scoped_timer(idx));
            e.evaluate(batch)
                .and_then(|col| col.into_array_of_size(num_rows))
        })
        .collect::<Result<Vec<ArrayRef>>>()
}

#[cfg(test)]
mod tests {

    use arrow::array::{Int32Array, StringArray, StringViewArray, as_string_array};
    use arrow::compute::filter;
    use datafusion_common::cast::{as_boolean_array, as_int32_array};

    use super::*;

    #[test]
    fn scatter_int() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(vec![1, 10, 11, 100]));
        let mask = BooleanArray::from(vec![true, true, false, false, true]);

        // the output array is expected to be the same length as the mask array
        let expected =
            Int32Array::from_iter(vec![Some(1), Some(10), None, None, Some(11)]);
        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;

        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_int_end_with_false() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(vec![1, 10, 11, 100]));
        let mask = BooleanArray::from(vec![true, false, true, false, false, false]);

        // output should be same length as mask
        let expected =
            Int32Array::from_iter(vec![Some(1), None, Some(10), None, None, None]);
        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;

        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_with_null_mask() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(vec![1, 10, 11]));
        let mask: BooleanArray = vec![Some(false), None, Some(true), Some(true), None]
            .into_iter()
            .collect();

        // output should treat nulls as though they are false
        let expected = Int32Array::from_iter(vec![None, None, Some(1), Some(10), None]);
        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;

        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_boolean() -> Result<()> {
        let truthy = Arc::new(BooleanArray::from(vec![false, false, false, true]));
        let mask = BooleanArray::from(vec![true, true, false, false, true]);

        // the output array is expected to be the same length as the mask array
        let expected = BooleanArray::from_iter(vec![
            Some(false),
            Some(false),
            None,
            None,
            Some(false),
        ]);
        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_boolean_array(&result)?;

        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_all_true() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(vec![1, 2, 3]));
        let mask = BooleanArray::from(vec![true, true, true]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;
        assert_eq!(&Int32Array::from(vec![1, 2, 3]), result);
        Ok(())
    }

    #[test]
    fn scatter_all_false() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(Vec::<i32>::new()));
        let mask = BooleanArray::from(vec![false, false, false]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;
        let expected = Int32Array::from(vec![None, None, None]);
        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_empty() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(Vec::<i32>::new()));
        let mask = BooleanArray::from(Vec::<bool>::new());

        let result = scatter(&mask, truthy.as_ref())?;
        assert_eq!(result.len(), 0);
        Ok(())
    }

    #[test]
    fn scatter_primitive_with_source_nulls() -> Result<()> {
        let truthy = Arc::new(Int32Array::from(vec![Some(1), None, Some(3)]));
        let mask = BooleanArray::from(vec![true, false, true, true, false]);

        let expected = Int32Array::from_iter(vec![Some(1), None, None, Some(3), None]);
        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_int32_array(&result)?;

        assert_eq!(&expected, result);
        Ok(())
    }

    #[test]
    fn scatter_string_test() -> Result<()> {
        let truthy = Arc::new(StringArray::from(vec!["hello", "world"]));
        let mask = BooleanArray::from(vec![true, false, false, true]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = as_string_array(&result);

        assert_eq!(result.len(), 4);
        assert!(result.is_valid(0));
        assert_eq!(result.value(0), "hello");
        assert!(result.is_null(1));
        assert!(result.is_null(2));
        assert!(result.is_valid(3));
        assert_eq!(result.value(3), "world");
        Ok(())
    }

    #[test]
    fn scatter_string_view_test() -> Result<()> {
        let truthy = Arc::new(StringViewArray::from(vec![
            "short",
            "a longer string that exceeds inline",
        ]));
        let mask = BooleanArray::from(vec![false, true, true, false]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = result.as_any().downcast_ref::<StringViewArray>().unwrap();

        assert_eq!(result.len(), 4);
        assert!(result.is_null(0));
        assert_eq!(result.value(1), "short");
        assert_eq!(result.value(2), "a longer string that exceeds inline");
        assert!(result.is_null(3));
        Ok(())
    }

    #[test]
    fn scatter_dictionary_test() -> Result<()> {
        use arrow::datatypes::Int8Type;

        let values = StringArray::from(vec!["a", "b"]);
        let truthy = Arc::new(
            DictionaryArray::<Int8Type>::try_new(
                arrow::array::Int8Array::from(vec![0, 1, 0]),
                Arc::new(values),
            )
            .unwrap(),
        );
        let mask = BooleanArray::from(vec![true, false, true, true, false]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = result
            .as_any()
            .downcast_ref::<DictionaryArray<Int8Type>>()
            .unwrap();

        assert_eq!(result.len(), 5);
        assert!(result.is_valid(0));
        assert!(result.is_null(1));
        assert!(result.is_valid(2));
        assert!(result.is_valid(3));
        assert!(result.is_null(4));
        Ok(())
    }

    #[test]
    fn scatter_fixed_size_binary_test() -> Result<()> {
        let truthy = Arc::new(FixedSizeBinaryArray::from(vec![
            &[1u8, 2][..],
            &[3, 4][..],
            &[5, 6][..],
        ]));
        let mask = BooleanArray::from(vec![true, false, true, false, true]);

        let result = scatter(&mask, truthy.as_ref())?;
        let result = result
            .as_any()
            .downcast_ref::<FixedSizeBinaryArray>()
            .unwrap();

        assert_eq!(result.len(), 5);
        assert!(result.is_valid(0));
        assert_eq!(result.value(0), &[1, 2]);
        assert!(result.is_null(1));
        assert!(result.is_valid(2));
        assert_eq!(result.value(2), &[3, 4]);
        assert!(result.is_null(3));
        assert!(result.is_valid(4));
        assert_eq!(result.value(4), &[5, 6]);
        Ok(())
    }

    #[test]
    fn scatter_filter_roundtrip() -> Result<()> {
        let original = Arc::new(Int32Array::from(vec![10, 20, 30, 40, 50]));
        let mask = BooleanArray::from(vec![true, false, true, false, true]);

        // filter compacts: [10, 30, 50]
        let filtered = filter(original.as_ref(), &mask).unwrap();
        // scatter expands back: [10, null, 30, null, 50]
        let scattered = scatter(&mask, filtered.as_ref())?;
        let scattered = as_int32_array(&scattered)?;

        assert_eq!(scattered.len(), 5);
        assert_eq!(scattered.value(0), 10);
        assert!(scattered.is_null(1));
        assert_eq!(scattered.value(2), 30);
        assert!(scattered.is_null(3));
        assert_eq!(scattered.value(4), 50);
        Ok(())
    }
}
