use std::time::Instant;
use rayon::prelude::*;
use feagi_data_structures::{FeagiDataError, FeagiSignal, FeagiSignalIndex};
use feagi_data_structures::genomic::descriptors::{CorticalChannelCount, CorticalChannelIndex};
use feagi_data_structures::neuron_voxels::xyzp::CorticalMappedXYZPNeuronVoxels;
use crate::data_pipeline::{PipelineStageProperties, PipelineStagePropertyIndex, PipelineStageRunner};
use crate::neuron_voxel_coding::xyzp::NeuronVoxelXYZPDecoder;
use crate::wrapped_io_data::{WrappedIOData, WrappedIOType};

#[derive(Debug)]
pub(crate) struct MotorChannelStreamCaches {
    neuron_decoder: Box<dyn NeuronVoxelXYZPDecoder>,
    pipeline_runners: Vec<PipelineStageRunner>,
    most_recent_directly_decoded_outputs: Vec<WrappedIOData>,
    has_channel_been_updated: Vec<bool>,
    value_updated_callbacks: Vec<FeagiSignal<()>>,
}

impl MotorChannelStreamCaches {
    pub fn new(neuron_decoder: Box<dyn NeuronVoxelXYZPDecoder>, stage_properties_per_channels: Vec<Vec<Box<dyn PipelineStageProperties + Sync + Send>>>) -> Result<Self, FeagiDataError> {
        if stage_properties_per_channels.is_empty() {
            return Err(FeagiDataError::InternalError("MotorChannelStreamCaches Cannot be initialized with 0 channels!".into()))
        }

        let num_channels = stage_properties_per_channels.len();
        let mut pipeline_runners: Vec<PipelineStageRunner> = Vec::with_capacity(num_channels);
        let mut most_recent_directly_decoded_outputs: Vec<WrappedIOData> = Vec::with_capacity(num_channels);
        let mut callbacks: Vec<FeagiSignal<()>> = Vec::with_capacity(num_channels);

        for stage_properties_per_channel in stage_properties_per_channels {
            let pipeline_runner = PipelineStageRunner::new(stage_properties_per_channel)?;
            let blank_data = pipeline_runner.get_input_data_type().create_blank_data_of_type()?;
            callbacks.push(FeagiSignal::new());
            
            pipeline_runners.push(pipeline_runner);
            most_recent_directly_decoded_outputs.push(blank_data);
        }

        Ok(Self {
            neuron_decoder,
            pipeline_runners,
            most_recent_directly_decoded_outputs,
            has_channel_been_updated: vec![false; num_channels],
            value_updated_callbacks: callbacks,
        })
    }

    //region Properties

    pub fn number_of_channels(&self) -> CorticalChannelCount {
        (self.pipeline_runners.len() as u32).try_into().unwrap()
    }

    pub fn verify_channel_exists(&self, channel_index: CorticalChannelIndex) -> Result<(), FeagiDataError> {
        _ =  self.try_get_pipeline_runner(channel_index)?;
        Ok(())
    }

    //endregion

    //region Pipeline Runner Data

