use std::str::FromStr;
use thiserror::Error;

#[cxx::bridge]
pub mod ffi {
    #[namespace = "fastNLO"]
    #[repr(u32)]
    enum EScaleFunctionalForm {
        kScale1,
        kScale2,
        kQuadraticSum,
        kQuadraticMean,
        kQuadraticSumOver4,
        kLinearMean,
        kLinearSum,
        kScaleMax,
        kScaleMin,
        kProd,
        kS2plusS1half,
        kPow4Sum,
        kWgtAvg,
        kS2plusS1fourth,
        kExpProd2,
        kExtern,
        kConst,
    }

    struct pair_double_double {
        first: f64,
        second: f64,
    }

    struct pair_int_int {
        first: i32,
        second: i32,
    }

    extern "C++" {
        include!("fastnlotk/fastNLOConstants.h");

        #[namespace = "fastNLO"]
        type EScaleFunctionalForm;

        #[namespace = "fastNLO"]
        type ESMCalculation;

        #[namespace = "fastNLO"]
        type ESMOrder;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffAddBase.h");

        type fastNLOCoeffAddBase;

        fn GetNevt(&self, _: i32, _: i32) -> f64;
        fn GetNObsBin(&self) -> i32;
        fn GetNPDF(&self) -> i32;
        fn GetNSubproc(&self) -> i32;
        fn GetNpow(&self) -> i32;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffAddFix.h");

        type fastNLOCoeffAddFix;

        fn GetNPDFDim(&self) -> i32;
        fn GetNxmax(&self, _: i32) -> i32;
        fn GetNxtot2(&self, _: i32) -> i32;
        fn GetScaleFactor(&self, _: i32) -> f64;
        fn GetSigmaTilde(&self, _: i32, _: i32, _: i32, _: i32, _: i32) -> f64;
        fn GetTotalScalenodes(&self) -> i32;
        fn GetTotalScalevars(&self) -> i32;
        fn GetXIndex(&self, _: i32, _: i32, _: i32) -> i32;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOPDFLinearCombinations.h");

        type fastNLOPDFLinearCombinations;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOLHAPDF.h");

        type fastNLOLHAPDF;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffBase.h");

        type fastNLOCoeffBase;

        fn IsEnabled(&self) -> bool;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOReader.h");

        type fastNLOReader;

        fn CalcCrossSection(self: Pin<&mut fastNLOReader>);
        unsafe fn GetIsFlexibleScaleTable(&self, _: *mut fastNLOCoeffAddBase) -> bool;
        fn GetMuRFunctionalForm(&self) -> EScaleFunctionalForm;
        fn GetMuFFunctionalForm(&self) -> EScaleFunctionalForm;
        fn SetScaleFactorsMuRMuF(self: Pin<&mut Self>, _: f64, _: f64) -> bool;
        fn SetMuRFunctionalForm(self: Pin<&mut fastNLOReader>, _: EScaleFunctionalForm);
        fn SetMuFFunctionalForm(self: Pin<&mut fastNLOReader>, _: EScaleFunctionalForm);
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOTable.h");

        type fastNLOTable;

        fn GetCoeffTable(&self, _: i32) -> *mut fastNLOCoeffBase;
        fn GetIpublunits(&self) -> i32;
        fn GetNObsBin(&self) -> u32;
        fn GetNumDiffBin(&self) -> u32;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffAddFlex.h");

        type fastNLOCoeffAddFlex;

        fn GetIXsectUnits(&self) -> i32;
        fn GetNScaleDep(&self) -> i32;
        fn GetPDFPDG(&self, _: i32) -> i32;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffData.h");

        type fastNLOCoeffData;
    }

    unsafe extern "C++" {
        include!("fastnlotk/fastNLOCoeffMult.h");

        type fastNLOCoeffMult;
    }

