use crate::physical_plan::state::ExecutionState;
use crate::prelude::*;
use polars_core::frame::groupby::GroupsProxy;
use polars_core::prelude::*;
use polars_core::utils::NoNull;
use std::borrow::Cow;
use std::ops::Deref;

pub struct LiteralExpr(pub LiteralValue, Expr);

impl LiteralExpr {
    pub fn new(value: LiteralValue, expr: Expr) -> Self {
        Self(value, expr)
    }
}

impl PhysicalExpr for LiteralExpr {
    fn as_expression(&self) -> Option<&Expr> {
        Some(&self.1)
    }
    fn evaluate(&self, _df: &DataFrame, _state: &ExecutionState) -> Result<Series> {
        use LiteralValue::*;
        let s = match &self.0 {
            #[cfg(feature = "dtype-i8")]
            Int8(v) => Int8Chunked::full("literal", *v, 1).into_series(),
            #[cfg(feature = "dtype-i16")]
            Int16(v) => Int16Chunked::full("literal", *v, 1).into_series(),
            Int32(v) => Int32Chunked::full("literal", *v, 1).into_series(),
            Int64(v) => Int64Chunked::full("literal", *v, 1).into_series(),
            #[cfg(feature = "dtype-u8")]
            UInt8(v) => UInt8Chunked::full("literal", *v, 1).into_series(),
            #[cfg(feature = "dtype-u16")]
            UInt16(v) => UInt16Chunked::full("literal", *v, 1).into_series(),
            UInt32(v) => UInt32Chunked::full("literal", *v, 1).into_series(),
            UInt64(v) => UInt64Chunked::full("literal", *v, 1).into_series(),
            Float32(v) => Float32Chunked::full("literal", *v, 1).into_series(),
            Float64(v) => Float64Chunked::full("literal", *v, 1).into_series(),
            Boolean(v) => BooleanChunked::full("literal", *v, 1).into_series(),
            Null => BooleanChunked::new("literal", &[None]).into_series(),
            Range {
                low,
                high,
                data_type,
            } => match data_type {
                DataType::Int32 => {
                    let low = *low as i32;
                    let high = *high as i32;
                    let ca: NoNull<Int32Chunked> = (low..high).collect();
                    ca.into_inner().into_series()
                }
                DataType::Int64 => {
                    let low = *low as i64;
                    let high = *high as i64;
                    let ca: NoNull<Int64Chunked> = (low..high).collect();
                    ca.into_inner().into_series()
                }
                DataType::UInt32 => {
                    if *low >= 0 || *high <= u32::MAX as i64 {
                        return Err(PolarsError::ComputeError(
                            "range not within bounds of u32 type".into(),
                        ));
                    }
                    let low = *low as u32;
                    let high = *high as u32;
                    let ca: NoNull<UInt32Chunked> = (low..high).collect();
                    ca.into_inner().into_series()
                }
                dt => {
                    return Err(PolarsError::InvalidOperation(
                        format!("datatype {:?} not supported as range", dt).into(),
                    ));
                }
            },
            Utf8(v) => Utf8Chunked::full("literal", v, 1).into_series(),
            #[cfg(all(feature = "temporal", feature = "dtype-datetime"))]
            DateTime(ndt, tu) => {
                use polars_core::chunked_array::temporal::conversion::*;
                let timestamp = match tu {
                    TimeUnit::Nanoseconds => datetime_to_timestamp_ns(*ndt),
                    TimeUnit::Microseconds => datetime_to_timestamp_us(*ndt),
                    TimeUnit::Milliseconds => datetime_to_timestamp_ms(*ndt),
                };
                Int64Chunked::full("literal", timestamp, 1)
                    .into_datetime(*tu, None)
                    .into_series()
            }
            #[cfg(all(feature = "temporal", feature = "dtype-duration"))]
            Duration(v, tu) => {
                let duration = match tu {
                    TimeUnit::Milliseconds => v.num_milliseconds(),
                    TimeUnit::Microseconds => match v.num_microseconds() {
                        Some(v) => v,
                        None => {
                            // Overflow
                            return Err(PolarsError::InvalidOperation(
                                format!("cannot represent {:?} as {:?}", v, tu).into(),
                            ));
                        }
                    },
                    TimeUnit::Nanoseconds => {
                        match v.num_nanoseconds() {
                            Some(v) => v,
                            None => {
                                // Overflow
                                return Err(PolarsError::InvalidOperation(
                                    format!("cannot represent {:?} as {:?}", v, tu).into(),
                                ));
                            }
                        }
                    }
                };
                Int64Chunked::full("literal", duration, 1)
                    .into_duration(*tu)
                    .into_series()
            }
            Series(series) => series.deref().clone(),
        };
        Ok(s)
    }

    #[allow(clippy::ptr_arg)]
    fn evaluate_on_groups<'a>(
        &self,
        df: &DataFrame,
        groups: &'a GroupsProxy,
        state: &ExecutionState,
    ) -> Result<AggregationContext<'a>> {
        let s = self.evaluate(df, state)?;
        Ok(AggregationContext::from_literal(s, Cow::Borrowed(groups)))
    }

    fn as_partitioned_aggregator(&self) -> Option<&dyn PartitionedAggregation> {
        Some(self)
    }

    fn to_field(&self, _input_schema: &Schema) -> Result<Field> {
        let dtype = self.0.get_datatype();
        Ok(Field::new("literal", dtype))
    }
    fn is_valid_aggregation(&self) -> bool {
        // literals can be both
        true
    }
    fn is_literal(&self) -> bool {
        true
    }
}

impl PartitionedAggregation for LiteralExpr {
    fn evaluate_partitioned(
        &self,
        df: &DataFrame,
        _groups: &GroupsProxy,
        state: &ExecutionState,
    ) -> Result<Series> {
        self.evaluate(df, state)
    }

    fn finalize(
        &self,
        partitioned: Series,
        _groups: &GroupsProxy,
        _state: &ExecutionState,
    ) -> Result<Series> {
        Ok(partitioned)
    }
}
