use core::fmt;
use std::array::IntoIter;

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

pub(crate) mod binary_search;
mod date_and_time;
mod engineering;
mod financial;
mod financial_util;
mod information;
mod logical;
mod lookup_and_reference;
mod mathematical;
mod statistical;
mod subtotal;
mod text;
mod text_util;
pub(crate) mod util;
mod xlookup;

/// List of all implemented functions
#[derive(PartialEq, Clone, Debug)]
pub enum Function {
    // Logical
    And,
    False,
    If,
    Iferror,
    Ifna,
    Ifs,
    Not,
    Or,
    Switch,
    True,
    Xor,

    // Mathematical and trigonometry
    Abs,
    Acos,
    Acosh,
    Asin,
    Asinh,
    Atan,
    Atan2,
    Atanh,
    Choose,
    Column,
    Columns,
    Cos,
    Cosh,
    Max,
    Min,
    Pi,
    Power,
    Product,
    Rand,
    Randbetween,
    Round,
    Rounddown,
    Roundup,
    Sin,
    Sinh,
    Sqrt,
    Sqrtpi,
    Sum,
    Sumif,
    Sumifs,
    Tan,
    Tanh,

    // Information
    ErrorType,
    Isblank,
    Iserr,
    Iserror,
    Iseven,
    Isformula,
    Islogical,
    Isna,
    Isnontext,
    Isnumber,
    Isodd,
    Isref,
    Istext,
    Na,
    Sheet,
    Type,

    // Lookup and reference
    Hlookup,
    Index,
    Indirect,
    Lookup,
    Match,
    Offset,
    Row,
    Rows,
    Vlookup,
    Xlookup,

    // Text
    Concat,
    Concatenate,
    Exact,
    Find,
    Left,
    Len,
    Lower,
    Mid,
    Rept,
    Right,
    Search,
    Substitute,
    T,
    Text,
    Textafter,
    Textbefore,
    Textjoin,
    Trim,
    Upper,
    Value,
    Valuetotext,

    // Statistical
    Average,
    Averagea,
    Averageif,
    Averageifs,
    Count,
    Counta,
    Countblank,
    Countif,
    Countifs,
    Maxifs,
    Minifs,

    // Date and time
    Date,
    Day,
    Edate,
    Eomonth,
    Month,
    Now,
    Today,
    Year,

    // Financial
    Cumipmt,
    Cumprinc,
    Db,
    Ddb,
    Dollarde,
    Dollarfr,
    Effect,
    Fv,
    Ipmt,
    Irr,
    Ispmt,
    Mirr,
    Nominal,
    Nper,
    Npv,
    Pduration,
    Pmt,
    Ppmt,
    Pv,
    Rate,
    Rri,
    Sln,
    Syd,
    Tbilleq,
    Tbillprice,
    Tbillyield,
    Xirr,
    Xnpv,

    // Engineering: Bessel and transcendental functions
    Besseli,
    Besselj,
    Besselk,
    Bessely,
    Erf,
    Erfc,
    ErfcPrecise,
    ErfPrecise,

    // Engineering: Number systems
    Bin2dec,
    Bin2hex,
    Bin2oct,
    Dec2Bin,
    Dec2hex,
    Dec2oct,
    Hex2bin,
    Hex2dec,
    Hex2oct,
    Oct2bin,
    Oct2dec,
    Oct2hex,

    // Engineering: Bit functions
    Bitand,
    Bitlshift,
    Bitor,
    Bitrshift,
    Bitxor,

    // Engineering: Complex functions
    Complex,
    Imabs,
    Imaginary,
    Imargument,
    Imconjugate,
    Imcos,
    Imcosh,
    Imcot,
    Imcsc,
    Imcsch,
    Imdiv,
    Imexp,
    Imln,
    Imlog10,
    Imlog2,
    Impower,
    Improduct,
    Imreal,
    Imsec,
    Imsech,
    Imsin,
    Imsinh,
    Imsqrt,
    Imsub,
    Imsum,
    Imtan,

    // Engineering: Misc function
    Convert,
    Delta,
    Gestep,
    Subtotal,
}

