use crate::{InputTransform, PyEngineInput, PyEngineInputType};
use pyo3::{PyResult, exceptions::PyTypeError};
use radiate::{chromosomes::NumericAllele, *};
use radiate_utils::{Float, Integer};
use std::collections::HashMap;

type AlterConv<C> = fn(&PyEngineInput) -> Alterer<C>;

macro_rules! table {
    ($($name:expr => $fn:ident),* $(,)?) => {{
        use std::collections::HashMap;
        let mut m: HashMap<&'static str, AlterConv<_>> = HashMap::new();
        $(
            m.insert($name, |inp| {
                $fn(inp).alterer()
            });
        )*
        m
    }};
}

macro_rules! impl_input_transform_for {
    ($chrom:ty, $map_fn:ident) => {
        impl InputTransform<Vec<Alterer<$chrom>>> for PyEngineInput {
            fn transform(&self) -> Vec<Alterer<$chrom>> {
                alters_from_table(self, $map_fn())
                    .expect("Failed to convert alterer from PyEngineInput")
                    .into()
            }
        }
    };
}

impl_input_transform_for!(IntChromosome<u8>, int_alterers);
impl_input_transform_for!(IntChromosome<u16>, int_alterers);
impl_input_transform_for!(IntChromosome<u32>, int_alterers);
impl_input_transform_for!(IntChromosome<u64>, int_alterers);

impl_input_transform_for!(IntChromosome<i8>, int_alterers);
impl_input_transform_for!(IntChromosome<i16>, int_alterers);
impl_input_transform_for!(IntChromosome<i32>, int_alterers);
impl_input_transform_for!(IntChromosome<i64>, int_alterers);

impl_input_transform_for!(FloatChromosome<f32>, float_alterers);
impl_input_transform_for!(FloatChromosome<f64>, float_alterers);

impl_input_transform_for!(CharChromosome, char_alterers);
impl_input_transform_for!(BitChromosome, bit_alterers);
impl_input_transform_for!(PermutationChromosome<usize>, perm_alterers);
impl_input_transform_for!(GraphChromosome<Op<f32>>, graph_alterers);
impl_input_transform_for!(TreeChromosome<Op<f32>>, tree_alterers);

impl<C> InputTransform<Vec<Alterer<C>>> for &[PyEngineInput]
where
    C: Chromosome + Clone,
    PyEngineInput: InputTransform<Vec<Alterer<C>>>,
{
    fn transform(&self) -> Vec<Alterer<C>> {
        let mut alters: Vec<Alterer<C>> = Vec::new();

        for input in self.iter() {
            if input.input_type != PyEngineInputType::Alterer {
                panic!("Input type {:?} not an alterer", input.input_type);
            }

            let mut converted = input.transform();
            alters.append(&mut converted);
        }

        alters
    }
}

fn alters_from_table<C>(
    input: &PyEngineInput,
    table: HashMap<&'static str, AlterConv<C>>,
) -> PyResult<Vec<Alterer<C>>>
where
    C: Chromosome + Clone + 'static,
{
    if input.input_type != PyEngineInputType::Alterer {
        return Err(PyTypeError::new_err(format!(
            "Input type {:?} is not an alterer",
            input.input_type
        )));
    }

    if let Some(make) = table.get(input.component.as_str()) {
        Ok(vec![make(input)])
    } else {
        Err(PyTypeError::new_err(format!(
            "Invalid alterer type: {}",
            input.component
        )))
    }
}

// INT
fn int_alterers<I: Integer>() -> HashMap<&'static str, AlterConv<IntChromosome<I>>> {
    table! {
        crate::names::MULTI_POINT_CROSSOVER   => convert_multi_point_crossover,
        crate::names::UNIFORM_CROSSOVER       => convert_uniform_crossover,
        crate::names::MEAN_CROSSOVER          => convert_mean_crossover,
        crate::names::SHUFFLE_CROSSOVER       => convert_shuffle_crossover,

        crate::names::ARITHMETIC_MUTATOR      => convert_arithmetic_mutator,
        crate::names::SWAP_MUTATOR            => convert_swap_mutator,
        crate::names::SCRAMBLE_MUTATOR        => convert_scramble_mutator,
        crate::names::UNIFORM_MUTATOR         => convert_uniform_mutator,
        crate::names::INVERSION_MUTATOR       => convert_inversion_mutator,
    }
}