    pub fn try_get_channel_output_type(&self, cortical_channel_index: CorticalChannelIndex) -> Result<WrappedIOType, FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner(cortical_channel_index)?;
        Ok(pipeline_runner.get_output_data_type())
    }

    pub fn try_get_most_recent_preprocessed_motor_value(&self, cortical_channel_index: CorticalChannelIndex) -> Result<&WrappedIOData, FeagiDataError> {
        _ = self.try_get_pipeline_runner(cortical_channel_index)?;
        Ok(&self.most_recent_directly_decoded_outputs[*cortical_channel_index as usize])
    }

    pub fn try_get_most_recent_postprocessed_motor_value(&self, cortical_channel_index: CorticalChannelIndex) -> Result<&WrappedIOData, FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner(cortical_channel_index)?;
        Ok(pipeline_runner.get_most_recent_output())
    }

    pub fn try_get_channel_last_processed_instant(&self, cortical_channel_index: CorticalChannelIndex) -> Result<Instant, FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner(cortical_channel_index)?;
        Ok(pipeline_runner.get_last_processed_instant())
    }

    /*
    pub(crate) fn try_get_neuron_decode_data_location_ref_mut(&mut self, cortical_channel_index: CorticalChannelIndex) -> Result<&mut WrappedIOData, FeagiDataError> {
        match self.most_recent_directly_decoded_outputs.get_mut(*cortical_channel_index as usize) {
            Some(data) => Ok(data),
            None => Err(FeagiDataError::BadParameters(format!("Channel Index {} is out of bounds for SensoryChannelStreamCaches with {} channels!",
                                                              cortical_channel_index, self.pipeline_runners.len())))
        }

    }

     */

    //endregion

    //region Pipeline Stages

    pub fn try_get_single_stage_properties(&self, cortical_channel_index: CorticalChannelIndex, stage_index: PipelineStagePropertyIndex) -> Result<Box<dyn PipelineStageProperties + Sync + Send>, FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner(cortical_channel_index)?;
        pipeline_runner.try_get_single_stage_properties(stage_index)
    }
    
    pub fn get_all_stage_properties(&self, cortical_channel_index: CorticalChannelIndex) -> Result<Vec<Box<dyn PipelineStageProperties + Sync + Send>>, FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner(cortical_channel_index)?;
        Ok(pipeline_runner.get_all_stage_properties())
    }

    pub fn try_update_single_stage_properties(&mut self, cortical_channel_index: CorticalChannelIndex, pipeline_stage_property_index: PipelineStagePropertyIndex, replacing_property: Box<dyn PipelineStageProperties + Sync + Send>) -> Result<(), FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner_mut(cortical_channel_index)?;
        pipeline_runner.try_update_single_stage_properties(pipeline_stage_property_index, replacing_property)
    }
    
    pub fn try_update_all_stage_properties(&mut self, cortical_channel_index: CorticalChannelIndex, new_pipeline_stage_properties: Vec<Box<dyn PipelineStageProperties + Sync + Send>>) -> Result<(), FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner_mut(cortical_channel_index)?;
        pipeline_runner.try_update_all_stage_properties(new_pipeline_stage_properties)
    }

    pub fn try_replace_single_stage(&mut self, cortical_channel_index: CorticalChannelIndex, replacing_at_index: PipelineStagePropertyIndex, new_pipeline_stage_properties: Box<dyn PipelineStageProperties + Sync + Send>) -> Result<(), FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner_mut(cortical_channel_index)?;
        pipeline_runner.try_replace_single_stage(replacing_at_index, new_pipeline_stage_properties)
    }
    
    pub fn try_replace_all_stages(&mut self, cortical_channel_index: CorticalChannelIndex, new_pipeline_stage_properties: Vec<Box<dyn PipelineStageProperties + Sync + Send>>) -> Result<(), FeagiDataError> {
        let pipeline_runner = self.try_get_pipeline_runner_mut(cortical_channel_index)?;
        pipeline_runner.try_replace_all_stages(new_pipeline_stage_properties)
    }

    //endregion

    //region Callbacks

    pub fn try_connect_to_data_processed_signal<F>(&mut self, cortical_channel_index: CorticalChannelIndex, callback: F) -> Result<FeagiSignalIndex, FeagiDataError>
    where
        F: Fn(&()) + Send + Sync + 'static,
    {
        _ = self.try_get_pipeline_runner(cortical_channel_index)?;
        let idx = *cortical_channel_index as usize;
        Ok(self.value_updated_callbacks[idx].connect(callback))
    }

    pub fn try_disconnect_to_data_processed_signal(&mut self, cortical_channel_index: CorticalChannelIndex, signal_index: FeagiSignalIndex) -> Result<(), FeagiDataError> {
        _ = self.try_get_pipeline_runner(cortical_channel_index)?;
        let idx = *cortical_channel_index as usize;
        self.value_updated_callbacks[idx].disconnect(signal_index)
    }

    pub(crate) fn try_run_callbacks_on_changed_channels(&mut self) -> Result<(), FeagiDataError> {
        for channel_index in 0..self.pipeline_runners.len() {
            if !self.has_channel_been_updated[channel_index] {
                continue;
            }
            self.value_updated_callbacks[channel_index].emit(&()); // no value
        }
        self.has_channel_been_updated.fill(false);
        Ok(())
    }

    //endregion

    pub(crate) fn try_read_neuron_data_to_wrapped_io_data(&mut self, neuron_data: &CorticalMappedXYZPNeuronVoxels, time_of_decode: Instant) -> Result<(), FeagiDataError> {
        self.neuron_decoder.read_neuron_data_multi_channel(neuron_data, time_of_decode, &mut self.most_recent_directly_decoded_outputs, &mut self.has_channel_been_updated)?;
        self.most_recent_directly_decoded_outputs.par_iter()
            .zip(&self.has_channel_been_updated)
            .zip(&mut self.pipeline_runners)
            .try_for_each(|((output, &has_changed), pipeline_runner)| {
                if has_changed {
                    pipeline_runner.try_update_value(output, time_of_decode)?;
                }
                Ok(())
            })?;
        Ok(())
    }

    //region Internal

    #[inline]
    fn try_get_pipeline_runner(&self, cortical_channel_index: CorticalChannelIndex) -> Result<&PipelineStageRunner, FeagiDataError> {
        match self.pipeline_runners.get(*cortical_channel_index as usize) {
            Some(pipeline_runner) => Ok(pipeline_runner),
            None => Err(FeagiDataError::BadParameters(format!("Channel Index {} is out of bounds for MotorChannelStreamCaches with {} channels!",
                                                              cortical_channel_index, self.pipeline_runners.len())))
        }

    }

    #[inline]
    fn try_get_pipeline_runner_mut(&mut self, cortical_channel_index: CorticalChannelIndex) -> Result<&mut PipelineStageRunner, FeagiDataError> {
        let num_runners = self.pipeline_runners.len();
        match self.pipeline_runners.get_mut(*cortical_channel_index as usize) {
            Some(pipeline_runner) => Ok(pipeline_runner),
            None => Err(FeagiDataError::BadParameters(format!("Channel Index {} is out of bounds for MotorChannelStreamCaches with {} channels!",
                                                              cortical_channel_index, num_runners)))
        }

    }

    //endregion

}