use crate::{
    calc_result::CalcResult,
    expressions::{parser::Node, token::Error, types::CellReferenceIndex},
    model::Model,
};

// 8_i64.pow(10);
const OCT_MAX: i64 = 1_073_741_824;
const OCT_MAX_HALF: i64 = 536_870_912;
// 16_i64.pow(10)
const HEX_MAX: i64 = 1_099_511_627_776;
const HEX_MAX_HALF: i64 = 549_755_813_888;
// Binary numbers are 10 bits and the most significant bit is the sign

fn from_binary_to_decimal(value: f64) -> Result<i64, String> {
    let value = format!("{value}");

    let result = match i64::from_str_radix(&value, 2) {
        Ok(b) => b,
        Err(_) => {
            return Err("cannot parse into binary".to_string());
        }
    };
    if !(0..=1023).contains(&result) {
        // 2^10
        return Err("too large".to_string());
    } else if result > 511 {
        // 2^9
        return Ok(result - 1024);
    };
    Ok(result)
}

impl Model {
    // BIN2DEC(number)
    pub(crate) fn fn_bin2dec(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if args.len() != 1 {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f,
            Err(s) => return s,
        };
        match from_binary_to_decimal(value) {
            Ok(n) => CalcResult::Number(n as f64),
            Err(message) => CalcResult::new_error(Error::NUM, cell, message),
        }
    }

    // BIN2HEX(number, [places])
    pub(crate) fn fn_bin2hex(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let value = match from_binary_to_decimal(value) {
            Ok(n) => n,
            Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
        };
        if value < 0 {
            CalcResult::String(format!("{:0width$X}", HEX_MAX + value, width = 9))
        } else {
            let result = format!("{value:X}");
            if let Some(places) = places {
                if places < result.len() as i32 {
                    return CalcResult::new_error(
                        Error::NUM,
                        cell,
                        "Not enough places".to_string(),
                    );
                }
                return CalcResult::String(format!("{:0width$X}", value, width = places as usize));
            }
            CalcResult::String(result)
        }
    }

    // BIN2OCT(number, [places])
    pub(crate) fn fn_bin2oct(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        let value = match from_binary_to_decimal(value) {
            Ok(n) => n,
            Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        if value < 0 {
            CalcResult::String(format!("{:0width$o}", OCT_MAX + value, width = 9))
        } else {
            let result = format!("{value:o}");
            if let Some(places) = places {
                if places < result.len() as i32 {
                    return CalcResult::new_error(
                        Error::NUM,
                        cell,
                        "Not enough places".to_string(),
                    );
                }
                return CalcResult::String(format!("{:0width$o}", value, width = places as usize));
            }
            CalcResult::String(result)
        }
    }

    pub(crate) fn fn_dec2bin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value_raw = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = value_raw.trunc() as i64;
        if !(-512..=511).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += 1024;
        }
        let result = format!("{value:b}");
        if let Some(places) = places {
            if value_raw > 0.0 && places < result.len() as i32 {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$b}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    pub(crate) fn fn_dec2hex(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value_raw = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f.trunc(),
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = value_raw.trunc() as i64;
        if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += HEX_MAX;
        }
        let result = format!("{value:X}");
        if let Some(places) = places {
            if value_raw > 0.0 && places < result.len() as i32 {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$X}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    pub(crate) fn fn_dec2oct(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value_raw = match self.get_number_no_bools(&args[0], cell) {
            Ok(f) => f,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = value_raw.trunc() as i64;

        if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += OCT_MAX;
        }
        let result = format!("{value:o}");
        if let Some(places) = places {
            if value_raw > 0.0 && places < result.len() as i32 {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$o}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    // HEX2BIN(number, [places])
    pub(crate) fn fn_hex2bin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if value.len() > 10 {
            return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
        }
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = match i64::from_str_radix(&value, 16) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value >= HEX_MAX_HALF {
            value -= HEX_MAX;
        }
        if !(-512..=511).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += 1024;
        }
        let result = format!("{value:b}");
        if let Some(places) = places {
            if places <= 0 || (value > 0 && places < result.len() as i32) {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$b}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    // HEX2DEC(number)
    pub(crate) fn fn_hex2dec(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        if value.len() > 10 {
            return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
        }
        let mut value = match i64::from_str_radix(&value, 16) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value >= HEX_MAX_HALF {
            value -= HEX_MAX;
        }
        CalcResult::Number(value as f64)
    }

    pub(crate) fn fn_hex2oct(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        if value.len() > 10 {
            return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
        }
        let mut value = match i64::from_str_radix(&value, 16) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value > HEX_MAX_HALF {
            value -= HEX_MAX;
        }
        if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += OCT_MAX;
        }
        let result = format!("{value:o}");
        if let Some(places) = places {
            if places <= 0 || (value > 0 && places < result.len() as i32) {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$o}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    pub(crate) fn fn_oct2bin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = match i64::from_str_radix(&value, 8) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value >= OCT_MAX_HALF {
            value -= OCT_MAX;
        }
        if !(-512..=511).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += 1024;
        }
        let result = format!("{value:b}");
        if let Some(places) = places {
            if value < 512 && places < result.len() as i32 {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$b}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }

    pub(crate) fn fn_oct2dec(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        let mut value = match i64::from_str_radix(&value, 8) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value >= OCT_MAX_HALF {
            value -= OCT_MAX
        }
        CalcResult::Number(value as f64)
    }

    pub(crate) fn fn_oct2hex(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
        if !(1..=2).contains(&args.len()) {
            return CalcResult::new_args_number_error(cell);
        }
        let value = match self.get_string(&args[0], cell) {
            Ok(s) => s,
            Err(s) => return s,
        };
        let places = if args.len() == 2 {
            match self.get_number_no_bools(&args[1], cell) {
                Ok(f) => Some(f.trunc() as i32),
                Err(s) => return s,
            }
        } else {
            None
        };
        // There is not a default value for places
        // But if there is a value it needs to be positive and less than 11
        if let Some(p) = places {
            if p <= 0 || p > 10 {
                return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
            }
        }
        let mut value = match i64::from_str_radix(&value, 8) {
            Ok(f) => f,
            Err(_) => {
                return CalcResult::new_error(
                    Error::NUM,
                    cell,
                    "Error parsing hex number".to_string(),
                );
            }
        };
        if value < 0 {
            return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
        }

        if value >= OCT_MAX_HALF {
            value -= OCT_MAX;
        }

        if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
            return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
        }
        if value < 0 {
            value += HEX_MAX;
        }
        let result = format!("{value:X}");
        if let Some(places) = places {
            if value < HEX_MAX_HALF && places < result.len() as i32 {
                return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
            }
            let result = format!("{:0width$X}", value, width = places as usize);
            return CalcResult::String(result);
        }
        CalcResult::String(result)
    }
}