// FLOAT
fn float_alterers<F: Float + NumericAllele>() -> HashMap<&'static str, AlterConv<FloatChromosome<F>>>
{
    table! {
        crate::names::MULTI_POINT_CROSSOVER        => convert_multi_point_crossover,
        crate::names::UNIFORM_CROSSOVER            => convert_uniform_crossover,
        crate::names::MEAN_CROSSOVER               => convert_mean_crossover,
        crate::names::INTERMEDIATE_CROSSOVER       => convert_intermediate_crossover,
        crate::names::BLEND_CROSSOVER              => convert_blend_crossover,
        crate::names::SIMULATED_BINARY_CROSSOVER   => convert_simulated_binary_crossover,

        crate::names::GAUSSIAN_MUTATOR             => convert_gaussian_mutator,
        crate::names::ARITHMETIC_MUTATOR           => convert_arithmetic_mutator,
        crate::names::SWAP_MUTATOR                 => convert_swap_mutator,
        crate::names::SCRAMBLE_MUTATOR             => convert_scramble_mutator,
        crate::names::UNIFORM_MUTATOR              => convert_uniform_mutator,
        crate::names::INVERSION_MUTATOR            => convert_inversion_mutator,
        crate::names::POLYNOMIAL_MUTATOR           => convert_polynomial_mutator,
        crate::names::JITTER_MUTATOR               => convert_jitter_mutator,
    }
}

// CHAR
fn char_alterers() -> HashMap<&'static str, AlterConv<CharChromosome>> {
    table! {
        crate::names::MULTI_POINT_CROSSOVER   => convert_multi_point_crossover,
        crate::names::UNIFORM_CROSSOVER       => convert_uniform_crossover,
        crate::names::SHUFFLE_CROSSOVER       => convert_shuffle_crossover,

        crate::names::SWAP_MUTATOR            => convert_swap_mutator,
        crate::names::SCRAMBLE_MUTATOR        => convert_scramble_mutator,
        crate::names::UNIFORM_MUTATOR         => convert_uniform_mutator,
        crate::names::INVERSION_MUTATOR       => convert_inversion_mutator,
    }
}

// BIT
fn bit_alterers() -> HashMap<&'static str, AlterConv<BitChromosome>> {
    table! {
        crate::names::MULTI_POINT_CROSSOVER   => convert_multi_point_crossover,
        crate::names::UNIFORM_CROSSOVER       => convert_uniform_crossover,
        crate::names::SHUFFLE_CROSSOVER       => convert_shuffle_crossover,

        crate::names::SWAP_MUTATOR            => convert_swap_mutator,
        crate::names::SCRAMBLE_MUTATOR        => convert_scramble_mutator,
        crate::names::UNIFORM_MUTATOR         => convert_uniform_mutator,
        crate::names::INVERSION_MUTATOR       => convert_inversion_mutator,
    }
}

// PERMUTATION<usize>
fn perm_alterers() -> HashMap<&'static str, AlterConv<PermutationChromosome<usize>>> {
    table! {
        crate::names::PARTIALLY_MAPPED_CROSSOVER  => convert_partially_mapped_crossover,
        crate::names::EDGE_RECOMBINE_CROSSOVER    => convert_edge_recombine_crossover,

        crate::names::SWAP_MUTATOR                => convert_swap_mutator,
        crate::names::SCRAMBLE_MUTATOR            => convert_scramble_mutator,
        crate::names::UNIFORM_MUTATOR             => convert_uniform_mutator,
        crate::names::INVERSION_MUTATOR           => convert_inversion_mutator,
    }
}

// GRAPH<Op<f32>>
fn graph_alterers() -> HashMap<&'static str, AlterConv<GraphChromosome<Op<f32>>>> {
    table! {
        crate::names::GRAPH_CROSSOVER       => convert_graph_crossover,

        crate::names::GRAPH_MUTATOR         => convert_graph_mutator,
        crate::names::OPERATION_MUTATOR     => convert_operation_mutator,
    }
}

// TREE<Op<f32>>
fn tree_alterers() -> HashMap<&'static str, AlterConv<TreeChromosome<Op<f32>>>> {
    table! {
        crate::names::TREE_CROSSOVER        => convert_tree_crossover,

        crate::names::HOIST_MUTATOR         => convert_hoist_mutator,
        crate::names::OPERATION_MUTATOR     => convert_operation_mutator,
    }
}

/// Concrete alterer conversion functions
/// These functions take a PyEngineInput and extract parameters to create the corresponding alterer.
/// Each function corresponds to a specific alterer type.
///
/// Because python is dynamically typed, there is no guarantee that the parameters exist or are of the correct type.
/// We do our best above to ensure that the correct parameters are provided by relying
/// as much as possible on rust's type system, but we provide default values where appropriate.
/// If a parameter is missing, we use a sensible default.
/// -------------------------------------------------------------------

fn convert_jitter_mutator(input: &PyEngineInput) -> JitterMutator {
    let rate = input.get_rate().unwrap();
    let magnitude = input.get_f32("magnitude").unwrap_or(0.5);
    JitterMutator::new(rate, magnitude)
}

