#[cfg(feature = "rug")]
pub mod with_rug;

#[cfg(feature = "rug")]
use rug::Float;
#[cfg(feature = "rug")]
pub use with_rug::*;

#[cfg(not(feature = "rug"))]
pub mod regular;
#[cfg(not(feature = "rug"))]
pub use regular::*;

mod rounding;

use crate::ast::Expr;
use crate::errors::KalkError;
use crate::radix;
use wasm_bindgen::prelude::*;

const ACCEPTABLE_COMPARISON_MARGIN: f64 = 0.00000001;

#[macro_export]
#[cfg(not(feature = "rug"))]
macro_rules! float {
    ($x:expr) => {{
        $x.clone() as f64
    }};
}

#[macro_export]
#[cfg(feature = "rug")]
macro_rules! float {
    ($x:expr) => {{
        use rug::Float;
        Float::with_val(63, $x)
    }};
}

#[macro_export]
#[cfg(not(feature = "rug"))]
macro_rules! primitive {
    ($x:expr) => {{
        $x.clone()
    }};
}

#[macro_export]
#[cfg(feature = "rug")]
macro_rules! primitive {
    ($x:expr) => {{
        $x.to_f64()
    }};
}

#[macro_export]
macro_rules! as_number_or_return {
    ($x:expr) => {{
        if let KalkValue::Number(real, imaginary, unit) = $x {
            (
                real,
                if imaginary == -0f64 {
                    float!(0)
                } else {
                    imaginary
                },
                unit,
            )
        } else {
            return Err(KalkError::UnexpectedType(
                $x.get_type_name(),
                vec![String::from("number")],
            ));
        }
    }};
}

#[macro_export]
macro_rules! as_vector_or_return {
    ($x:expr) => {{
        if let KalkValue::Vector(values) = $x {
            if values.len() == 0 {
                return Err(KalkError::Expected(String::from("a non-empty vector")));
            }

            values
        } else {
            return Err(KalkError::UnexpectedType(
                $x.get_type_name(),
                vec![String::from("vector")],
            ));
        }
    }};
}

#[macro_export]
macro_rules! as_number_or_zero {
    ($x:expr) => {{
        use crate::float;
        if let KalkValue::Number(real, imaginary, unit) = $x {
            (real, imaginary, unit)
        } else {
            (float!(0), float!(0), None)
        }
    }};
}

#[wasm_bindgen]
#[derive(Clone)]
pub struct ScientificNotation {
    pub negative: bool,
    pub value: f64,
    pub exponent: i32,
    pub imaginary: bool,
}

#[wasm_bindgen]
#[derive(PartialEq)]
pub enum ComplexNumberType {
    Real,
    Imaginary,
}

#[wasm_bindgen]
impl ScientificNotation {
    #[wasm_bindgen(js_name = toString)]
    pub fn to_js_string(&self) -> String {
        self.to_string()
    }
}

impl std::fmt::Display for ScientificNotation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let sign = if self.negative { "-" } else { "" };
        let digits_and_mul = if self.value == 1f64 {
            String::new()
        } else {
            format!("{}×", format_number(self.value))
        };

        write!(
            f,
            "{}{}10^{} {}",
            sign,
            digits_and_mul,
            self.exponent - 1,
            if self.imaginary { "i" } else { "" }
        )
    }
}

#[derive(PartialEq, Debug, Clone)]
pub enum KalkValue {
    #[cfg(not(feature = "rug"))]
    Number(f64, f64, Option<String>),
    #[cfg(feature = "rug")]
    Number(Float, Float, Option<String>),
    Boolean(bool),
    Vector(Vec<KalkValue>),
    Matrix(Vec<Vec<KalkValue>>),
}

impl std::fmt::Display for KalkValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KalkValue::Number(real, imaginary, _) => {
                let as_str = format_number(primitive!(real));

                if self.has_imaginary() {
                    let imaginary_as_str = format_number(primitive!(imaginary).abs());
                    let sign = if imaginary < &0f64 { "-" } else { "+" };

                    if &as_str == "0" {
                        write!(f, "{}", imaginary_as_str)
                    } else {
                        write!(f, "{} {} {}i", as_str, sign, imaginary_as_str)
                    }
                } else {
                    write!(f, "{}", as_str)
                }
            }
            KalkValue::Boolean(is_true) => {
                if *is_true {
                    write!(f, "true")
                } else {
                    write!(f, "false")
                }
            }
            KalkValue::Vector(values) => {
                write!(
                    f,
                    "({})",
                    values
                        .iter()
                        .map(|x| x.estimate().unwrap_or_else(|| x.to_string()))
                        .collect::<Vec<String>>()
                        .join(", ")
                )
            }
            KalkValue::Matrix(rows) => {
                let mut value_strings = Vec::new();
                let mut longest = 0;
                for row in rows {
                    for value in row {
                        let value_str = value.estimate().unwrap_or_else(|| value.to_string());
                        longest = longest.max(value_str.len());
                        value_strings.push(format!("{},", value_str));
                    }

                    value_strings.last_mut().unwrap().pop(); // Trailing comma
                    value_strings.push(String::from("\n"));
                }

                let mut result = String::from("[");
                for value_str in value_strings {
                    if value_str == "\n" {
                        result.push_str("\n ");
                    } else {
                        result.push_str(&format!("{:width$} ", value_str, width = longest + 1));
                    }
                }

                result.pop(); // Trailing new-line
                result.pop(); // Trailing space
                result.pop(); // Trailing space
                result.pop(); // Trailing comma
                result.push(']');

                write!(f, "{}", result)
            }
        }
    }
}