impl Function {
    pub fn into_iter() -> IntoIter<Function, 192> {
        [
            Function::And,
            Function::False,
            Function::If,
            Function::Iferror,
            Function::Ifna,
            Function::Ifs,
            Function::Not,
            Function::Or,
            Function::Switch,
            Function::True,
            Function::Xor,
            Function::Sin,
            Function::Cos,
            Function::Tan,
            Function::Asin,
            Function::Acos,
            Function::Atan,
            Function::Sinh,
            Function::Cosh,
            Function::Tanh,
            Function::Asinh,
            Function::Acosh,
            Function::Atanh,
            Function::Abs,
            Function::Pi,
            Function::Sqrt,
            Function::Sqrtpi,
            Function::Atan2,
            Function::Power,
            Function::Max,
            Function::Min,
            Function::Product,
            Function::Rand,
            Function::Randbetween,
            Function::Round,
            Function::Rounddown,
            Function::Roundup,
            Function::Sum,
            Function::Sumif,
            Function::Sumifs,
            Function::Choose,
            Function::Column,
            Function::Columns,
            Function::Index,
            Function::Indirect,
            Function::Hlookup,
            Function::Lookup,
            Function::Match,
            Function::Offset,
            Function::Row,
            Function::Rows,
            Function::Vlookup,
            Function::Xlookup,
            Function::Concatenate,
            Function::Exact,
            Function::Value,
            Function::T,
            Function::Valuetotext,
            Function::Concat,
            Function::Find,
            Function::Left,
            Function::Len,
            Function::Lower,
            Function::Mid,
            Function::Right,
            Function::Search,
            Function::Text,
            Function::Trim,
            Function::Upper,
            Function::Isnumber,
            Function::Isnontext,
            Function::Istext,
            Function::Islogical,
            Function::Isblank,
            Function::Iserr,
            Function::Iserror,
            Function::Isna,
            Function::Na,
            Function::Isref,
            Function::Isodd,
            Function::Iseven,
            Function::ErrorType,
            Function::Isformula,
            Function::Type,
            Function::Sheet,
            Function::Average,
            Function::Averagea,
            Function::Averageif,
            Function::Averageifs,
            Function::Count,
            Function::Counta,
            Function::Countblank,
            Function::Countif,
            Function::Countifs,
            Function::Maxifs,
            Function::Minifs,
            Function::Year,
            Function::Day,
            Function::Month,
            Function::Eomonth,
            Function::Date,
            Function::Edate,
            Function::Today,
            Function::Now,
            Function::Pmt,
            Function::Pv,
            Function::Rate,
            Function::Nper,
            Function::Fv,
            Function::Ppmt,
            Function::Ipmt,
            Function::Npv,
            Function::Mirr,
            Function::Irr,
            Function::Xirr,
            Function::Xnpv,
            Function::Rept,
            Function::Textafter,
            Function::Textbefore,
            Function::Textjoin,
            Function::Substitute,
            Function::Ispmt,
            Function::Rri,
            Function::Sln,
            Function::Syd,
            Function::Nominal,
            Function::Effect,
            Function::Pduration,
            Function::Tbillyield,
            Function::Tbillprice,
            Function::Tbilleq,
            Function::Dollarde,
            Function::Dollarfr,
            Function::Ddb,
            Function::Db,
            Function::Cumprinc,
            Function::Cumipmt,
            Function::Besseli,
            Function::Besselj,
            Function::Besselk,
            Function::Bessely,
            Function::Erf,
            Function::ErfPrecise,
            Function::Erfc,
            Function::ErfcPrecise,
            Function::Bin2dec,
            Function::Bin2hex,
            Function::Bin2oct,
            Function::Dec2Bin,
            Function::Dec2hex,
            Function::Dec2oct,
            Function::Hex2bin,
            Function::Hex2dec,
            Function::Hex2oct,
            Function::Oct2bin,
            Function::Oct2dec,
            Function::Oct2hex,
            Function::Bitand,
            Function::Bitlshift,
            Function::Bitor,
            Function::Bitrshift,
            Function::Bitxor,
            Function::Complex,
            Function::Imabs,
            Function::Imaginary,
            Function::Imargument,
            Function::Imconjugate,
            Function::Imcos,
            Function::Imcosh,
            Function::Imcot,
            Function::Imcsc,
            Function::Imcsch,
            Function::Imdiv,
            Function::Imexp,
            Function::Imln,
            Function::Imlog10,
            Function::Imlog2,
            Function::Impower,
            Function::Improduct,
            Function::Imreal,
            Function::Imsec,
            Function::Imsech,
            Function::Imsin,
            Function::Imsinh,
            Function::Imsqrt,
            Function::Imsub,
            Function::Imsum,
            Function::Imtan,
            Function::Convert,
            Function::Delta,
            Function::Gestep,
            Function::Subtotal,
        ]
        .into_iter()
    }
}

impl Function {
    /// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
    pub fn to_xlsx_string(&self) -> String {
        match self {
            Function::Concat => "_xlfn.CONCAT".to_string(),
            Function::Ifna => "_xlfn.IFNA".to_string(),
            Function::Ifs => "_xlfn.IFS".to_string(),
            Function::Maxifs => "_xlfn.MAXIFS".to_string(),
            Function::Minifs => "_xlfn.MINIFS".to_string(),
            Function::Switch => "_xlfn.SWITCH".to_string(),
            Function::Xlookup => "_xlfn.XLOOKUP".to_string(),
            Function::Xor => "_xlfn.XOR".to_string(),
            Function::Textbefore => "_xlfn.TEXTBEFORE".to_string(),
            Function::Textafter => "_xlfn.TEXTAFTER".to_string(),
            Function::Textjoin => "_xlfn.TEXTJOIN".to_string(),
            Function::Rri => "_xlfn.RRI".to_string(),
            Function::Pduration => "_xlfn.PDURATION".to_string(),
            Function::Bitand => "_xlfn.BITAND".to_string(),
            Function::Bitor => "_xlfn.BITOR".to_string(),
            Function::Bitxor => "_xlfn.BITXOR".to_string(),
            Function::Bitlshift => "_xlfn.BITLSHIFT".to_string(),
            Function::Bitrshift => "_xlfn.BITRSHIFT".to_string(),
            Function::Imtan => "_xlfn.IMTAN".to_string(),
            Function::Imsinh => "_xlfn.IMSINH".to_string(),
            Function::Imcosh => "_xlfn.IMCOSH".to_string(),
            Function::Imcot => "_xlfn.IMCOT".to_string(),
            Function::Imcsc => "_xlfn.IMCSC".to_string(),
            Function::Imcsch => "_xlfn.IMCSCH".to_string(),
            Function::Imsec => "_xlfn.IMSEC".to_string(),
            Function::ErfcPrecise => "_xlfn.ERFC.PRECISE".to_string(),
            Function::ErfPrecise => "_xlfn.ERF.PRECISE".to_string(),
            Function::Valuetotext => "_xlfn.VALUETOTEXT".to_string(),
            Function::Isformula => "_xlfn.ISFORMULA".to_string(),
            Function::Sheet => "_xlfn.SHEET".to_string(),
            _ => self.to_string(),
        }
    }

