use crate::{Context, Error};
use tracing::{debug, trace, warn};
use crate::shared_functions::trace_message;
use regex::Regex;
use std::sync::LazyLock;
use std::str::FromStr;
use std::fmt::{Display, Formatter};
use f128::f128;
use exmex::{Express, MakeOperators, Operator, BinOp};
use num_traits::float::Float;

#[derive(Clone, Copy, Debug)]
pub struct F128(pub f128);

impl Default for F128 {
    fn default() -> Self {
        F128(f128::new(0.0))
    }
}

impl FromStr for F128 {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match f128::parse(s) {
            Ok(v) => Ok(F128(v)),
            Err(e) => Err(format!("{:?}", e))
        }
    }
}

impl Display for F128 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Clone, Debug)]
pub struct F128OpsFactory;
impl MakeOperators<F128> for F128OpsFactory {
    fn make<'a>() -> Vec<Operator<'a, F128>> {
        vec![
            Operator::make_bin("^", BinOp { apply: |a, b| F128(a.0.powf(b.0)), prio: 4, is_commutative: false }),
            Operator::make_bin("*", BinOp { apply: |a, b| F128(a.0 * b.0), prio: 2, is_commutative: true }),
            Operator::make_bin("/", BinOp { apply: |a, b| F128(a.0 / b.0), prio: 3, is_commutative: false }),
            Operator::make_bin_unary("+", BinOp { apply: |a, b| F128(a.0 + b.0), prio: 0, is_commutative: true }, |a| a),
            Operator::make_bin_unary("-", BinOp { apply: |a, b| F128(a.0 - b.0), prio: 1, is_commutative: false }, |a| F128(-a.0)),
            Operator::make_unary("sin", |a| F128(a.0.sin())),
            Operator::make_unary("cos", |a| F128(a.0.cos())),
            Operator::make_unary("tan", |a| F128(a.0.tan())),
            Operator::make_unary("floor", |a| F128(a.0.floor())),
            Operator::make_unary("round", |a| F128(a.0.round())),
            Operator::make_unary("ceil", |a| F128(a.0.ceil())),
            Operator::make_unary("trunc", |a| F128(a.0.trunc())),
            Operator::make_unary("fract", |a| F128(a.0.fract())),
            Operator::make_unary("exp", |a| F128(a.0.exp())),
            Operator::make_unary("sqrt", |a| F128(a.0.sqrt())),
            Operator::make_unary("cbrt", |a| F128(a.0.cbrt())),
            Operator::make_unary("ln", |a| F128(a.0.ln())),
            Operator::make_unary("log2", |a| F128(a.0.log2())),
            Operator::make_unary("log10", |a| F128(a.0.log10())),
            Operator::make_unary("abs", |a| F128(a.0.abs())),
            Operator::make_unary("signum", |a| F128(a.0.signum())),
            Operator::make_constant("PI", F128(f128::new(std::f64::consts::PI))),
            Operator::make_constant("π", F128(f128::new(std::f64::consts::PI))),
            Operator::make_constant("E", F128(f128::new(std::f64::consts::E))),
            Operator::make_constant("e", F128(f128::new(std::f64::consts::E))),
            Operator::make_constant("TAU", F128(f128::new(std::f64::consts::TAU))),
            Operator::make_constant("τ", F128(f128::new(std::f64::consts::TAU))),
        ]
    }
}

#[derive(Clone, Debug)]
pub struct F256OpsFactory;
impl MakeOperators<f256::f256> for F256OpsFactory {
    fn make<'a>() -> Vec<Operator<'a, f256::f256>> {
        vec![
            Operator::make_bin("^", BinOp { apply: |a, b| a.powf(&b), prio: 4, is_commutative: false }),
            Operator::make_bin("*", BinOp { apply: |a, b| a * b, prio: 2, is_commutative: true }),
            Operator::make_bin("/", BinOp { apply: |a, b| a / b, prio: 3, is_commutative: false }),
            Operator::make_bin_unary("+", BinOp { apply: |a, b| a + b, prio: 0, is_commutative: true }, |a| a),
            Operator::make_bin_unary("-", BinOp { apply: |a, b| a - b, prio: 1, is_commutative: false }, |a| -a),
            Operator::make_unary("sin", |a| a.sin()),
            Operator::make_unary("cos", |a| a.cos()),
            Operator::make_unary("tan", |a| a.tan()),
            Operator::make_unary("floor", |a| a.floor()),
            Operator::make_unary("round", |a| a.round()),
            Operator::make_unary("ceil", |a| a.ceil()),
            Operator::make_unary("trunc", |a| a.trunc()),
            Operator::make_unary("fract", |a| a.fract()),
            Operator::make_unary("exp", |a| a.exp()),
            Operator::make_unary("sqrt", |a| a.sqrt()),
            Operator::make_unary("cbrt", |a| a.cbrt()),
            Operator::make_unary("ln", |a| a.ln()),
            Operator::make_unary("log2", |a| a.log2()),
            Operator::make_unary("log10", |a| a.log10()),
            Operator::make_unary("abs", |a| a.abs()),
            Operator::make_unary("signum", |a| a.signum()),
            Operator::make_constant("PI", f256::f256::from(std::f64::consts::PI)),
            Operator::make_constant("π", f256::f256::from(std::f64::consts::PI)),
            Operator::make_constant("E", f256::f256::from(std::f64::consts::E)),
            Operator::make_constant("e", f256::f256::from(std::f64::consts::E)),
            Operator::make_constant("TAU", f256::f256::from(std::f64::consts::TAU)),
            Operator::make_constant("τ", f256::f256::from(std::f64::consts::TAU)),
        ]
    }
}

static FIND_SCI_NOTATION_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"([0-9]+(?:\.[0-9]+)?)e([+-]?[0-9]+)").unwrap());