impl KalkValue {
    pub fn nan() -> Self {
        KalkValue::Number(float!(f64::NAN), float!(0f64), None)
    }

    pub fn get_type_name(&self) -> String {
        match self {
            KalkValue::Number(_, _, _) => String::from("number"),
            KalkValue::Boolean(_) => String::from("boolean"),
            KalkValue::Vector(_) => String::from("vector"),
            KalkValue::Matrix(_) => String::from("matrix"),
        }
    }

    pub fn to_string_big(&self) -> String {
        fn trim_num(num_str: String) -> String {
            num_str
                .trim_end_matches('0')
                .trim_end_matches('.')
                .to_string()
        }

        if let KalkValue::Number(real, imaginary, _) = self {
            if !self.has_imaginary() {
                return trim_num(real.to_string());
            }

            let sign = if imaginary < &0f64 { "-" } else { "+" };
            format!(
                "{} {} {}i",
                spaced(&trim_num(real.to_string())),
                sign,
                spaced(&trim_num(imaginary.to_string()))
            )
        } else {
            trim_num(self.to_string())
        }
    }

    pub fn to_string_real(&self, radix: u8) -> String {
        radix::to_radix_pretty(self.to_f64(), radix)
    }

    pub fn to_string_imaginary(&self, radix: u8, include_i: bool) -> String {
        let value = radix::to_radix_pretty(self.imaginary_to_f64(), radix);
        if include_i && value == "1" {
            String::from("i")
        } else if include_i && value == "-1" {
            String::from("-i")
        } else if include_i {
            format!("{}i", value)
        } else {
            value
        }
    }

    pub fn to_string_pretty_radix(&self, radix: u8) -> String {
        let (real, imaginary, unit) = match self {
            KalkValue::Number(real, imaginary, unit) => (real, imaginary, unit),
            _ => return self.to_string(),
        };

        let real_f64 = self.to_f64();
        let imaginary_f64 = self.imaginary_to_f64();
        if real_f64.is_nan() || imaginary_f64.is_nan() {
            return String::from("Not defined.");
        }

        if real_f64.is_infinite() {
            return format!("{}∞", if real_f64.is_sign_negative() { "-" } else { "" });
        }

        let sci_notation_real = self.to_scientific_notation(ComplexNumberType::Real);
        let mut new_real = real.clone();
        let mut new_imaginary = imaginary.clone();
        let mut has_scientific_notation = false;
        let result_str = if (-6..8).contains(&sci_notation_real.exponent) || real == &0f64 {
            self.to_string_real(radix)
        } else if sci_notation_real.exponent <= -14 {
            new_real = float!(0);
            String::from("0")
        } else if radix == 10 {
            has_scientific_notation = true;

            sci_notation_real.to_string().trim().to_string()
        } else {
            return String::new();
        };

        let sci_notation_imaginary = self.to_scientific_notation(ComplexNumberType::Imaginary);
        let result_str_imaginary = if (-6..8).contains(&sci_notation_imaginary.exponent)
            || imaginary == &0f64
            || imaginary == &1f64
        {
            self.to_string_imaginary(radix, true)
        } else if sci_notation_imaginary.exponent <= -14 {
            new_imaginary = float!(0);
            String::from("0")
        } else if radix == 10 {
            has_scientific_notation = true;

            sci_notation_imaginary.to_string().trim().to_string()
        } else {
            return String::new();
        };

        let mut output = result_str;
        if imaginary != &0f64 && new_imaginary != 0f64 && result_str_imaginary != "0" {
            // If the real value is 0, and there is an imaginary one,
            // clear the output so that the real value is not shown.
            if output == "0" {
                output = String::new();
            }

            // If there is a real value as well
            if !output.is_empty() {
                output.push_str(&format!(
                    " {} {}",
                    if imaginary < &0f64 { "-" } else { "+" },
                    result_str_imaginary.trim_start_matches('-'),
                ));
            } else {
                output.push_str(&result_str_imaginary);
            }
        }

        if let Some(unit) = unit {
            output.push_str(&format!(" {}", unit));
        }

        let new_value = KalkValue::Number(new_real, new_imaginary, unit.clone());

        if let Some(estimate) = new_value.estimate() {
            if estimate != output && radix == 10 {
                output.push_str(&format!(" ≈ {}", estimate));
            }
        } else if has_scientific_notation {
            output.insert_str(0, &format!("{} ≈ ", self));
        }

        output
    }

    pub fn to_string_pretty(&self) -> String {
        self.to_string_pretty_radix(10)
    }

    pub fn to_string_with_unit(&self) -> String {
        match self {
            KalkValue::Number(_, _, unit) => {
                format!("{} {}", self, unit.as_ref().unwrap_or(&String::new()))
            }
            _ => self.to_string(),
        }
    }