    pub(crate) fn returns_reference(&self) -> bool {
        matches!(self, Function::Indirect | Function::Offset)
    }
    /// Gets the function from the name.
    /// Note that in Excel some (modern) functions are prefixed by `_xlfn.`
    pub fn get_function(name: &str) -> Option<Function> {
        match name.to_ascii_uppercase().as_str() {
            "AND" => Some(Function::And),
            "FALSE" => Some(Function::False),
            "IF" => Some(Function::If),
            "IFERROR" => Some(Function::Iferror),
            "IFNA" | "_XLFN.IFNA" => Some(Function::Ifna),
            "IFS" | "_XLFN.IFS" => Some(Function::Ifs),
            "NOT" => Some(Function::Not),
            "OR" => Some(Function::Or),
            "SWITCH" | "_XLFN.SWITCH" => Some(Function::Switch),
            "TRUE" => Some(Function::True),
            "XOR" | "_XLFN.XOR" => Some(Function::Xor),

            "SIN" => Some(Function::Sin),
            "COS" => Some(Function::Cos),
            "TAN" => Some(Function::Tan),

            "ASIN" => Some(Function::Asin),
            "ACOS" => Some(Function::Acos),
            "ATAN" => Some(Function::Atan),

            "SINH" => Some(Function::Sinh),
            "COSH" => Some(Function::Cosh),
            "TANH" => Some(Function::Tanh),

            "ASINH" => Some(Function::Asinh),
            "ACOSH" => Some(Function::Acosh),
            "ATANH" => Some(Function::Atanh),

            "PI" => Some(Function::Pi),
            "ABS" => Some(Function::Abs),
            "SQRT" => Some(Function::Sqrt),
            "SQRTPI" => Some(Function::Sqrtpi),
            "POWER" => Some(Function::Power),
            "ATAN2" => Some(Function::Atan2),

            "MAX" => Some(Function::Max),
            "MIN" => Some(Function::Min),
            "PRODUCT" => Some(Function::Product),
            "RAND" => Some(Function::Rand),
            "RANDBETWEEN" => Some(Function::Randbetween),
            "ROUND" => Some(Function::Round),
            "ROUNDDOWN" => Some(Function::Rounddown),
            "ROUNDUP" => Some(Function::Roundup),
            "SUM" => Some(Function::Sum),
            "SUMIF" => Some(Function::Sumif),
            "SUMIFS" => Some(Function::Sumifs),

            // Lookup and Reference
            "CHOOSE" => Some(Function::Choose),
            "COLUMN" => Some(Function::Column),
            "COLUMNS" => Some(Function::Columns),
            "INDEX" => Some(Function::Index),
            "INDIRECT" => Some(Function::Indirect),
            "HLOOKUP" => Some(Function::Hlookup),
            "LOOKUP" => Some(Function::Lookup),
            "MATCH" => Some(Function::Match),
            "OFFSET" => Some(Function::Offset),
            "ROW" => Some(Function::Row),
            "ROWS" => Some(Function::Rows),
            "VLOOKUP" => Some(Function::Vlookup),
            "XLOOKUP" | "_XLFN.XLOOKUP" => Some(Function::Xlookup),

            "CONCATENATE" => Some(Function::Concatenate),
            "EXACT" => Some(Function::Exact),
            "VALUE" => Some(Function::Value),
            "T" => Some(Function::T),
            "VALUETOTEXT" | "_XLFN.VALUETOTEXT" => Some(Function::Valuetotext),
            "CONCAT" | "_XLFN.CONCAT" => Some(Function::Concat),
            "FIND" => Some(Function::Find),
            "LEFT" => Some(Function::Left),
            "LEN" => Some(Function::Len),
            "LOWER" => Some(Function::Lower),
            "MID" => Some(Function::Mid),
            "RIGHT" => Some(Function::Right),
            "SEARCH" => Some(Function::Search),
            "TEXT" => Some(Function::Text),
            "TRIM" => Some(Function::Trim),
            "UPPER" => Some(Function::Upper),

            "REPT" => Some(Function::Rept),
            "TEXTAFTER" | "_XLFN.TEXTAFTER" => Some(Function::Textafter),
            "TEXTBEFORE" | "_XLFN.TEXTBEFORE" => Some(Function::Textbefore),
            "TEXTJOIN" | "_XLFN.TEXTJOIN" => Some(Function::Textjoin),
            "SUBSTITUTE" => Some(Function::Substitute),

            "ISNUMBER" => Some(Function::Isnumber),
            "ISNONTEXT" => Some(Function::Isnontext),
            "ISTEXT" => Some(Function::Istext),
            "ISLOGICAL" => Some(Function::Islogical),
            "ISBLANK" => Some(Function::Isblank),
            "ISERR" => Some(Function::Iserr),
            "ISERROR" => Some(Function::Iserror),
            "ISNA" => Some(Function::Isna),
            "NA" => Some(Function::Na),
            "ISREF" => Some(Function::Isref),
            "ISODD" => Some(Function::Isodd),
            "ISEVEN" => Some(Function::Iseven),
            "ERROR.TYPE" => Some(Function::ErrorType),
            "ISFORMULA" | "_XLFN.ISFORMULA" => Some(Function::Isformula),
            "TYPE" => Some(Function::Type),
            "SHEET" | "_XLFN.SHEET" => Some(Function::Sheet),

            "AVERAGE" => Some(Function::Average),
            "AVERAGEA" => Some(Function::Averagea),
            "AVERAGEIF" => Some(Function::Averageif),
            "AVERAGEIFS" => Some(Function::Averageifs),
            "COUNT" => Some(Function::Count),
            "COUNTA" => Some(Function::Counta),
            "COUNTBLANK" => Some(Function::Countblank),
            "COUNTIF" => Some(Function::Countif),
            "COUNTIFS" => Some(Function::Countifs),
            "MAXIFS" | "_XLFN.MAXIFS" => Some(Function::Maxifs),
            "MINIFS" | "_XLFN.MINIFS" => Some(Function::Minifs),
            // Date and Time
            "YEAR" => Some(Function::Year),
            "DAY" => Some(Function::Day),
            "EOMONTH" => Some(Function::Eomonth),
            "MONTH" => Some(Function::Month),
            "DATE" => Some(Function::Date),
            "EDATE" => Some(Function::Edate),
            "TODAY" => Some(Function::Today),
            "NOW" => Some(Function::Now),
            // Financial
            "PMT" => Some(Function::Pmt),
            "PV" => Some(Function::Pv),
            "RATE" => Some(Function::Rate),
            "NPER" => Some(Function::Nper),
            "FV" => Some(Function::Fv),
            "PPMT" => Some(Function::Ppmt),
            "IPMT" => Some(Function::Ipmt),
            "NPV" => Some(Function::Npv),
            "XNPV" => Some(Function::Xnpv),
            "MIRR" => Some(Function::Mirr),
            "IRR" => Some(Function::Irr),
            "XIRR" => Some(Function::Xirr),
            "ISPMT" => Some(Function::Ispmt),
            "RRI" | "_XLFN.RRI" => Some(Function::Rri),

            "SLN" => Some(Function::Sln),
            "SYD" => Some(Function::Syd),
            "NOMINAL" => Some(Function::Nominal),
            "EFFECT" => Some(Function::Effect),
            "PDURATION" | "_XLFN.PDURATION" => Some(Function::Pduration),

            "TBILLYIELD" => Some(Function::Tbillyield),
            "TBILLPRICE" => Some(Function::Tbillprice),
            "TBILLEQ" => Some(Function::Tbilleq),

            "DOLLARDE" => Some(Function::Dollarde),
            "DOLLARFR" => Some(Function::Dollarfr),

            "DDB" => Some(Function::Ddb),
            "DB" => Some(Function::Db),

            "CUMPRINC" => Some(Function::Cumprinc),
            "CUMIPMT" => Some(Function::Cumipmt),

            "BESSELI" => Some(Function::Besseli),
            "BESSELJ" => Some(Function::Besselj),
            "BESSELK" => Some(Function::Besselk),
            "BESSELY" => Some(Function::Bessely),
            "ERF" => Some(Function::Erf),
            "ERF.PRECISE" | "_XLFN.ERF.PRECISE" => Some(Function::ErfPrecise),
            "ERFC" => Some(Function::Erfc),
            "ERFC.PRECISE" | "_XLFN.ERFC.PRECISE" => Some(Function::ErfcPrecise),
            "BIN2DEC" => Some(Function::Bin2dec),
            "BIN2HEX" => Some(Function::Bin2hex),
            "BIN2OCT" => Some(Function::Bin2oct),
            "DEC2BIN" => Some(Function::Dec2Bin),
            "DEC2HEX" => Some(Function::Dec2hex),
            "DEC2OCT" => Some(Function::Dec2oct),
            "HEX2BIN" => Some(Function::Hex2bin),
            "HEX2DEC" => Some(Function::Hex2dec),
            "HEX2OCT" => Some(Function::Hex2oct),
            "OCT2BIN" => Some(Function::Oct2bin),
            "OCT2DEC" => Some(Function::Oct2dec),
            "OCT2HEX" => Some(Function::Oct2hex),
            "BITAND" | "_XLFN.BITAND" => Some(Function::Bitand),
            "BITLSHIFT" | "_XLFN.BITLSHIFT" => Some(Function::Bitlshift),
            "BITOR" | "_XLFN.BITOR" => Some(Function::Bitor),
            "BITRSHIFT" | "_XLFN.BITRSHIFT" => Some(Function::Bitrshift),
            "BITXOR" | "_XLFN.BITXOR" => Some(Function::Bitxor),
            "COMPLEX" => Some(Function::Complex),
            "IMABS" => Some(Function::Imabs),
            "IMAGINARY" => Some(Function::Imaginary),
            "IMARGUMENT" => Some(Function::Imargument),
            "IMCONJUGATE" => Some(Function::Imconjugate),
            "IMCOS" => Some(Function::Imcos),
            "IMCOSH" | "_XLFN.IMCOSH" => Some(Function::Imcosh),
            "IMCOT" | "_XLFN.IMCOT" => Some(Function::Imcot),
            "IMCSC" | "_XLFN.IMCSC" => Some(Function::Imcsc),
            "IMCSCH" | "_XLFN.IMCSCH" => Some(Function::Imcsch),
            "IMDIV" => Some(Function::Imdiv),
            "IMEXP" => Some(Function::Imexp),
            "IMLN" => Some(Function::Imln),
            "IMLOG10" => Some(Function::Imlog10),
            "IMLOG2" => Some(Function::Imlog2),
            "IMPOWER" => Some(Function::Impower),
            "IMPRODUCT" => Some(Function::Improduct),
            "IMREAL" => Some(Function::Imreal),
            "IMSEC" | "_XLFN.IMSEC" => Some(Function::Imsec),
            "IMSECH" | "_XLFN.IMSECH" => Some(Function::Imsech),
            "IMSIN" => Some(Function::Imsin),
            "IMSINH" | "_XLFN.IMSINH" => Some(Function::Imsinh),
            "IMSQRT" => Some(Function::Imsqrt),
            "IMSUB" => Some(Function::Imsub),
            "IMSUM" => Some(Function::Imsum),
            "IMTAN" | "_XLFN.IMTAN" => Some(Function::Imtan),
            "CONVERT" => Some(Function::Convert),
            "DELTA" => Some(Function::Delta),
            "GESTEP" => Some(Function::Gestep),

            "SUBTOTAL" => Some(Function::Subtotal),
            _ => None,
        }
    }
}