    unsafe extern "C++" {
        include!("pineappl_fastnlo/src/fastnlo.hpp");

        fn CalcPDFLinearCombination(
            _: &fastNLOPDFLinearCombinations,
            _: &fastNLOCoeffAddBase,
            _: &[f64],
            _: &[f64],
            _: bool,
        ) -> Vec<f64>;

        fn GetCrossSection(_: Pin<&mut fastNLOReader>, _: bool) -> Vec<f64>;
        fn GetNx(_: &fastNLOCoeffAddFlex, _: usize) -> usize;
        fn GetDimLabels(_: &fastNLOTable) -> Vec<String>;
        fn GetObsBinDimBounds(_: &fastNLOTable, _: u32, _: u32) -> pair_double_double;
        fn GetScDescr(_: &fastNLOTable) -> Vec<String>;
        fn GetXSDescr(_: &fastNLOTable) -> String;
        fn GetPDFCoeff(_: &fastNLOCoeffAddBase, index: usize) -> Vec<pair_int_int>;
        fn GetPDFCoeffSize(_: &fastNLOCoeffAddBase) -> usize;
        fn GetScaleNodes(_: &fastNLOCoeffAddFix, _: i32, _: i32) -> Vec<f64>;
        fn GetScaleNodes1(_: &fastNLOCoeffAddFlex, _: i32) -> Vec<f64>;
        fn GetScaleNodes2(_: &fastNLOCoeffAddFlex, _: i32) -> Vec<f64>;
        fn GetXNodes1(_: &fastNLOCoeffAddBase, _: i32) -> Vec<f64>;
        fn GetXNodes2(_: &fastNLOCoeffAddBase, _: i32) -> Vec<f64>;
        fn GetSigmaTilde(
            _: &fastNLOCoeffAddFlex,
            _: usize,
            _: usize,
            _: usize,
            _: usize,
            _: usize,
            _: i32,
        ) -> f64;

        fn downcast_coeff_add_fix_to_base(_: &fastNLOCoeffAddFix) -> &fastNLOCoeffAddBase;
        fn downcast_coeff_add_flex_to_base(_: &fastNLOCoeffAddFlex) -> &fastNLOCoeffAddBase;
        fn downcast_lhapdf_to_reader(_: &fastNLOLHAPDF) -> &fastNLOReader;
        fn downcast_lhapdf_to_reader_mut(_: Pin<&mut fastNLOLHAPDF>) -> Pin<&mut fastNLOReader>;
        fn downcast_lhapdf_to_table(_: &fastNLOLHAPDF) -> &fastNLOTable;
        fn downcast_reader_to_pdf_linear_combinations(
            _: &fastNLOReader,
        ) -> &fastNLOPDFLinearCombinations;

        unsafe fn dynamic_cast_coeff_add_fix(
            _: *const fastNLOCoeffBase,
        ) -> *const fastNLOCoeffAddFix;
        unsafe fn dynamic_cast_coeff_add_flex(
            _: *const fastNLOCoeffBase,
        ) -> *const fastNLOCoeffAddFlex;

        fn make_fastnlo_lhapdf_with_name_file_set(
            _: &str,
            _: &str,
            _: i32,
        ) -> UniquePtr<fastNLOLHAPDF>;
    }
}

#[derive(Debug, Error)]
#[error("invalid ScaleFunctionalForm syntax: {0}")]
pub struct ScaleFunctionalFormParseError(String);

impl FromStr for ffi::EScaleFunctionalForm {
    type Err = ScaleFunctionalFormParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "kScale1" => Ok(Self::kScale1),
            "kScale2" => Ok(Self::kScale2),
            "kQuadraticSum" => Ok(Self::kQuadraticSum),
            "kQuadraticMean" => Ok(Self::kQuadraticMean),
            "kQuadraticSumOver4" => Ok(Self::kQuadraticSumOver4),
            "kLinearMean" => Ok(Self::kLinearMean),
            "kLinearSum" => Ok(Self::kLinearSum),
            "kScaleMax" => Ok(Self::kScaleMax),
            "kScaleMin" => Ok(Self::kScaleMin),
            "kProd" => Ok(Self::kProd),
            "kS2plusS1half" => Ok(Self::kS2plusS1half),
            "kPow4Sum" => Ok(Self::kPow4Sum),
            "kWgtAvg" => Ok(Self::kWgtAvg),
            "kS2plusS1fourth" => Ok(Self::kS2plusS1fourth),
            "kExpProd2" => Ok(Self::kExpProd2),
            "kExtern" => Ok(Self::kExtern),
            "kConst" => Ok(Self::kConst),
            _ => Err(ScaleFunctionalFormParseError(s.to_string())),
        }
    }
}

impl ffi::EScaleFunctionalForm {
    pub fn compute_scale(self, s1: f64, s2: f64) -> f64 {
        match self {
            ffi::EScaleFunctionalForm::kScale1 => s1 * s1,
            ffi::EScaleFunctionalForm::kScale2 => s2 * s2,
            ffi::EScaleFunctionalForm::kQuadraticSum => s1 * s1 + s2 * s2,
            ffi::EScaleFunctionalForm::kQuadraticMean => 0.5 * (s1 * s1 + s2 * s2),
            ffi::EScaleFunctionalForm::kQuadraticSumOver4 => 0.25 * (s1 * s1 + s2 * s2),
            ffi::EScaleFunctionalForm::kLinearMean => 0.25 * (s1 + s2).powi(2),
            ffi::EScaleFunctionalForm::kLinearSum => (s1 + s2).powi(2),
            ffi::EScaleFunctionalForm::kScaleMax => s1.max(s2).powi(2),
            ffi::EScaleFunctionalForm::kScaleMin => s1.min(s2).powi(2),
            ffi::EScaleFunctionalForm::kProd => (s1 * s2).powi(2),
            ffi::EScaleFunctionalForm::kS2plusS1half => 0.5 * (s1 * s1 + 2.0 * s2 * s2),
            ffi::EScaleFunctionalForm::kPow4Sum => (s1.powi(4) + s2.powi(4)).sqrt(),
            ffi::EScaleFunctionalForm::kWgtAvg => (s1.powi(4) + s2.powi(4)) / (s1 * s1 + s2 * s2),
            ffi::EScaleFunctionalForm::kS2plusS1fourth => 0.25 * s1 * s1 + s2 * s2,
            ffi::EScaleFunctionalForm::kExpProd2 => (s1 * (0.3 * s2).exp()).powi(2),
            ffi::EScaleFunctionalForm::kExtern => todo!(),
            ffi::EScaleFunctionalForm::kConst => todo!(),
            _ => unreachable!(),
        }
    }
}
