use std::rc::Rc;
use chrono::Offset;
use ibig::IBig;
use rust_decimal::Decimal;
use crate::atomic;
use crate::error;
use super::cast_binary::cast_binary_arithmetic;
use super::datetime::ToDateTimeStamp;
use super::datetime::{
NaiveDateTimeWithOffset, NaiveDateWithOffset, NaiveTimeWithOffset, YearMonthDuration,
};
pub(crate) fn op_subtract(
a: atomic::Atomic,
b: atomic::Atomic,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
use atomic::Atomic;
let (a, b) = cast_binary_arithmetic(a, b)?;
match (a, b) {
(Atomic::Decimal(a), Atomic::Decimal(b)) => Ok(op_substract_decimal(a, b)?),
(Atomic::Integer(_, a), Atomic::Integer(_, b)) => Ok(op_substract_integer(a, b)),
(Atomic::Float(a), Atomic::Float(b)) => Ok((a - b).into()),
(Atomic::Double(a), Atomic::Double(b)) => Ok((a - b).into()),
(Atomic::Date(a), Atomic::Date(b)) => Ok(op_subtract_dates(a, b, default_offset)?),
(Atomic::Date(a), Atomic::YearMonthDuration(b)) => {
Ok(op_subtract_year_month_duration_from_date(a, b)?)
}
(Atomic::Date(a), Atomic::DayTimeDuration(b)) => {
Ok(op_subtract_day_time_duration_from_date(a, *b)?)
}
(Atomic::Time(a), Atomic::Time(b)) => Ok(op_subtract_times(a, b, default_offset)?),
(Atomic::Time(a), Atomic::DayTimeDuration(b)) => {
Ok(op_subtract_day_time_duration_from_time(a, *b)?)
}
(Atomic::DateTime(a), Atomic::DateTime(b)) => {
Ok(op_subtract_date_times(a, b, default_offset)?)
}
(Atomic::DateTimeStamp(a), Atomic::DateTimeStamp(b)) => {
Ok(op_subtract_date_time_stamps(*a.as_ref(), *b.as_ref())?)
}
(Atomic::DateTimeStamp(a), Atomic::DateTime(b)) => Ok(
op_subtract_date_time_from_date_time_stamp(a, b, default_offset)?,
),
(Atomic::DateTime(a), Atomic::DateTimeStamp(b)) => Ok(
op_subtract_date_time_stamp_from_date_time(a, b, default_offset)?,
),
(Atomic::DateTime(a), Atomic::YearMonthDuration(b)) => {
Ok(op_subtract_year_month_duration_from_date_time(a, b)?)
}
(Atomic::DateTimeStamp(a), Atomic::YearMonthDuration(b)) => {
Ok(op_subtract_year_month_duration_from_date_time_stamp(a, b)?)
}
(Atomic::DateTime(a), Atomic::DayTimeDuration(b)) => {
Ok(op_subtract_day_time_duration_from_date_time(a, *b)?)
}
(Atomic::DateTimeStamp(a), Atomic::DayTimeDuration(b)) => {
Ok(op_subtract_day_time_duration_from_date_time_stamp(a, *b)?)
}
(Atomic::YearMonthDuration(a), Atomic::YearMonthDuration(b)) => {
Ok(op_subtract_year_month_durations(a, b)?)
}
(Atomic::DayTimeDuration(a), Atomic::DayTimeDuration(b)) => {
Ok(op_subtract_day_time_durations(a, b)?)
}
_ => Err(error::Error::XPTY0004),
}
}
fn op_substract_decimal(a: Rc<Decimal>, b: Rc<Decimal>) -> error::Result<atomic::Atomic> {
Ok(a.as_ref()
.checked_sub(*b.as_ref())
.ok_or(error::Error::FOAR0002)?
.into())
}
fn op_substract_integer(a: Rc<IBig>, b: Rc<IBig>) -> atomic::Atomic {
(a.as_ref() - b.as_ref()).into()
}
fn op_subtract_dates(
a: Rc<NaiveDateWithOffset>,
b: Rc<NaiveDateWithOffset>,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
let a = a.to_date_time_stamp(default_offset);
let b = b.to_date_time_stamp(default_offset);
op_subtract_date_time_stamps(a, b)
}
fn op_subtract_year_month_duration_from_date(
a: Rc<NaiveDateWithOffset>,
b: YearMonthDuration,
) -> error::Result<atomic::Atomic> {
let a = a.as_ref();
let date = a.date;
let new_date = if b.months >= 0 {
date.checked_sub_months(chrono::Months::new(b.months as u32))
.ok_or(error::Error::FOAR0002)
} else {
date.checked_add_months(chrono::Months::new(b.months.unsigned_abs() as u32))
.ok_or(error::Error::FOAR0002)
}?;
Ok(NaiveDateWithOffset::new(new_date, a.offset).into())
}
fn op_subtract_day_time_duration_from_date(
a: Rc<NaiveDateWithOffset>,
b: chrono::Duration,
) -> error::Result<atomic::Atomic> {
let offset = a.as_ref().offset;
let a = a.to_date_time_stamp(chrono::offset::Utc.fix());
let a = a.checked_sub_signed(b).ok_or(error::Error::FOAR0002)?;
let new_date = a.date_naive();
Ok(NaiveDateWithOffset::new(new_date, offset).into())
}
fn op_subtract_times(
a: Rc<NaiveTimeWithOffset>,
b: Rc<NaiveTimeWithOffset>,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
let a = a.to_date_time_stamp(default_offset);
let b = b.to_date_time_stamp(default_offset);
op_subtract_date_time_stamps(a, b)
}
fn op_subtract_day_time_duration_from_time(
a: Rc<NaiveTimeWithOffset>,
b: chrono::Duration,
) -> error::Result<atomic::Atomic> {
let new_time = a.as_ref().time - b;
Ok(NaiveTimeWithOffset::new(new_time, a.as_ref().offset).into())
}
fn op_subtract_date_times(
a: Rc<NaiveDateTimeWithOffset>,
b: Rc<NaiveDateTimeWithOffset>,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
let a = a.to_date_time_stamp(default_offset);
let b = b.to_date_time_stamp(default_offset);
op_subtract_date_time_stamps(a, b)
}
fn op_subtract_date_time_stamps(
a: chrono::DateTime<chrono::FixedOffset>,
b: chrono::DateTime<chrono::FixedOffset>,
) -> error::Result<atomic::Atomic> {
Ok((a - b).into())
}
fn op_subtract_date_time_stamp_from_date_time(
a: Rc<NaiveDateTimeWithOffset>,
b: Rc<chrono::DateTime<chrono::FixedOffset>>,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
let a = a.to_date_time_stamp(default_offset);
op_subtract_date_time_stamps(a, *b.as_ref())
}
fn op_subtract_date_time_from_date_time_stamp(
a: Rc<chrono::DateTime<chrono::FixedOffset>>,
b: Rc<NaiveDateTimeWithOffset>,
default_offset: chrono::FixedOffset,
) -> error::Result<atomic::Atomic> {
let b = b.to_date_time_stamp(default_offset);
op_subtract_date_time_stamps(*a.as_ref(), b)
}
fn op_subtract_year_month_duration_from_date_time(
a: Rc<NaiveDateTimeWithOffset>,
b: YearMonthDuration,
) -> error::Result<atomic::Atomic> {
let a = a.as_ref();
let date_time = a.date_time;
let new_date_time = if b.months >= 0 {
date_time
.checked_sub_months(chrono::Months::new(b.months as u32))
.ok_or(error::Error::FOAR0002)
} else {
date_time
.checked_add_months(chrono::Months::new(b.months.unsigned_abs() as u32))
.ok_or(error::Error::FOAR0002)
}?;
Ok(NaiveDateTimeWithOffset::new(new_date_time, a.offset).into())
}
fn op_subtract_year_month_duration_from_date_time_stamp(
a: Rc<chrono::DateTime<chrono::FixedOffset>>,
b: YearMonthDuration,
) -> error::Result<atomic::Atomic> {
let a = a.as_ref();
let date_time = *a;
let new_date_time = if b.months >= 0 {
date_time
.checked_sub_months(chrono::Months::new(b.months as u32))
.ok_or(error::Error::FOAR0002)
} else {
date_time
.checked_add_months(chrono::Months::new(b.months.unsigned_abs() as u32))
.ok_or(error::Error::FOAR0002)
}?;
Ok(new_date_time.into())
}
fn op_subtract_day_time_duration_from_date_time(
a: Rc<NaiveDateTimeWithOffset>,
b: chrono::Duration,
) -> error::Result<atomic::Atomic> {
let new_date_time = a
.as_ref()
.date_time
.checked_sub_signed(b)
.ok_or(error::Error::FOAR0002)?;
Ok(NaiveDateTimeWithOffset::new(new_date_time, a.as_ref().offset).into())
}
fn op_subtract_day_time_duration_from_date_time_stamp(
a: Rc<chrono::DateTime<chrono::FixedOffset>>,
b: chrono::Duration,
) -> error::Result<atomic::Atomic> {
let new_date_time = (*a.as_ref())
.checked_sub_signed(b)
.ok_or(error::Error::FOAR0002)?;
Ok(new_date_time.into())
}
fn op_subtract_year_month_durations(
a: YearMonthDuration,
b: YearMonthDuration,
) -> error::Result<atomic::Atomic> {
let new_months = a
.months
.checked_sub(b.months)
.ok_or(error::Error::FOAR0002)?;
Ok(YearMonthDuration { months: new_months }.into())
}
fn op_subtract_day_time_durations(
a: Rc<chrono::Duration>,
b: Rc<chrono::Duration>,
) -> error::Result<atomic::Atomic> {
let new_duration = (*a.as_ref())
.checked_sub(b.as_ref())
.ok_or(error::Error::FOAR0002)?;
Ok(new_duration.into())
}