    /// Get an estimate of what the number is, eg. 3.141592 => π. Does not work properly with scientific notation.
    pub fn estimate(&self) -> Option<String> {
        let rounded_real = rounding::estimate(self, ComplexNumberType::Real);
        let rounded_imaginary = rounding::estimate(self, ComplexNumberType::Imaginary);

        if let (None, None) = (&rounded_real, &rounded_imaginary) {
            return None;
        }

        let mut output = String::new();
        if let Some(value) = rounded_real {
            output.push_str(&value);
        } else if self.has_real() {
            output.push_str(&self.to_string_real(10));
        }

        let imaginary_value = if let Some(value) = rounded_imaginary {
            Some(value)
        } else if self.has_imaginary() {
            Some(self.to_string_imaginary(10, false))
        } else {
            None
        };

        if let Some(value) = imaginary_value {
            // Clear output if it's just 0.
            if output == "0" {
                output = String::new();
            }

            if value == "0" {
                // If both values ended up being estimated as zero,
                // return zero.
                if output.is_empty() {
                    return Some(String::from("0"));
                }
            } else {
                let sign = if value.starts_with('-') { "-" } else { "+" };
                let value = match value.as_ref() {
                    "1" => String::from("i"),
                    "-1" => String::from("-i"),
                    _ => format!("{}i", value),
                };

                // If there is a real value as well
                if !output.is_empty() {
                    output.push_str(&format!(" {} {}", sign, value.trim_start_matches('-')));
                } else {
                    output.push_str(&value);
                }
            }
        }

        Some(output)
    }

    /// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.
    pub fn round(&self) -> Option<KalkValue> {
        let rounded_real = rounding::round(self, ComplexNumberType::Real);
        let rounded_imaginary = rounding::round(self, ComplexNumberType::Imaginary);

        if let (None, None) = (&rounded_real, &rounded_imaginary) {
            return None;
        }

        let (original_real, original_imaginary, unit) = match self {
            KalkValue::Number(real, imaginary, unit) => (real, imaginary, unit),
            _ => return None,
        };

        Some(KalkValue::Number(
            if let Some(KalkValue::Number(real, _, _)) = rounded_real {
                real
            } else {
                original_real.clone()
            },
            if let Some(KalkValue::Number(_, imaginary, _)) = rounded_imaginary {
                imaginary
            } else {
                original_imaginary.clone()
            },
            unit.clone(),
        ))
    }

    pub fn round_if_needed(self) -> KalkValue {
        if let Some(rounded) = self.round() {
            rounded
        } else {
            self
        }
    }

    pub fn has_real(&self) -> bool {
        if let KalkValue::Number(real, _, _) = self {
            real != &0f64
        } else {
            false
        }
    }

    pub fn has_imaginary(&self) -> bool {
        if let KalkValue::Number(_, imaginary, _) = self {
            imaginary != &0f64 && imaginary != &-0f64
        } else {
            false
        }
    }

    pub fn is_nan(&self) -> bool {
        if let KalkValue::Number(real, imaginary, _) = self {
            real.is_nan() || imaginary.is_nan()
        } else {
            false
        }
    }

    pub fn to_scientific_notation(
        &self,
        complex_number_type: ComplexNumberType,
    ) -> ScientificNotation {
        let value = match complex_number_type {
            ComplexNumberType::Real => self.to_f64(),
            ComplexNumberType::Imaginary => self.imaginary_to_f64(),
        };
        let exponent = value.abs().log10().floor() as i32 + 1;

        ScientificNotation {
            negative: value < 0f64,
            value: value / (10f64.powf(exponent as f64 - 1f64) as f64),
            // I... am not sure what else to do...
            exponent,
            imaginary: complex_number_type == ComplexNumberType::Imaginary,
        }
    }

    pub fn has_unit(&self) -> bool {
        if let KalkValue::Number(_, _, unit) = self {
            unit.is_some()
        } else {
            false
        }
    }

    pub fn get_unit(&self) -> Option<&String> {
        if let KalkValue::Number(_, _, unit) = self {
            unit.as_ref()
        } else {
            None
        }
    }

    pub(crate) fn convert_to_unit(
        &self,
        context: &mut crate::interpreter::Context,
        to_unit: &str,
    ) -> Option<KalkValue> {
        if let KalkValue::Number(real, _, unit) = self {
            let result = crate::interpreter::convert_unit(
                context,
                &Expr::Literal(primitive!(real)),
                unit.as_ref(),
                Some(&to_unit.to_string()),
            );

            if let Ok(num) = result {
                Some(num)
            } else {
                None
            }
        } else {
            None
        }
    }

    pub(crate) fn add(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.add_without_unit(&right)
    }

    pub(crate) fn sub(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.sub_without_unit(&right)
    }

    pub(crate) fn mul(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.mul_without_unit(&right)
    }

    pub(crate) fn div(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.div_without_unit(&right)
    }

    pub(crate) fn pow(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.pow_without_unit(&right)
    }

    pub(crate) fn rem(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        Ok(if let KalkValue::Number(real, _, _) = &self {
            let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
            if let KalkValue::Number(right_real, _, right_unit) = right {
                KalkValue::Number(real % right_real, float!(0f64), right_unit)
            } else {
                self
            }
        } else {
            self
        })
    }

