use crate::constants::{MAX_I32_SCALE, MAX_PRECISION_I32, POWERS_10};
use crate::decimal::{CalculationResult, Decimal};
use crate::ops::common::{Buf12, Buf16, Buf24, Dec64};

pub(crate) fn rem_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
    if d2.is_zero() {
        return CalculationResult::DivByZero;
    }
    if d1.is_zero() {
        return CalculationResult::Ok(Decimal::ZERO);
    }

    // We handle the structs a bit different here. Firstly, we ignore both the sign/scale of d2.
    // This is because during a remainder operation we do not care about the sign of the divisor
    // and only concern ourselves with that of the dividend.
    let mut d1 = Dec64::new(d1);
    let d2_scale = d2.scale();
    let mut d2 = Buf12::from_decimal(d2);

    let cmp = crate::ops::cmp::cmp_internal(
        &d1,
        &Dec64 {
            negative: d1.negative,
            scale: d2_scale,
            hi: d2.hi(),
            low64: d2.low64(),
        },
    );
    match cmp {
        core::cmp::Ordering::Equal => {
            // Same numbers meaning that remainder is zero
            return CalculationResult::Ok(Decimal::ZERO);
        }
        core::cmp::Ordering::Less => {
            // d1 < d2, e.g. 1/2. This means that the result is the value of d1
            return CalculationResult::Ok(d1.to_decimal());
        }
        core::cmp::Ordering::Greater => {}
    }

    // At this point we know that the dividend > divisor and that they are both non-zero.
    let mut scale = d1.scale as i32 - d2_scale as i32;
    if scale > 0 {
        // Scale up the divisor
        loop {
            let power = if scale >= MAX_I32_SCALE {
                POWERS_10[9]
            } else {
                POWERS_10[scale as usize]
            } as u64;

            let mut tmp = d2.lo() as u64 * power;
            d2.set_lo(tmp as u32);
            tmp >>= 32;
            tmp = tmp.wrapping_add((d2.mid() as u64 + ((d2.hi() as u64) << 32)) * power);
            d2.set_mid(tmp as u32);
            d2.set_hi((tmp >> 32) as u32);

            // Keep scaling if there is more to go
            scale -= MAX_I32_SCALE;
            if scale <= 0 {
                break;
            }
        }
        scale = 0;
    }

    loop {
        // If the dividend is smaller than the divisor then try to scale that up first
        if scale < 0 {
            let mut quotient = Buf12 {
                data: [d1.lo(), d1.mid(), d1.hi],
            };
            loop {
                // Figure out how much we can scale by
                let power_scale;
                if let Some(u) = quotient.find_scale(MAX_PRECISION_I32 + scale) {
                    if u >= POWERS_10.len() {
                        power_scale = 9;
                    } else {
                        power_scale = u;
                    }
                } else {
                    return CalculationResult::Overflow;
                };
                if power_scale == 0 {
                    break;
                }
                let power = POWERS_10[power_scale] as u64;
                scale += power_scale as i32;

                let mut tmp = quotient.data[0] as u64 * power;
                quotient.data[0] = tmp as u32;
                tmp >>= 32;
                quotient.set_high64(tmp.wrapping_add(quotient.high64().wrapping_mul(power)));
                if power_scale != 9 {
                    break;
                }
                if scale >= 0 {
                    break;
                }
            }
            d1.low64 = quotient.low64();
            d1.hi = quotient.data[2];
            d1.scale = d2_scale;
        }

        // if the high portion is empty then return the modulus of the bottom portion
        if d1.hi == 0 {
            d1.low64 %= d2.low64();
            return CalculationResult::Ok(d1.to_decimal());
        } else if (d2.mid() | d2.hi()) == 0 {
            let mut tmp = d1.high64();
            tmp = ((tmp % d2.lo() as u64) << 32) | (d1.lo() as u64);
            d1.low64 = tmp % d2.lo() as u64;
            d1.hi = 0;
        } else {
            // Divisor is > 32 bits
            return rem_full(&d1, &d2, scale);
        }

        if scale >= 0 {
            break;
        }
    }

    CalculationResult::Ok(d1.to_decimal())
}