impl fmt::Display for Function {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Function::And => write!(f, "AND"),
            Function::False => write!(f, "FALSE"),
            Function::If => write!(f, "IF"),
            Function::Iferror => write!(f, "IFERROR"),
            Function::Ifna => write!(f, "IFNA"),
            Function::Ifs => write!(f, "IFS"),
            Function::Not => write!(f, "NOT"),
            Function::Or => write!(f, "OR"),
            Function::Switch => write!(f, "SWITCH"),
            Function::True => write!(f, "TRUE"),
            Function::Xor => write!(f, "XOR"),
            Function::Sin => write!(f, "SIN"),
            Function::Cos => write!(f, "COS"),
            Function::Tan => write!(f, "TAN"),
            Function::Asin => write!(f, "ASIN"),
            Function::Acos => write!(f, "ACOS"),
            Function::Atan => write!(f, "ATAN"),
            Function::Sinh => write!(f, "SINH"),
            Function::Cosh => write!(f, "COSH"),
            Function::Tanh => write!(f, "TANH"),
            Function::Asinh => write!(f, "ASINH"),
            Function::Acosh => write!(f, "ACOSH"),
            Function::Atanh => write!(f, "ATANH"),
            Function::Abs => write!(f, "ABS"),
            Function::Pi => write!(f, "PI"),
            Function::Sqrt => write!(f, "SQRT"),
            Function::Sqrtpi => write!(f, "SQRTPI"),
            Function::Atan2 => write!(f, "ATAN2"),
            Function::Power => write!(f, "POWER"),
            Function::Max => write!(f, "MAX"),
            Function::Min => write!(f, "MIN"),
            Function::Product => write!(f, "PRODUCT"),
            Function::Rand => write!(f, "RAND"),
            Function::Randbetween => write!(f, "RANDBETWEEN"),
            Function::Round => write!(f, "ROUND"),
            Function::Rounddown => write!(f, "ROUNDDOWN"),
            Function::Roundup => write!(f, "ROUNDUP"),
            Function::Sum => write!(f, "SUM"),
            Function::Sumif => write!(f, "SUMIF"),
            Function::Sumifs => write!(f, "SUMIFS"),
            Function::Choose => write!(f, "CHOOSE"),
            Function::Column => write!(f, "COLUMN"),
            Function::Columns => write!(f, "COLUMNS"),
            Function::Index => write!(f, "INDEX"),
            Function::Indirect => write!(f, "INDIRECT"),
            Function::Hlookup => write!(f, "HLOOKUP"),
            Function::Lookup => write!(f, "LOOKUP"),
            Function::Match => write!(f, "MATCH"),
            Function::Offset => write!(f, "OFFSET"),
            Function::Row => write!(f, "ROW"),
            Function::Rows => write!(f, "ROWS"),
            Function::Vlookup => write!(f, "VLOOKUP"),
            Function::Xlookup => write!(f, "XLOOKUP"),
            Function::Concatenate => write!(f, "CONCATENATE"),
            Function::Exact => write!(f, "EXACT"),
            Function::Value => write!(f, "VALUE"),
            Function::T => write!(f, "T"),
            Function::Valuetotext => write!(f, "VALUETOTEXT"),
            Function::Concat => write!(f, "CONCAT"),
            Function::Find => write!(f, "FIND"),
            Function::Left => write!(f, "LEFT"),
            Function::Len => write!(f, "LEN"),
            Function::Lower => write!(f, "LOWER"),
            Function::Mid => write!(f, "MID"),
            Function::Right => write!(f, "RIGHT"),
            Function::Search => write!(f, "SEARCH"),
            Function::Text => write!(f, "TEXT"),
            Function::Trim => write!(f, "TRIM"),
            Function::Upper => write!(f, "UPPER"),
            Function::Isnumber => write!(f, "ISNUMBER"),
            Function::Isnontext => write!(f, "ISNONTEXT"),
            Function::Istext => write!(f, "ISTEXT"),
            Function::Islogical => write!(f, "ISLOGICAL"),
            Function::Isblank => write!(f, "ISBLANK"),
            Function::Iserr => write!(f, "ISERR"),
            Function::Iserror => write!(f, "ISERROR"),
            Function::Isna => write!(f, "ISNA"),
            Function::Na => write!(f, "NA"),
            Function::Isref => write!(f, "ISREF"),
            Function::Isodd => write!(f, "ISODD"),
            Function::Iseven => write!(f, "ISEVEN"),
            Function::ErrorType => write!(f, "ERROR.TYPE"),
            Function::Isformula => write!(f, "ISFORMULA"),
            Function::Type => write!(f, "TYPE"),
            Function::Sheet => write!(f, "SHEET"),