    pub(crate) fn eq(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.eq_without_unit(&right)
    }

    pub(crate) fn not_eq(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.not_eq_without_unit(&right)
    }

    pub(crate) fn greater_than(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.greater_than_without_unit(&right)
    }

    pub(crate) fn less_than(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        self.less_than_without_unit(&right)
    }

    pub(crate) fn greater_or_equals(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        if let (KalkValue::Boolean(greater), KalkValue::Boolean(equal)) = (
            self.greater_than_without_unit(&right)?,
            self.eq_without_unit(&right)?,
        ) {
            Ok(KalkValue::Boolean(greater || equal))
        } else {
            unreachable!()
        }
    }

    pub(crate) fn less_or_equals(
        self,
        context: &mut crate::interpreter::Context,
        rhs: KalkValue,
    ) -> Result<KalkValue, KalkError> {
        let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
        if let (KalkValue::Boolean(less), KalkValue::Boolean(equal)) = (
            self.less_than_without_unit(&right)?,
            self.eq_without_unit(&right)?,
        ) {
            Ok(KalkValue::Boolean(less || equal))
        } else {
            unreachable!()
        }
    }

    pub(crate) fn and(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self, rhs) {
            (KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
                Ok(KalkValue::Boolean(boolean && *boolean_rhs))
            }
            (lhs, rhs) => Err(KalkError::IncompatibleTypesForOperation(
                String::from("and"),
                lhs.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn or(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self, rhs) {
            (KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
                Ok(KalkValue::Boolean(boolean || *boolean_rhs))
            }
            (lhs, rhs) => Err(KalkError::IncompatibleTypesForOperation(
                String::from("or"),
                lhs.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn add_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self.clone(), rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, unit),
            ) => Ok(KalkValue::Number(
                real + real_rhs,
                imaginary + imaginary_rhs,
                unit.clone(),
            )),
            (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
                calculate_matrix(self, rhs, &KalkValue::add_without_unit)
            }
            (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
                calculate_vector(self, rhs, &KalkValue::add_without_unit)
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("addition"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn sub_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self.clone(), rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, unit),
            ) => Ok(KalkValue::Number(
                real - real_rhs,
                imaginary - imaginary_rhs,
                unit.clone(),
            )),
            (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
                calculate_matrix(self, rhs, &KalkValue::sub_without_unit)
            }
            (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
                calculate_vector(self, rhs, &KalkValue::sub_without_unit)
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("subtraction"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn mul_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        // Make sure matrix is always first to avoid having to match
        // different orders in the next match expression.
        let (lhs, rhs) = match (&self, rhs) {
            (KalkValue::Matrix(_), KalkValue::Matrix(_)) => (&self, rhs),
            (_, KalkValue::Matrix(_)) => (rhs, &self),
            _ => (&self, rhs),
        };

        match (lhs, rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, unit),
            ) => Ok(KalkValue::Number(
                // (a + bi)(c + di) = ac + adi + bci + bdi²
                real.clone() * real_rhs - imaginary.clone() * imaginary_rhs,
                real.clone() * imaginary_rhs + imaginary * real_rhs,
                unit.clone(),
            )),
            (KalkValue::Matrix(_), KalkValue::Number(_, _, _)) => {
                calculate_matrix(lhs.clone(), rhs, &KalkValue::mul_without_unit)
            }
            (KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => {
                if rows.first().unwrap().len() != values_rhs.len() {
                    return Err(KalkError::IncompatibleVectorsMatrixes);
                }

                let mut new_values: Vec<KalkValue> = Vec::new();
                for row in rows {
                    let mut sum = KalkValue::from(0);
                    for (x, y) in row.iter().zip(values_rhs) {
                        sum = sum
                            .clone()
                            .add_without_unit(&x.clone().mul_without_unit(y)?)?;
                    }

                    new_values.push(sum);
                }

                Ok(KalkValue::Vector(new_values))
            }
            (KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
                let lhs_columns = rows.first().unwrap();
                if lhs_columns.len() != rows_rhs.len() {
                    return Err(KalkError::IncompatibleVectorsMatrixes);
                }

                let rhs_columns = rows_rhs.first().unwrap();
                let mut result = vec![vec![KalkValue::from(0f64); rhs_columns.len()]; rows.len()];

                // For every row in lhs
                for i in 0..rows.len() {
                    // For every column in rhs
                    for j in 0..rhs_columns.len() {
                        let mut sum = KalkValue::from(0f64);

                        // For every value in the current lhs row
                        for (k, value) in rows[i].iter().enumerate() {
                            let value_rhs = &rows_rhs[k][j];
                            sum =
                                sum.add_without_unit(&value.clone().mul_without_unit(value_rhs)?)?;
                        }

                        result[i][j] = sum;
                    }
                }

                Ok(KalkValue::Matrix(result))
            }
            (KalkValue::Vector(values), KalkValue::Number(_, _, _)) => {
                let mut multiplied_values = Vec::new();
                for value in values {
                    multiplied_values.push(value.clone().mul_without_unit(rhs)?);
                }

                Ok(KalkValue::Vector(multiplied_values))
            }
            (KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
                if values.len() != values_rhs.len() {
                    return Err(KalkError::IncompatibleVectorsMatrixes);
                }

                let mut sum = KalkValue::from(0f64);
                for (value, value_rhs) in values.iter().zip(values_rhs) {
                    sum = sum.add_without_unit(&value.clone().mul_without_unit(value_rhs)?)?;
                }

                Ok(sum)
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("multiplication"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn div_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self.clone(), rhs.clone()) {
            (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, unit)) => {
                // Avoid unecessary calculations
                if !self.has_imaginary() && !rhs.has_imaginary() {
                    Ok(KalkValue::Number(real / real_rhs, float!(0f64), unit))
                } else {
                    // Multiply both the numerator and denominator
                    // with the conjugate of the denominator, and divide.
                    let conjugate = rhs.get_conjugate()?;
                    let (numerator, numerator_imaginary) =
                        self.mul_without_unit(&conjugate)?.values();
                    let (denominator, _) = rhs.clone().mul_without_unit(&conjugate)?.values();
                    Ok(KalkValue::Number(
                        numerator / denominator.clone(),
                        numerator_imaginary / denominator,
                        unit,
                    ))
                }
            }
            (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
                calculate_matrix(self, rhs, &KalkValue::div_without_unit)
            }
            (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
                calculate_vector(self, rhs, &KalkValue::div_without_unit)
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("division"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn pow_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self.clone(), rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, unit),
            ) => {
                if self.has_imaginary()
                    || imaginary_rhs != &0f64
                    || (real < 0f64 && real_rhs < &1f64)
                {
                    let a = real;
                    let b = imaginary;
                    let c = real_rhs;
                    let d = imaginary_rhs;
                    let arg = crate::prelude::funcs::arg(self)?.values().0;
                    let raised = a.clone() * a + b.clone() * b;
                    let exp =
                        pow(raised.clone(), c.clone() / 2f64) * (-d.clone() * arg.clone()).exp();
                    let polar = c * arg + d.clone() / 2f64 * raised.ln();

                    Ok(KalkValue::Number(
                        polar.clone().cos() * exp.clone(),
                        polar.sin() * exp,
                        unit.clone(),
                    ))
                } else {
                    Ok(KalkValue::Number(
                        pow(real, real_rhs.clone()),
                        float!(0),
                        unit.clone(),
                    ))
                }
            }
            (KalkValue::Matrix(rows), KalkValue::Number(real, _, _)) => {
                if real < &0f64 || real.clone().fract() > 0.000001f64 {
                    return Err(KalkError::Expected(String::from("a positive integer")));
                }

                if rhs.has_imaginary() {
                    return Err(KalkError::ExpectedReal);
                }

                if rows.len() != rows.first().unwrap().len() {
                    return Err(KalkError::Expected(String::from("a square matrix")));
                }

                if real == &0f64 {
                    return Ok(KalkValue::from(1f64));
                }

                let mut result = KalkValue::from(1f64);
                for _ in 0..primitive!(real) as i32 {
                    result = result.mul_without_unit(&self)?;
                }

                Ok(result)
            }
            (KalkValue::Number(_, _, _), KalkValue::Matrix(rows)) => {
                let mut new_rows = Vec::new();
                for row in rows {
                    new_rows.push(Vec::new());
                    for item in row {
                        new_rows
                            .last_mut()
                            .unwrap()
                            .push(self.clone().pow_without_unit(item)?);
                    }
                }

                Ok(KalkValue::Matrix(new_rows))
            }
            (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
                calculate_vector(self, rhs, &KalkValue::pow_without_unit)
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("pow"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn eq_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self, rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, _),
            ) => Ok(KalkValue::Boolean(
                (real.clone() - real_rhs.clone()).abs() < ACCEPTABLE_COMPARISON_MARGIN
                    && (imaginary.clone() - imaginary_rhs.clone()).abs()
                        < ACCEPTABLE_COMPARISON_MARGIN,
            )),
            (KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
                Ok(KalkValue::Boolean(boolean == boolean_rhs))
            }
            (KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
                let mut matrices_are_equal = true;
                for (row, row_rhs) in rows.iter().zip(rows_rhs) {
                    for (value, value_rhs) in row.iter().zip(row_rhs) {
                        if let KalkValue::Boolean(are_equal) = value.eq_without_unit(value_rhs)? {
                            if !are_equal {
                                matrices_are_equal = false;
                            }
                        }
                    }
                }

                Ok(KalkValue::Boolean(matrices_are_equal))
            }

            (KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
                let mut vecs_are_equal = true;
                for (value, value_rhs) in values.iter().zip(values_rhs) {
                    if let KalkValue::Boolean(are_equal) = value.eq_without_unit(value_rhs)? {
                        if !are_equal {
                            vecs_are_equal = false;
                        }
                    }
                }

                Ok(KalkValue::Boolean(vecs_are_equal))
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("equal"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn not_eq_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        match (self, rhs) {
            (
                KalkValue::Number(real, imaginary, _),
                KalkValue::Number(real_rhs, imaginary_rhs, _),
            ) => Ok(KalkValue::Boolean(
                (real.clone() - real_rhs.clone()).abs() > ACCEPTABLE_COMPARISON_MARGIN
                    || (imaginary.clone() - imaginary_rhs.clone()).abs()
                        > ACCEPTABLE_COMPARISON_MARGIN,
            )),
            (KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
                Ok(KalkValue::Boolean(boolean != boolean_rhs))
            }
            (KalkValue::Vector(_), KalkValue::Vector(_))
            | (KalkValue::Matrix(_), KalkValue::Matrix(_)) => {
                if let KalkValue::Boolean(boolean) = self.eq_without_unit(rhs)? {
                    Ok(KalkValue::Boolean(!boolean))
                } else {
                    unreachable!()
                }
            }
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("not equal"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn greater_than_without_unit(
        &self,
        rhs: &KalkValue,
    ) -> Result<KalkValue, KalkError> {
        if self.has_imaginary() || rhs.has_imaginary() {
            return Err(KalkError::ExpectedReal);
        }

        match (self, rhs) {
            (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => Ok(
                KalkValue::Boolean(real.clone() - real_rhs.clone() > ACCEPTABLE_COMPARISON_MARGIN),
            ),
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("greater than"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub(crate) fn less_than_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
        if self.has_imaginary() || rhs.has_imaginary() {
            return Err(KalkError::ExpectedReal);
        }

        match (self, rhs) {
            (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => Ok(
                KalkValue::Boolean(real.clone() - real_rhs.clone() < -ACCEPTABLE_COMPARISON_MARGIN),
            ),
            _ => Err(KalkError::IncompatibleTypesForOperation(
                String::from("less than"),
                self.get_type_name(),
                rhs.get_type_name(),
            )),
        }
    }

    pub fn get_conjugate(&self) -> Result<KalkValue, KalkError> {
        match self {
            KalkValue::Number(real, imaginary, unit) => Ok(KalkValue::Number(
                real.clone(),
                imaginary.clone() * (-1f64),
                unit.clone(),
            )),
            _ => Err(KalkError::UnexpectedType(
                self.get_type_name(),
                vec![String::from("number")],
            )),
        }
    }
}

pub fn format_number(input: f64) -> String {
    let rounded = format!("{:.1$}", input, 10);
    let result = if rounded.contains('.') {
        rounded
            .trim_end_matches('0')
            .trim_end_matches('.')
            .to_string()
    } else {
        rounded
    };

    spaced(&result)
}

fn calculate_vector(
    x: KalkValue,
    y: &KalkValue,
    action: &dyn Fn(KalkValue, &KalkValue) -> Result<KalkValue, KalkError>,
) -> Result<KalkValue, KalkError> {
    match (x, y) {
        (KalkValue::Vector(values), KalkValue::Number(_, _, _)) => {
            let mut new_values = Vec::new();
            for value in values {
                new_values.push(action(value.clone(), y)?);
            }

            Ok(KalkValue::Vector(new_values))
        }
        (KalkValue::Number(_, _, _), KalkValue::Vector(values_rhs)) => {
            let mut new_values = Vec::new();
            for value in values_rhs {
                new_values.push(action(y.clone(), value)?);
            }

            Ok(KalkValue::Vector(new_values))
        }
        (KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
            if values.len() != values_rhs.len() {
                return Err(KalkError::IncompatibleVectorsMatrixes);
            }

            let mut new_values = Vec::new();
            for (value, value_rhs) in values.iter().zip(values_rhs) {
                new_values.push(action(value.clone(), value_rhs)?);
            }

            Ok(KalkValue::Vector(new_values))
        }
        (x, y) => Err(KalkError::IncompatibleTypesForOperation(
            String::from("vector operation"),
            x.get_type_name(),
            y.get_type_name(),
        )),
    }
}

fn calculate_matrix(
    x: KalkValue,
    y: &KalkValue,
    action: &dyn Fn(KalkValue, &KalkValue) -> Result<KalkValue, KalkError>,
) -> Result<KalkValue, KalkError> {
    // Make sure matrix is always first to avoid having to match
    // different orders in the next match expression.
    let (x, y) = match (&x, y) {
        (KalkValue::Matrix(_), KalkValue::Matrix(_)) => (&x, y),
        (_, KalkValue::Matrix(_)) => (y, &x),
        _ => (&x, y),
    };

    match (x, y) {
        (KalkValue::Matrix(rows), KalkValue::Number(_, _, _)) => {
            let mut new_rows = Vec::new();
            for row in rows {
                new_rows.push(Vec::new());
                for item in row {
                    new_rows.last_mut().unwrap().push(action(item.clone(), y)?);
                }
            }

            Ok(KalkValue::Matrix(new_rows))
        }
        (KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => {
            if rows.len() != values_rhs.len() {
                return Err(KalkError::IncompatibleVectorsMatrixes);
            }

            let mut new_rows = Vec::new();
            for (i, row) in rows.iter().enumerate() {
                new_rows.push(Vec::new());
                for value in row {
                    new_rows
                        .last_mut()
                        .unwrap()
                        .push(action(value.clone(), &values_rhs[i])?)
                }
            }

            Ok(KalkValue::Matrix(new_rows))
        }
        (KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
            if rows.len() != rows_rhs.len()
                || rows.first().unwrap().len() != rows_rhs.first().unwrap().len()
            {
                return Err(KalkError::IncompatibleVectorsMatrixes);
            }

            let mut new_rows = Vec::new();
            for (i, row) in rows.iter().enumerate() {
                new_rows.push(Vec::new());
                for (j, value) in row.iter().enumerate() {
                    new_rows
                        .last_mut()
                        .unwrap()
                        .push(action(value.clone(), &rows_rhs[i][j])?)
                }
            }

            Ok(KalkValue::Matrix(new_rows))
        }
        _ => Err(KalkError::IncompatibleTypesForOperation(
            String::from("matrix operation"),
            x.get_type_name(),
            y.get_type_name(),
        )),
    }
}

fn spaced(number_str: &str) -> String {
    let dot_pos = number_str.find('.');
    let integer_boundary = if let Some(dot_pos) = dot_pos {
        dot_pos
    } else {
        number_str.len()
    };

    if integer_boundary < 5 {
        return number_str.into();
    }

    let bytes = number_str.as_bytes();
    let mut at_decimals = dot_pos.is_some();
    let mut i = number_str.len() - 1;
    let mut c = 0;
    let mut new_str = String::new();
    while i > 0 {
        if bytes[i] as char == '.' {
            new_str.push('.');
            at_decimals = false;
            i -= 1;
            c = 0;
            continue;
        }

        if !at_decimals && c == 3 {
            new_str.push(' ');
            c = 0;
        }

        new_str.push(bytes[i] as char);
        c += 1;
        i -= 1;
    }

    if c == 3 {
        new_str.push(' ');
    }
    new_str.push(bytes[0] as char);

    new_str.chars().rev().collect::<String>()
}

fn calculate_unit(
    context: &mut crate::interpreter::Context,
    left: &KalkValue,
    right: KalkValue,
) -> Option<KalkValue> {
    if let (KalkValue::Number(_, _, unit_left), KalkValue::Number(real_right, imaginary_right, _)) =
        (left, &right)
    {
        if left.has_unit() && right.has_unit() {
            right.convert_to_unit(context, unit_left.as_ref().unwrap())
        } else {
            Some(KalkValue::Number(
                real_right.clone(),
                imaginary_right.clone(),
                unit_left.clone(),
            ))
        }
    } else {
        None
    }
}

#[cfg(not(feature = "rug"))]
fn pow(x: f64, y: f64) -> f64 {
    x.powf(y)
}

#[cfg(feature = "rug")]
fn pow(x: Float, y: Float) -> Float {
    use rug::ops::Pow;
    x.pow(y)
}

impl From<ScientificNotation> for String {
    fn from(val: ScientificNotation) -> Self {
        val.to_string()
    }
}

/*impl std::iter::Sum<KalkValue> for KalkValue {
    fn sum<I>(iter: I) -> KalkValue
    where
        I: std::iter::Iterator<Item = KalkValue>,
    {
        let mut sum = KalkValue::from(0f64);
        for x in iter {
            sum = sum.add_without_unit(&x);
        }

        sum
    }
}*/

impl From<KalkValue> for String {
    fn from(val: KalkValue) -> Self {
        val.to_string()
    }
}

impl From<KalkValue> for f64 {
    fn from(val: KalkValue) -> Self {
        val.to_f64()
    }
}

impl From<f64> for KalkValue {
    fn from(x: f64) -> Self {
        KalkValue::Number(float!(x), float!(0), None)
    }
}

impl From<f32> for KalkValue {
    fn from(x: f32) -> Self {
        KalkValue::Number(float!(x), float!(0), None)
    }
}

impl From<i128> for KalkValue {
    fn from(x: i128) -> Self {
        KalkValue::Number(float!(x), float!(0), None)
    }
}

impl From<i64> for KalkValue {
    fn from(x: i64) -> Self {
        KalkValue::Number(float!(x), float!(0), None)
    }
}

impl From<i32> for KalkValue {
    fn from(x: i32) -> Self {
        KalkValue::Number(float!(x), float!(0), None)
    }
}

#[cfg(test)]
mod tests {
    use crate::kalk_value::{spaced, KalkValue};
    use crate::test_helpers::cmp;

    #[test]
    fn test_spaced() {
        assert_eq!(spaced("1"), String::from("1"));
        assert_eq!(spaced("10"), String::from("10"));
        assert_eq!(spaced("100"), String::from("100"));
        assert_eq!(spaced("1000"), String::from("1000"));
        assert_eq!(spaced("10000"), String::from("10 000"));
        assert_eq!(spaced("100000"), String::from("100 000"));
        assert_eq!(spaced("1000000"), String::from("1 000 000"));
        assert_eq!(spaced("10000000"), String::from("10 000 000"));
        assert_eq!(spaced("1.12345"), String::from("1.12345"));
        assert_eq!(spaced("10.12345"), String::from("10.12345"));
        assert_eq!(spaced("100.12345"), String::from("100.12345"));
        assert_eq!(spaced("1000.12345"), String::from("1000.12345"));
        assert_eq!(spaced("10000.12345"), String::from("10 000.12345"));
        assert_eq!(spaced("100000.12345"), String::from("100 000.12345"));
        assert_eq!(spaced("1000000.12345"), String::from("1 000 000.12345"));
        assert_eq!(spaced("10000000.12345"), String::from("10 000 000.12345"));
    }

    #[test]
    fn test_add_complex() {
        let in_out = vec![
            ((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
            ((2f64, 0f64), (3f64, 4f64), (5f64, 4f64)),
            ((0f64, 2f64), (3f64, 4f64), (3f64, 6f64)),
            ((3f64, -2f64), (-3f64, 4f64), (0f64, 2f64)),
        ];

        for (a, b, expected_result) in in_out {
            let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
                .add_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
                .unwrap();
            assert_eq!(actual_result.to_f64(), expected_result.0);
            assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
        }
    }

    #[test]
    fn test_sub_complex() {
        let in_out = vec![
            ((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
            ((2f64, 0f64), (3f64, 4f64), (-1f64, -4f64)),
            ((0f64, 2f64), (3f64, 4f64), (-3f64, -2f64)),
            ((3f64, -2f64), (-3f64, 4f64), (6f64, -6f64)),
        ];

        for (a, b, expected_result) in in_out {
            let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
                .sub_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
                .unwrap();
            assert_eq!(actual_result.to_f64(), expected_result.0);
            assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
        }
    }

    #[test]
    fn test_mul_complex() {
        let in_out = vec![
            ((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
            ((2f64, 0f64), (3f64, 4f64), (6f64, 8f64)),
            ((0f64, 2f64), (3f64, 4f64), (-8f64, 6f64)),
            ((3f64, -2f64), (-3f64, 4f64), (-1f64, 18f64)),
        ];

        for (a, b, expected_result) in in_out {
            let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
                .mul_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
                .unwrap();
            assert_eq!(actual_result.to_f64(), expected_result.0);
            assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
        }
    }

    #[test]
    fn test_div_complex() {
        let in_out = vec![
            ((2f64, 0f64), (3f64, 4f64), (0.24f64, -0.32f64)),
            ((0f64, 2f64), (3f64, 4f64), (0.32f64, 0.24f64)),
            ((3f64, -2f64), (-3f64, 4f64), (-0.68f64, -0.24f64)),
        ];

        for (a, b, expected_result) in in_out {
            let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
                .div_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
                .unwrap();
            assert_eq!(actual_result.to_f64(), expected_result.0);
            assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
        }
    }

    #[test]
    fn test_pow_complex() {
        let in_out = vec![
            ((2f64, 0f64), (0f64, 3f64), (-0.4869944f64, 0.8734050f64)),
            ((2f64, 0f64), (2f64, 3f64), (-1.9479776f64, 3.4936203f64)),
            ((0f64, 2f64), (0f64, 3f64), (-0.0043748f64, 0.0078460f64)),
            ((3f64, 2f64), (0f64, 3f64), (-0.1304148f64, -0.111153f64)),
            ((3f64, 2f64), (4f64, 3f64), (28.8577819f64, -2.422530f64)),
            (
                (3f64, 0f64),
                (0f64, 1f64 / 3f64),
                (0.9336932f64, 0.3580738f64),
            ),
            (
                (3f64, 4f64),
                (0f64, 1f64 / 4f64),
                (0.7297490f64, 0.3105648f64),
            ),
        ];

        for (a, b, expected_result) in in_out {
            let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
                .pow_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
                .unwrap();
            assert!(cmp(actual_result.to_f64(), expected_result.0));
            assert!(cmp(actual_result.imaginary_to_f64(), expected_result.1));
        }
    }

    #[test]
    fn test_to_string_pretty() {
        let in_out = vec![
            (0.99999, 0.0, "0.99999 ≈ 1"),
            (-0.99999, 0.0, "-0.99999 ≈ -1"),
            (0.0, 0.99999, "0.99999i ≈ i"),
            (0.000000001, 0.0, "10^-9 ≈ 0"),
            (0.0, 0.000000001, "10^-9 i ≈ 0"),
            (0.99999, 0.999999, "0.99999 + 0.999999i ≈ 1 + i"),
            (1.0, 0.99999, "1 + 0.99999i ≈ 1 + i"),
            (-0.99999, 0.999999, "-0.99999 + 0.999999i ≈ -1 + i"),
            (0.99999, -0.999999, "0.99999 - 0.999999i ≈ 1 - i"),
            (-1.0, 0.99999, "-1 + 0.99999i ≈ -1 + i"),
            (1.0, -0.99999, "1 - 0.99999i ≈ 1 - i"),
            (-0.99999, 1.0, "-0.99999 + i ≈ -1 + i"),
            (0.99999, -1.0, "0.99999 - i ≈ 1 - i"),
            (0.000000001, 0.000000001, "10^-9 + 10^-9 i ≈ 0"),
            (1.0, 0.000000001, "1 + 10^-9 i ≈ 1"),
            (0.000000001, 1.0, "10^-9 + i ≈ i"),
            (-1.0, 0.000000001, "-1 + 10^-9 i ≈ -1"),
            (0.000000001, -1.0, "10^-9 - i ≈ -i"),
            (10e-17, 1.0, "i"),
            (1.0, 10e-17, "1"),
            (10e-16, 0.0, "0"),
            (3.00000000004, 0.0, "3"),
        ];
        for (real, imaginary, output) in in_out {
            let result =
                KalkValue::Number(float!(real), float!(imaginary), None).to_string_pretty();
            assert_eq!(output, result);
        }
    }
}