fn convert_inversion_mutator(input: &PyEngineInput) -> InversionMutator {
    let rate = input.get_rate().unwrap();
    InversionMutator::new(rate)
}

fn convert_hoist_mutator(input: &PyEngineInput) -> HoistMutator {
    let rate = input.get_rate().unwrap();
    HoistMutator::new(rate)
}

fn convert_tree_crossover(input: &PyEngineInput) -> TreeCrossover {
    let rate = input.get_rate().unwrap();
    let max_size = input.get_usize("max_size").unwrap_or(30);
    TreeCrossover::new(rate).with_max_size(max_size)
}

fn convert_multi_point_crossover(input: &PyEngineInput) -> MultiPointCrossover {
    let rate = input.get_rate().unwrap();
    let points = input.get_usize("num_points").unwrap_or(2);

    MultiPointCrossover::new(rate, points)
}

fn convert_uniform_crossover(input: &PyEngineInput) -> UniformCrossover {
    let rate = input.get_rate().unwrap();
    UniformCrossover::new(rate)
}

fn convert_uniform_mutator(input: &PyEngineInput) -> UniformMutator {
    let rate = input.get_rate().unwrap();
    UniformMutator::new(rate)
}

fn convert_mean_crossover(input: &PyEngineInput) -> MeanCrossover {
    let rate = input.get_rate().unwrap();
    MeanCrossover::new(rate)
}

fn convert_intermediate_crossover(input: &PyEngineInput) -> IntermediateCrossover {
    let rate = input.get_rate().unwrap();
    let alpha = input.get_f32("alpha").unwrap_or(0.5);
    IntermediateCrossover::new(rate, alpha)
}

fn convert_blend_crossover(input: &PyEngineInput) -> BlendCrossover {
    let rate = input.get_rate().unwrap();
    let alpha = input.get_f32("alpha").unwrap_or(0.5);
    BlendCrossover::new(rate, alpha)
}

fn convert_shuffle_crossover(input: &PyEngineInput) -> ShuffleCrossover {
    let rate = input.get_rate().unwrap();
    ShuffleCrossover::new(rate)
}

fn convert_partially_mapped_crossover(input: &PyEngineInput) -> PMXCrossover {
    let rate = input.get_rate().unwrap();
    PMXCrossover::new(rate)
}

fn convert_scramble_mutator(input: &PyEngineInput) -> ScrambleMutator {
    let rate = input.get_rate().unwrap();
    ScrambleMutator::new(rate)
}

fn convert_swap_mutator(input: &PyEngineInput) -> SwapMutator {
    let rate = input.get_rate().unwrap();
    SwapMutator::new(rate)
}

fn convert_arithmetic_mutator(input: &PyEngineInput) -> ArithmeticMutator {
    let rate = input.get_rate().unwrap();
    ArithmeticMutator::new(rate)
}

fn convert_gaussian_mutator(input: &PyEngineInput) -> GaussianMutator {
    let rate = input.get_rate().unwrap();

    GaussianMutator::new(rate)
}

fn convert_simulated_binary_crossover(input: &PyEngineInput) -> SimulatedBinaryCrossover {
    let rate = input.get_rate().unwrap();
    let contiguity = input.get_f32("contiguity").unwrap_or(0.5);
    SimulatedBinaryCrossover::new(rate, contiguity)
}

fn convert_graph_crossover(input: &PyEngineInput) -> GraphCrossover {
    let rate = input.get_rate().unwrap();
    let parent_node_rate = input.get_f32("parent_node_rate").unwrap_or(0.5);

    GraphCrossover::new(rate, parent_node_rate)
}

fn convert_graph_mutator(input: &PyEngineInput) -> GraphMutator {
    let vertex_rate = input.get_f32("vertex_rate").unwrap_or(0.5);
    let edge_rate = input.get_f32("edge_rate").unwrap_or(0.5);
    let allow_recurrent = input.get_bool("allow_recurrent").unwrap_or(false);

    GraphMutator::new(vertex_rate, edge_rate).allow_recurrent(allow_recurrent)
}

fn convert_operation_mutator(input: &PyEngineInput) -> OperationMutator {
    let rate = input.get_rate().unwrap();
    let replace_rate = input.get_f32("replace_rate").unwrap_or(0.5);

    OperationMutator::new(rate, replace_rate)
}

fn convert_edge_recombine_crossover(input: &PyEngineInput) -> EdgeRecombinationCrossover {
    let rate = input.get_rate().unwrap();
    EdgeRecombinationCrossover::new(rate)
}

fn convert_polynomial_mutator(input: &PyEngineInput) -> PolynomialMutator {
    let rate = input.get_rate().unwrap();
    let eta = input.get_f32("eta").unwrap_or(20.0);
    PolynomialMutator::new(rate, eta)
}