            Function::Average => write!(f, "AVERAGE"),
            Function::Averagea => write!(f, "AVERAGEA"),
            Function::Averageif => write!(f, "AVERAGEIF"),
            Function::Averageifs => write!(f, "AVERAGEIFS"),
            Function::Count => write!(f, "COUNT"),
            Function::Counta => write!(f, "COUNTA"),
            Function::Countblank => write!(f, "COUNTBLANK"),
            Function::Countif => write!(f, "COUNTIF"),
            Function::Countifs => write!(f, "COUNTIFS"),
            Function::Maxifs => write!(f, "MAXIFS"),
            Function::Minifs => write!(f, "MINIFS"),
            Function::Year => write!(f, "YEAR"),
            Function::Day => write!(f, "DAY"),
            Function::Month => write!(f, "MONTH"),
            Function::Eomonth => write!(f, "EOMONTH"),
            Function::Date => write!(f, "DATE"),
            Function::Edate => write!(f, "EDATE"),
            Function::Today => write!(f, "TODAY"),
            Function::Now => write!(f, "NOW"),
            Function::Pmt => write!(f, "PMT"),
            Function::Pv => write!(f, "PV"),
            Function::Rate => write!(f, "RATE"),
            Function::Nper => write!(f, "NPER"),
            Function::Fv => write!(f, "FV"),
            Function::Ppmt => write!(f, "PPMT"),
            Function::Ipmt => write!(f, "IPMT"),
            Function::Npv => write!(f, "NPV"),
            Function::Mirr => write!(f, "MIRR"),
            Function::Irr => write!(f, "IRR"),
            Function::Xirr => write!(f, "XIRR"),
            Function::Xnpv => write!(f, "XNPV"),
            Function::Rept => write!(f, "REPT"),
            Function::Textafter => write!(f, "TEXTAFTER"),
            Function::Textbefore => write!(f, "TEXTBEFORE"),
            Function::Textjoin => write!(f, "TEXTJOIN"),
            Function::Substitute => write!(f, "SUBSTITUTE"),
            Function::Ispmt => write!(f, "ISPMT"),
            Function::Rri => write!(f, "RRI"),
            Function::Sln => write!(f, "SLN"),
            Function::Syd => write!(f, "SYD"),
            Function::Nominal => write!(f, "NOMINAL"),
            Function::Effect => write!(f, "EFFECT"),
            Function::Pduration => write!(f, "PDURATION"),
            Function::Tbillyield => write!(f, "TBILLYIELD"),
            Function::Tbillprice => write!(f, "TBILLPRICE"),
            Function::Tbilleq => write!(f, "TBILLEQ"),
            Function::Dollarde => write!(f, "DOLLARDE"),
            Function::Dollarfr => write!(f, "DOLLARFR"),
            Function::Ddb => write!(f, "DDB"),
            Function::Db => write!(f, "DB"),
            Function::Cumprinc => write!(f, "CUMPRINC"),
            Function::Cumipmt => write!(f, "CUMIPMT"),
            Function::Besseli => write!(f, "BESSELI"),
            Function::Besselj => write!(f, "BESSELJ"),
            Function::Besselk => write!(f, "BESSELK"),
            Function::Bessely => write!(f, "BESSELY"),
            Function::Erf => write!(f, "ERF"),
            Function::ErfPrecise => write!(f, "ERF.PRECISE"),
            Function::Erfc => write!(f, "ERFC"),
            Function::ErfcPrecise => write!(f, "ERFC.PRECISE"),
            Function::Bin2dec => write!(f, "BIN2DEC"),
            Function::Bin2hex => write!(f, "BIN2HEX"),
            Function::Bin2oct => write!(f, "BIN2OCT"),
            Function::Dec2Bin => write!(f, "DEC2BIN"),
            Function::Dec2hex => write!(f, "DEC2HEX"),
            Function::Dec2oct => write!(f, "DEC2OCT"),
            Function::Hex2bin => write!(f, "HEX2BIN"),
            Function::Hex2dec => write!(f, "HEX2DEC"),
            Function::Hex2oct => write!(f, "HEX2OCT"),
            Function::Oct2bin => write!(f, "OCT2BIN"),
            Function::Oct2dec => write!(f, "OCT2DEC"),
            Function::Oct2hex => write!(f, "OCT2HEX"),
            Function::Bitand => write!(f, "BITAND"),
            Function::Bitlshift => write!(f, "BITLSHIFT"),
            Function::Bitor => write!(f, "BITOR"),
            Function::Bitrshift => write!(f, "BITRSHIFT"),
            Function::Bitxor => write!(f, "BITXOR"),
            Function::Complex => write!(f, "COMPLEX"),
            Function::Imabs => write!(f, "IMABS"),
            Function::Imaginary => write!(f, "IMAGINARY"),
            Function::Imargument => write!(f, "IMARGUMENT"),
            Function::Imconjugate => write!(f, "IMCONJUGATE"),
            Function::Imcos => write!(f, "IMCOS"),
            Function::Imcosh => write!(f, "IMCOSH"),
            Function::Imcot => write!(f, "IMCOT"),
            Function::Imcsc => write!(f, "IMCSC"),
            Function::Imcsch => write!(f, "IMCSCH"),
            Function::Imdiv => write!(f, "IMDIV"),
            Function::Imexp => write!(f, "IMEXP"),
            Function::Imln => write!(f, "IMLN"),
            Function::Imlog10 => write!(f, "IMLOG10"),
            Function::Imlog2 => write!(f, "IMLOG2"),
            Function::Impower => write!(f, "IMPOWER"),
            Function::Improduct => write!(f, "IMPRODUCT"),
            Function::Imreal => write!(f, "IMREAL"),
            Function::Imsec => write!(f, "IMSEC"),
            Function::Imsech => write!(f, "IMSECH"),
            Function::Imsin => write!(f, "IMSIN"),
            Function::Imsinh => write!(f, "IMSINH"),
            Function::Imsqrt => write!(f, "IMSQRT"),
            Function::Imsub => write!(f, "IMSUB"),
            Function::Imsum => write!(f, "IMSUM"),
            Function::Imtan => write!(f, "IMTAN"),
            Function::Convert => write!(f, "CONVERT"),
            Function::Delta => write!(f, "DELTA"),
            Function::Gestep => write!(f, "GESTEP"),