fn rem_full(d1: &Dec64, d2: &Buf12, scale: i32) -> CalculationResult {
    let mut scale = scale;

    // First normalize the divisor
    let shift = if d2.hi() == 0 {
        d2.mid().leading_zeros()
    } else {
        d2.hi().leading_zeros()
    };

    let mut buffer = Buf24::zero();
    let mut overflow = 0u32;
    buffer.set_low64(d1.low64 << shift);
    buffer.set_mid64(((d1.mid() as u64).wrapping_add((d1.hi as u64) << 32)) >> (32 - shift));
    let mut upper = 3; // We start at 3 due to bit shifting

    while scale < 0 {
        let power = if -scale >= MAX_I32_SCALE {
            POWERS_10[9]
        } else {
            POWERS_10[-scale as usize]
        } as u64;
        let mut tmp64 = buffer.data[0] as u64 * power;
        buffer.data[0] = tmp64 as u32;

        for (index, part) in buffer.data.iter_mut().enumerate().skip(1) {
            if index > upper {
                break;
            }
            tmp64 >>= 32;
            tmp64 = tmp64.wrapping_add((*part as u64).wrapping_mul(power));
            *part = tmp64 as u32;
        }
        // If we have overflow then also process that
        if upper == 6 {
            tmp64 >>= 32;
            tmp64 = tmp64.wrapping_add((overflow as u64).wrapping_mul(power));
            overflow = tmp64 as u32;
        }

        // Make sure the high bit is not set
        if tmp64 > 0x7FFF_FFFF {
            upper += 1;
            if upper > 5 {
                overflow = (tmp64 >> 32) as u32;
            } else {
                buffer.data[upper] = (tmp64 >> 32) as u32;
            }
        }
        scale += MAX_I32_SCALE;
    }

    // TODO: Optimize slice logic

    let mut tmp = Buf16::zero();
    if d2.hi() == 0 {
        // 64 bit divisor so we adjust accordingly
        let divisor = d2.low64() << shift;

        // Do some division
        if upper == 6 {
            upper -= 1;

            tmp.data = [buffer.data[4], buffer.data[5], overflow, 0];
            tmp.partial_divide_64(divisor);
            buffer.data[4] = tmp.data[0];
            buffer.data[5] = tmp.data[1];
        }
        if upper == 5 {
            upper -= 1;
            tmp.data = [buffer.data[3], buffer.data[4], buffer.data[5], 0];
            tmp.partial_divide_64(divisor);
            buffer.data[3] = tmp.data[0];
            buffer.data[4] = tmp.data[1];
            buffer.data[5] = tmp.data[2];
        }
        if upper == 4 {
            tmp.data = [buffer.data[2], buffer.data[3], buffer.data[4], 0];
            tmp.partial_divide_64(divisor);
            buffer.data[2] = tmp.data[0];
            buffer.data[3] = tmp.data[1];
            buffer.data[4] = tmp.data[2];
        }

        tmp.data = [buffer.data[1], buffer.data[2], buffer.data[3], 0];
        tmp.partial_divide_64(divisor);
        buffer.data[1] = tmp.data[0];
        buffer.data[2] = tmp.data[1];
        buffer.data[3] = tmp.data[2];

        tmp.data = [buffer.data[0], buffer.data[1], buffer.data[2], 0];
        tmp.partial_divide_64(divisor);
        buffer.data[0] = tmp.data[0];
        buffer.data[1] = tmp.data[1];
        buffer.data[2] = tmp.data[2];

        let low64 = buffer.low64() >> shift;
        CalculationResult::Ok(Decimal::from_parts(
            low64 as u32,
            (low64 >> 32) as u32,
            0,
            d1.negative,
            d1.scale,
        ))
    } else {
        let divisor_low64 = d2.low64() << shift;
        let divisor = Buf12 {
            data: [
                divisor_low64 as u32,
                (divisor_low64 >> 32) as u32,
                (((d2.mid() as u64) + ((d2.hi() as u64) << 32)) >> (32 - shift)) as u32,
            ],
        };

        // Do some division
        if upper == 6 {
            upper -= 1;
            tmp.data = [buffer.data[3], buffer.data[4], buffer.data[5], overflow];
            tmp.partial_divide_96(&divisor);
            buffer.data[3] = tmp.data[0];
            buffer.data[4] = tmp.data[1];
            buffer.data[5] = tmp.data[2];
        }
        if upper == 5 {
            upper -= 1;
            tmp.data = [buffer.data[2], buffer.data[3], buffer.data[4], buffer.data[5]];
            tmp.partial_divide_96(&divisor);
            buffer.data[2] = tmp.data[0];
            buffer.data[3] = tmp.data[1];
            buffer.data[4] = tmp.data[2];
            buffer.data[5] = tmp.data[3];
        }
        if upper == 4 {
            tmp.data = [buffer.data[1], buffer.data[2], buffer.data[3], buffer.data[4]];
            tmp.partial_divide_96(&divisor);
            buffer.data[1] = tmp.data[0];
            buffer.data[2] = tmp.data[1];
            buffer.data[3] = tmp.data[2];
            buffer.data[4] = tmp.data[3];
        }

        tmp.data = [buffer.data[0], buffer.data[1], buffer.data[2], buffer.data[3]];
        tmp.partial_divide_96(&divisor);
        buffer.data[0] = tmp.data[0];
        buffer.data[1] = tmp.data[1];
        buffer.data[2] = tmp.data[2];
        buffer.data[3] = tmp.data[3];

        let low64 = (buffer.low64() >> shift) + ((buffer.data[2] as u64) << (32 - shift) << 32);
        CalculationResult::Ok(Decimal::from_parts(
            low64 as u32,
            (low64 >> 32) as u32,
            buffer.data[2] >> shift,
            d1.negative,
            d1.scale,
        ))
    }
}