#[derive(Debug, poise::ChoiceParameter)]
pub enum Precision {
    #[name = "32-bit"]
    Bits32,
    #[name = "64-bit"]
    Bits64,
    #[name = "128-bit"]
    Bits128,
    #[name = "256-bit"]
    Bits256,
}

#[derive(Debug, poise::ChoiceParameter)]
pub enum Mode {
    #[name = "Floating Point"]
    Float,
    #[name = "Integer"]
    Int,
}

/// Calculator
#[poise::command(slash_command, prefix_command)]
pub async fn calc(ctx: Context<'_>,
    #[description = "Expression"]
    expression: String,
    #[description = "Precision"]
    precision: Option<Precision>
) -> Result<(), Error> {
    debug!("{} has requested to calculate {}", ctx.author().name, expression);
    let msg = "Calculating...".to_string();
    trace_message(msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
    let response_message = ctx.say(msg).await?;
    let start_time = std::time::Instant::now();
    trace!("Saving start time: {:?}", start_time);
    if expression.contains('=') {
        let error_msg = format!("Invalid expression: {}\nYou are not allowed to have an equal sign in the expression.", expression);
        debug!("Invalid expression detected: {}", expression);
        trace_message(error_msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
        edit_response_message(&response_message, ctx, error_msg, false).await?;
        return Ok(());
    }
    let processed_expr : String = FIND_SCI_NOTATION_RE.replace_all(&expression, "$1 * 10^($2)").to_string();
    trace!("Processed expression: {}", processed_expr);
    
    let value = match evaluate_expression(processed_expr, &precision) {
        Ok(v) => v,
        Err(err_msg) => {
            let error_msg = format!("Failed to parse or evaluate expression: `{}`", err_msg);
            warn!("Failed to parse or evaluate expression: `{}` in guild {} channel {} by {}", err_msg, ctx.guild_id().unwrap().get(), ctx.channel_id().get(), ctx.author().name);
            trace_message(error_msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
            edit_response_message(&response_message, ctx, error_msg, false).await?;
            return Ok(());
        }
    };
    trace!("Result: {}", value);
    let end_time = std::time::Instant::now();
    trace!("Saving end time: {:?}", end_time);
    let duration = end_time - start_time;
    trace!("Saving duration: {:?}", duration);
    let precision_str = match precision {
        Some(Precision::Bits32) => "32-bit".to_string(),
        Some(Precision::Bits64) => "64-bit".to_string(),
        Some(Precision::Bits128) => "128-bit".to_string(),
        Some(Precision::Bits256) => "256-bit".to_string(),
        None => "64-bit".to_string(),
    };
    // Turn to scientifc notation if number has more than 30 digits
    let value_string = if value.len() > 30 {
        to_scientific_notation(value)
    } else {
        value
    };

    let msg = format!("{} = {} \n-# Precision: {} Compute time : {:?}", expression, value_string, precision_str, duration);
    trace_message(msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
    edit_response_message(&response_message, ctx, msg, false).await?;
    debug!("Calculation perfomed for {} with result {} in {:?} by {}", expression, value_string, duration, ctx.author().name);
    Ok(())
}

async fn edit_response_message<'a>(response_message: &poise::ReplyHandle<'a>, ctx: Context<'_>, content: String, silent: bool) -> Result<(), Error> {
    if silent {
        response_message.edit(ctx, poise::CreateReply::default().content(content).allowed_mentions(serenity::all::CreateAllowedMentions::new().empty_users())).await?;
    } else {
        response_message.edit(ctx, poise::CreateReply::default().content(content)).await?;
    }
    Ok(())
}

fn to_scientific_notation(value: String) -> String {
    trace!("Converting {} to scientific notation", value);
    if value.contains('e') || value.contains('E') {
        trace!("Already in scientific notation");
        return value;
    }

    let mut is_negative = false;
    let mut val_str = value.as_str();
    if val_str.starts_with('-') {
        is_negative = true;
        val_str = &val_str[1..];
    }

    let parts: Vec<&str> = val_str.split('.').collect();
    let integer_part = parts[0];
    let fractional_part = if parts.len() > 1 { parts[1] } else { "" };

    if integer_part.len() <= 1 {
        return value;
    }

    let first_digit = &integer_part[0..1];
    let rest_integer = &integer_part[1..];
    let exponent = integer_part.len() - 1;

    let mut fractional_combined = format!("{}{}", rest_integer, fractional_part);
    fractional_combined = fractional_combined.trim_end_matches('0').to_string();

    let sign = if is_negative { "-" } else { "" };

    if fractional_combined.is_empty() {
        format!("{}{}e{}", sign, first_digit, exponent)
    } else {
        format!("{}{}.{}e{}", sign, first_digit, fractional_combined, exponent)
    }
}


fn evaluate_expression(expr: String, precision: &Option<Precision>) -> Result<String, String> {
    let value = match precision {
        Some(Precision::Bits32) => exmex::eval_str::<f32>(&expr).map_err(|err| err.to_string())?.to_string(),
        Some(Precision::Bits64) => exmex::eval_str::<f64>(&expr).map_err(|err| err.to_string())?.to_string(),
        Some(Precision::Bits128) => {
            let parsed_expr = exmex::FlatEx::<F128, F128OpsFactory>::parse(&expr).map_err(|err| err.to_string())?;
            parsed_expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
        },
        Some(Precision::Bits256) => {
            let parsed_expr = exmex::FlatEx::<f256::f256, F256OpsFactory>::parse(&expr).map_err(|err| err.to_string())?;
            parsed_expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
        },
        None => exmex::eval_str::<f64>(&expr).map_err(|err| err.to_string())?.to_string(),
    };
    Ok(value)
}