            Function::Subtotal => write!(f, "SUBTOTAL"),
        }
    }
}

/// Documentation for one function
pub struct Documentation {
    pub name: String,
}

impl Model {
    /// Produces documentation for all implemented functions
    pub fn documentation() -> Vec<Documentation> {
        let mut doc = Vec::new();
        for function in Function::into_iter() {
            doc.push(Documentation {
                name: function.to_string(),
            });
        }
        doc
    }

    pub(crate) fn evaluate_function(
        &mut self,
        kind: &Function,
        args: &[Node],
        cell: CellReferenceIndex,
    ) -> CalcResult {
        match kind {
            // Logical
            Function::And => self.fn_and(args, cell),
            Function::False => CalcResult::Boolean(false),
            Function::If => self.fn_if(args, cell),
            Function::Iferror => self.fn_iferror(args, cell),
            Function::Ifna => self.fn_ifna(args, cell),
            Function::Ifs => self.fn_ifs(args, cell),
            Function::Not => self.fn_not(args, cell),
            Function::Or => self.fn_or(args, cell),
            Function::Switch => self.fn_switch(args, cell),
            Function::True => CalcResult::Boolean(true),
            Function::Xor => self.fn_xor(args, cell),
            // Math and trigonometry
            Function::Sin => self.fn_sin(args, cell),
            Function::Cos => self.fn_cos(args, cell),
            Function::Tan => self.fn_tan(args, cell),

            Function::Asin => self.fn_asin(args, cell),
            Function::Acos => self.fn_acos(args, cell),
            Function::Atan => self.fn_atan(args, cell),

            Function::Sinh => self.fn_sinh(args, cell),
            Function::Cosh => self.fn_cosh(args, cell),
            Function::Tanh => self.fn_tanh(args, cell),

            Function::Asinh => self.fn_asinh(args, cell),
            Function::Acosh => self.fn_acosh(args, cell),
            Function::Atanh => self.fn_atanh(args, cell),

            Function::Pi => self.fn_pi(args, cell),
            Function::Abs => self.fn_abs(args, cell),

            Function::Sqrt => self.fn_sqrt(args, cell),
            Function::Sqrtpi => self.fn_sqrtpi(args, cell),
            Function::Atan2 => self.fn_atan2(args, cell),
            Function::Power => self.fn_power(args, cell),

            Function::Max => self.fn_max(args, cell),
            Function::Min => self.fn_min(args, cell),
            Function::Product => self.fn_product(args, cell),
            Function::Rand => self.fn_rand(args, cell),
            Function::Randbetween => self.fn_randbetween(args, cell),
            Function::Round => self.fn_round(args, cell),
            Function::Rounddown => self.fn_rounddown(args, cell),
            Function::Roundup => self.fn_roundup(args, cell),
            Function::Sum => self.fn_sum(args, cell),
            Function::Sumif => self.fn_sumif(args, cell),
            Function::Sumifs => self.fn_sumifs(args, cell),

            // Lookup and Reference
            Function::Choose => self.fn_choose(args, cell),
            Function::Column => self.fn_column(args, cell),
            Function::Columns => self.fn_columns(args, cell),
            Function::Index => self.fn_index(args, cell),
            Function::Indirect => self.fn_indirect(args, cell),
            Function::Hlookup => self.fn_hlookup(args, cell),
            Function::Lookup => self.fn_lookup(args, cell),
            Function::Match => self.fn_match(args, cell),
            Function::Offset => self.fn_offset(args, cell),
            Function::Row => self.fn_row(args, cell),
            Function::Rows => self.fn_rows(args, cell),
            Function::Vlookup => self.fn_vlookup(args, cell),
            Function::Xlookup => self.fn_xlookup(args, cell),
            // Text
            Function::Concatenate => self.fn_concatenate(args, cell),
            Function::Exact => self.fn_exact(args, cell),
            Function::Value => self.fn_value(args, cell),
            Function::T => self.fn_t(args, cell),
            Function::Valuetotext => self.fn_valuetotext(args, cell),
            Function::Concat => self.fn_concat(args, cell),
            Function::Find => self.fn_find(args, cell),
            Function::Left => self.fn_left(args, cell),
            Function::Len => self.fn_len(args, cell),
            Function::Lower => self.fn_lower(args, cell),
            Function::Mid => self.fn_mid(args, cell),
            Function::Right => self.fn_right(args, cell),
            Function::Search => self.fn_search(args, cell),
            Function::Text => self.fn_text(args, cell),
            Function::Trim => self.fn_trim(args, cell),
            Function::Upper => self.fn_upper(args, cell),
            // Information
            Function::Isnumber => self.fn_isnumber(args, cell),
            Function::Isnontext => self.fn_isnontext(args, cell),
            Function::Istext => self.fn_istext(args, cell),
            Function::Islogical => self.fn_islogical(args, cell),
            Function::Isblank => self.fn_isblank(args, cell),
            Function::Iserr => self.fn_iserr(args, cell),
            Function::Iserror => self.fn_iserror(args, cell),
            Function::Isna => self.fn_isna(args, cell),
            Function::Na => CalcResult::new_error(Error::NA, cell, "".to_string()),
            Function::Isref => self.fn_isref(args, cell),
            Function::Isodd => self.fn_isodd(args, cell),
            Function::Iseven => self.fn_iseven(args, cell),
            Function::ErrorType => self.fn_errortype(args, cell),
            Function::Isformula => self.fn_isformula(args, cell),
            Function::Type => self.fn_type(args, cell),
            Function::Sheet => self.fn_sheet(args, cell),
            // Statistical
            Function::Average => self.fn_average(args, cell),
            Function::Averagea => self.fn_averagea(args, cell),
            Function::Averageif => self.fn_averageif(args, cell),
            Function::Averageifs => self.fn_averageifs(args, cell),
            Function::Count => self.fn_count(args, cell),
            Function::Counta => self.fn_counta(args, cell),
            Function::Countblank => self.fn_countblank(args, cell),
            Function::Countif => self.fn_countif(args, cell),
            Function::Countifs => self.fn_countifs(args, cell),
            Function::Maxifs => self.fn_maxifs(args, cell),
            Function::Minifs => self.fn_minifs(args, cell),
            // Date and Time
            Function::Year => self.fn_year(args, cell),
            Function::Day => self.fn_day(args, cell),
            Function::Eomonth => self.fn_eomonth(args, cell),
            Function::Month => self.fn_month(args, cell),
            Function::Date => self.fn_date(args, cell),
            Function::Edate => self.fn_edate(args, cell),
            Function::Today => self.fn_today(args, cell),
            Function::Now => self.fn_now(args, cell),
            // Financial
            Function::Pmt => self.fn_pmt(args, cell),
            Function::Pv => self.fn_pv(args, cell),
            Function::Rate => self.fn_rate(args, cell),
            Function::Nper => self.fn_nper(args, cell),
            Function::Fv => self.fn_fv(args, cell),
            Function::Ppmt => self.fn_ppmt(args, cell),
            Function::Ipmt => self.fn_ipmt(args, cell),
            Function::Npv => self.fn_npv(args, cell),
            Function::Mirr => self.fn_mirr(args, cell),
            Function::Irr => self.fn_irr(args, cell),
            Function::Xirr => self.fn_xirr(args, cell),
            Function::Xnpv => self.fn_xnpv(args, cell),
            Function::Rept => self.fn_rept(args, cell),
            Function::Textafter => self.fn_textafter(args, cell),
            Function::Textbefore => self.fn_textbefore(args, cell),
            Function::Textjoin => self.fn_textjoin(args, cell),
            Function::Substitute => self.fn_substitute(args, cell),
            Function::Ispmt => self.fn_ispmt(args, cell),
            Function::Rri => self.fn_rri(args, cell),
            Function::Sln => self.fn_sln(args, cell),
            Function::Syd => self.fn_syd(args, cell),
            Function::Nominal => self.fn_nominal(args, cell),
            Function::Effect => self.fn_effect(args, cell),
            Function::Pduration => self.fn_pduration(args, cell),
            Function::Tbillyield => self.fn_tbillyield(args, cell),
            Function::Tbillprice => self.fn_tbillprice(args, cell),
            Function::Tbilleq => self.fn_tbilleq(args, cell),
            Function::Dollarde => self.fn_dollarde(args, cell),
            Function::Dollarfr => self.fn_dollarfr(args, cell),
            Function::Ddb => self.fn_ddb(args, cell),
            Function::Db => self.fn_db(args, cell),
            Function::Cumprinc => self.fn_cumprinc(args, cell),
            Function::Cumipmt => self.fn_cumipmt(args, cell),
            // Engineering
            Function::Besseli => self.fn_besseli(args, cell),
            Function::Besselj => self.fn_besselj(args, cell),
            Function::Besselk => self.fn_besselk(args, cell),
            Function::Bessely => self.fn_bessely(args, cell),
            Function::Erf => self.fn_erf(args, cell),
            Function::ErfPrecise => self.fn_erfprecise(args, cell),
            Function::Erfc => self.fn_erfc(args, cell),
            Function::ErfcPrecise => self.fn_erfcprecise(args, cell),
            Function::Bin2dec => self.fn_bin2dec(args, cell),
            Function::Bin2hex => self.fn_bin2hex(args, cell),
            Function::Bin2oct => self.fn_bin2oct(args, cell),
            Function::Dec2Bin => self.fn_dec2bin(args, cell),
            Function::Dec2hex => self.fn_dec2hex(args, cell),
            Function::Dec2oct => self.fn_dec2oct(args, cell),
            Function::Hex2bin => self.fn_hex2bin(args, cell),
            Function::Hex2dec => self.fn_hex2dec(args, cell),
            Function::Hex2oct => self.fn_hex2oct(args, cell),
            Function::Oct2bin => self.fn_oct2bin(args, cell),
            Function::Oct2dec => self.fn_oct2dec(args, cell),
            Function::Oct2hex => self.fn_oct2hex(args, cell),
            Function::Bitand => self.fn_bitand(args, cell),
            Function::Bitlshift => self.fn_bitlshift(args, cell),
            Function::Bitor => self.fn_bitor(args, cell),
            Function::Bitrshift => self.fn_bitrshift(args, cell),
            Function::Bitxor => self.fn_bitxor(args, cell),
            Function::Complex => self.fn_complex(args, cell),
            Function::Imabs => self.fn_imabs(args, cell),
            Function::Imaginary => self.fn_imaginary(args, cell),
            Function::Imargument => self.fn_imargument(args, cell),
            Function::Imconjugate => self.fn_imconjugate(args, cell),
            Function::Imcos => self.fn_imcos(args, cell),
            Function::Imcosh => self.fn_imcosh(args, cell),
            Function::Imcot => self.fn_imcot(args, cell),
            Function::Imcsc => self.fn_imcsc(args, cell),
            Function::Imcsch => self.fn_imcsch(args, cell),
            Function::Imdiv => self.fn_imdiv(args, cell),
            Function::Imexp => self.fn_imexp(args, cell),
            Function::Imln => self.fn_imln(args, cell),
            Function::Imlog10 => self.fn_imlog10(args, cell),
            Function::Imlog2 => self.fn_imlog2(args, cell),
            Function::Impower => self.fn_impower(args, cell),
            Function::Improduct => self.fn_improduct(args, cell),
            Function::Imreal => self.fn_imreal(args, cell),
            Function::Imsec => self.fn_imsec(args, cell),
            Function::Imsech => self.fn_imsech(args, cell),
            Function::Imsin => self.fn_imsin(args, cell),
            Function::Imsinh => self.fn_imsinh(args, cell),
            Function::Imsqrt => self.fn_imsqrt(args, cell),
            Function::Imsub => self.fn_imsub(args, cell),
            Function::Imsum => self.fn_imsum(args, cell),
            Function::Imtan => self.fn_imtan(args, cell),
            Function::Convert => self.fn_convert(args, cell),
            Function::Delta => self.fn_delta(args, cell),
            Function::Gestep => self.fn_gestep(args, cell),

            Function::Subtotal => self.fn_subtotal(args, cell),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::{
        fs::File,
        io::{BufRead, BufReader},
    };

    use crate::functions::Function;

    #[test]
    fn function_iterator() {
        // This checks that the number of functions in the enum is the same
        // as the number of functions in the Iterator.

        // This is tricky. In Rust we cannot loop over all the members of an enum.
        // There are alternatives like using an external crate like strum.
        // But I am not in the mood for that.

        // What we do here is read this file , extract the functions in the enum
        // and check they are the same as in the iterator
        let file = File::open("src/functions/mod.rs").unwrap();
        let reader = BufReader::new(file);
        let mut start = false;
        let mut list = Vec::new();

        for line in reader.lines() {
            let text = line.unwrap();
            let text = text.trim().trim_end_matches(',');
            if text == "pub enum Function {" {
                start = true;
                continue;
            }
            if start {
                if text == "}" {
                    break;
                }
                if text.starts_with("//") {
                    // skip comments
                    continue;
                }
                if text.is_empty() {
                    // skip empty lines
                    continue;
                }
                list.push(text.to_owned());
            }
        }
        // We make a list with their functions names, but we escape ".": ERROR.TYPE => ERRORTYPE
        let iter_list = Function::into_iter()
            .map(|f| format!("{}", f).replace('.', ""))
            .collect::<Vec<_>>();

        let len = iter_list.len();

        assert_eq!(list.len(), len);
        // We still need to check there are no duplicates. This will fail if a function in iter_list
        // is included twice and one is missing
        for function in list {
            assert!(iter_list.contains(&function.to_uppercase()));
        }
    }
}
