use std::fmt::Debug;
use std::fs::File;
use std::marker::PhantomData;
use tboard::EventWriter;
use tch::nn::Optimizer;
use tch::Tensor;
use amfiteatr_core::agent::{AgentStepView, AgentTrajectory, InformationSet, Policy};
use amfiteatr_core::scheme::Scheme;
use amfiteatr_core::error::{AmfiteatrError, TensorError};
use amfiteatr_core::util::{MaybeContainsOne, TensorboardSupport};
use crate::error::AmfiteatrRlError;
use crate::policy::actor_critic::base::{ConfigA2C, PolicyHelperA2C, PolicyTrainHelperA2C};
use crate::policy::{CyclicReplayBufferActorCritic, LearnSummary, LearningNetworkPolicyGeneric};
use crate::{MaskingInformationSetAction, tensor_data};
use crate::tensor_data::{ContextDecodeIndexI64, ContextEncodeIndexI64, ContextEncodeTensor, TensorDecoding, TensorEncoding, TensorIndexI64Encoding};
use crate::torch_net::{ActorCriticOutput, NeuralNet, NeuralNetActorCritic, TensorActorCritic};

/// Policy A2C for discrete action space with single distribution using [`tch`] crate for `torch` backed
/// [`Tensors`](tch::Tensor).
pub struct PolicyDiscreteA2C<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding,
>{
    config: ConfigA2C,
    network: NeuralNetActorCritic,
    optimizer: Optimizer,
    _dp: PhantomData<S>,
    _is: PhantomData<InfoSet>,
    info_set_encoding: InfoSetConversionContext,
    action_encoding: ActionBuildContext,

    exploration: bool,
    tboard_writer: Option<tboard::EventWriter<File>>,
    global_step: i64,
    cyclic_replay_buffer: Option<CyclicReplayBufferActorCritic<S>>,

}

impl<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding,
> PolicyDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext>{
    pub fn new(
        config: ConfigA2C,
        network: NeuralNetActorCritic,
        optimizer: Optimizer,
        info_set_conversion_context: InfoSetConversionContext,
        action_build_context: ActionBuildContext,
    ) -> Self{
        Self{
            config,
            network,
            optimizer,
            _dp: Default::default(),
            _is: Default::default(),
            info_set_encoding: info_set_conversion_context,
            action_encoding: action_build_context,
            exploration: true,
            tboard_writer: None,
            global_step: 0,
            cyclic_replay_buffer: None,
        }
    }
    /*
    /// Creates [`tboard::EventWriter`]. Initialy policy does not use `tensorboard` directory to store epoch
    /// training results (like entropy, policy loss, value loss). However, you cen provide it with directory
    /// to create tensorboard files.
    pub fn add_tboard_directory<P: AsRef<std::path::Path>>(&mut self, directory_path: P) -> Result<(), AmfiteatrError<S>>{
        let tboard = EventWriter::create(directory_path).map_err(|e|{
            AmfiteatrError::TboardFlattened {context: "Creating tboard EventWriter".into(), error: format!("{e}")}
        })?;
        self.tboard_writer = Some(tboard);
        Ok(())
    }

     */

    /// Initializes cyclic replay buffer with requested capacity.
    /// Unless the method is invoked policy will not use this buffer and only recent trajectories
    /// will be used in learning.
    pub fn initialize_cyclic_replay_buffer(&mut self, capacity: usize) -> Result<(), AmfiteatrError<S>>
    where <S as Scheme>::ActionType: ContextDecodeIndexI64<ActionBuildContext> + ContextEncodeIndexI64<ActionBuildContext>,
    ActionBuildContext: TensorIndexI64Encoding{



        let replay_buffer = CyclicReplayBufferActorCritic::new(
            capacity,
            self.info_set_encoding.desired_shape(),
            self.action_encoding.expected_input_shape()[0],
            tch::Kind::Float, // possibly change one time, when policy will hold information about it,
            self.network.device(),
            self.is_action_masking_supported()
        )?;
        self.cyclic_replay_buffer = Some(replay_buffer);

        Ok(())
    }


}

impl<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding,
> TensorboardSupport<S> for PolicyDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext>
{
    /// Creates [`tboard::EventWriter`]. Initially policy does not use `tensorboard` directory to store epoch
    /// training results (like entropy, policy loss, value loss). However, you cen provide it with directory
    /// to create tensorboard files.
    fn add_tboard_directory(&mut self, directory_path: &std::path::Path) -> Result<(), AmfiteatrError<S>> {
        let tboard = EventWriter::create(directory_path).map_err(|e| {
            AmfiteatrError::TboardFlattened { context: "Creating tboard EventWriter".into(), error: format!("{e}") }
        })?;
        self.tboard_writer = Some(tboard);
        Ok(())
    }
    fn t_write_scalar(&mut self, step: i64, tag: &str, value: f32) -> Result<bool, AmfiteatrError<S>> {
        match &mut self.tboard_writer{
            None => Ok(false),
            Some(writer) => {
                writer.write_scalar(step, tag, value)
                    .map_err(|e| AmfiteatrError::TboardFlattened {
                        context: "Tboard - writing scalar (PPO)".to_string(),
                        error: e.to_string(),
                    })?;
                Ok(true)
            }
        }
    }
}
impl<
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding
    + tensor_data::ActionTensorFormat<Tensor>,
> PolicyHelperA2C<S> for PolicyDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
where <S as Scheme>::ActionType:
ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type InfoSet = InfoSet;
    type InfoSetConversionContext = InfoSetEncoding;
    type ActionConversionContext = ActionEncoding;
    type NetworkOutput = TensorActorCritic;
    type Config = ConfigA2C;

    fn config(&self) -> &Self::Config {
        &self.config
    }

    fn optimizer_mut(&mut self) -> &mut Optimizer {
        &mut self.optimizer
    }

    fn network(&self) -> &NeuralNet<Self::NetworkOutput> {
        &self.network
    }

    fn tboard_writer(&mut self) -> Option<&mut EventWriter<std::fs::File>> {
        self.tboard_writer.as_mut()
    }

    fn global_learning_step(&self) -> i64 {
        self.global_step
    }

    fn set_global_learning_step(&mut self, step: i64) {
        self.global_step = step;
    }

    fn info_set_encoding(&self) -> &Self::InfoSetConversionContext {
        &self.info_set_encoding
    }

    fn action_encoding(&self) -> &Self::ActionConversionContext {
        &self.action_encoding
    }

    fn dist(&self, _info_set: &Self::InfoSet, network_output: &Self::NetworkOutput) -> Result<<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, AmfiteatrError<S>> {
        Ok(network_output.actor.f_softmax(-1, tch::Kind::Float)
            .map_err(|e| TensorError::from_tch_with_context(e, "Calculating action distribution (a2c_dist)".into()))?)
    }

    fn is_action_masking_supported(&self) -> bool {
        false
    }

    fn generate_action_masks(&self, _information_set: &Self::InfoSet) -> Result<<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, AmfiteatrError<S>> {
        Err(AmfiteatrError::Custom("Action masking is not supported.".into()))
    }

    fn is_exploration_on(&self) -> bool {
        self.exploration
    }

    fn try_action_from_choice_tensor(&self, choice_tensor: &<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType) -> Result<S::ActionType, AmfiteatrError<S>> {
        let index = choice_tensor.f_int64_value(&[0])
            .map_err(|e| AmfiteatrError::Tensor { error: TensorError::Torch {
                origin: format!("{}", e),
                context: "Converting choice tensor to i64 action index.".to_string() } }
            )?;
        Ok(<S::ActionType>::try_from_index(index, &self.action_encoding)?)
    }

    fn vectorize_action_and_create_category_mask(&self, action: &S::ActionType) -> Result<(<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, <Self::NetworkOutput as ActorCriticOutput>::ActionTensorType), AmfiteatrError<S>> {
        let act_i = action.try_to_index(&self.action_encoding)?;


        Ok((Tensor::from(act_i), Tensor::from(true)))
    }

    fn batch_get_logprob_entropy_critic(&self, info_set_batch: &Tensor, action_param_batches: &<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, action_category_mask_batches: Option<&<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType>, action_forward_mask_batches: Option<&<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType>) -> Result<(Tensor, Tensor, Tensor), AmfiteatrError<S>> {

        /*
        let a2c_net = self.network().net()(info_set_batch);

        let (log_prob, entropy) = a2c_net.batch_get_logprob_and_entropy(
            action_param_batches,
            action_category_mask_batches,
            action_forward_mask_batches
        )?;

        Ok((log_prob, entropy, a2c_net.critic))

         */
        let critic_actor= self.network.net()(info_set_batch);

        let batch_logprob = critic_actor.batch_log_probability_of_action::<S>(
            action_param_batches,
            action_forward_mask_batches,
            action_category_mask_batches
        )?;
        let batch_entropy = critic_actor.batch_entropy_masked(
            action_forward_mask_batches,
            action_category_mask_batches

        ).map_err(|e|AmfiteatrError::Tensor {
            error: TensorError::Torch {
                context: "batch_get_actor_critic_with_logprob_and_entropy".into(),
                origin: format!("{e}")
            }
        })?;



        Ok((batch_logprob, batch_entropy, critic_actor.critic))

    }
}

impl<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding
    + tensor_data::ActionTensorFormat<Tensor>,
> Policy<S> for PolicyDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
    where <S as Scheme>::ActionType:
    ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type InfoSetType = InfoSet;

    fn select_action(&self, state: &Self::InfoSetType) -> Result<S::ActionType, AmfiteatrError<S>> {
        self.a2c_select_action(state)
    }
}

impl<

    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding
    + tensor_data::ActionTensorFormat<Tensor>,
> MaybeContainsOne<CyclicReplayBufferActorCritic<S>> for PolicyDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>{
    fn get(&self) -> Option<&CyclicReplayBufferActorCritic<S>> {
        self.cyclic_replay_buffer.as_ref()
    }

    fn get_mut(&mut self) -> Option<&mut CyclicReplayBufferActorCritic<S>> {
        self.cyclic_replay_buffer.as_mut()
    }
}

impl<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding
    + tensor_data::ActionTensorFormat<Tensor>,
> LearningNetworkPolicyGeneric<S> for PolicyDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
    where <S as Scheme>::ActionType:
    ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type Summary = LearnSummary;

    /*
    fn var_store(&self) -> &VarStore {
        self.network().var_store()
    }

    fn var_store_mut(&mut self) -> &mut VarStore {
        self.network.var_store_mut()
    }

     */

    fn switch_explore(&mut self, enabled: bool) {
        self.exploration = enabled
    }

    fn train_generic<R: Fn(&AgentStepView<S, <Self as Policy<S>>::InfoSetType>) -> Tensor>(&mut self, trajectories: &[AgentTrajectory<S, <Self as Policy<S>>::InfoSetType>], reward_f: R)
                                                                                             -> Result<Self::Summary, AmfiteatrRlError<S>> {
        if self.cyclic_replay_buffer.is_some(){
            Ok(self.a2c_train_on_trajectories_with_replay_buffer(trajectories, reward_f)?)
        } else {
            Ok(self.a2c_train_on_trajectories(trajectories, reward_f)?)
        }
    }


    fn set_gradient_tracing(&mut self, enabled: bool) {
        self.network.set_gradient_tracing(enabled)

    }


}


/// Policy PPO structure for discrete action space with support of masking.
pub struct PolicyMaskingDiscreteA2C<
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext> + MaskingInformationSetAction<S, ActionBuildContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding + TensorIndexI64Encoding,
>{
    /// It wraps around 'normal' PPO policy, but with more traits enforced.
    pub base: PolicyDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext>,

}

impl <
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext> + MaskingInformationSetAction<S, ActionBuildContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding + TensorIndexI64Encoding,
> PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext>{

    pub fn new(
        config: ConfigA2C,
        network: NeuralNetActorCritic,
        optimizer: Optimizer,
        info_set_conversion_context: InfoSetConversionContext,
        action_build_context: ActionBuildContext,
    ) -> Self{
        Self{
            base: PolicyDiscreteA2C::new(config, network, optimizer, info_set_conversion_context, action_build_context),
        }
    }

    pub fn initialize_cyclic_replay_buffer(&mut self, capacity: usize) -> Result<(), AmfiteatrError<S>>
    where <S as Scheme>::ActionType: ContextDecodeIndexI64<ActionBuildContext> + ContextEncodeIndexI64<ActionBuildContext>{
        self.base.initialize_cyclic_replay_buffer(capacity)
    }

}

impl <

    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext> + MaskingInformationSetAction<S, ActionBuildContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding + TensorIndexI64Encoding,
> MaybeContainsOne<CyclicReplayBufferActorCritic<S>> for PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext, >
{
    fn get(&self) -> Option<&CyclicReplayBufferActorCritic<S>> {
        self.base.get()
    }

    fn get_mut(&mut self) -> Option<&mut CyclicReplayBufferActorCritic<S>> {
        self.base.get_mut()
    }
}

impl <
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetConversionContext> + MaskingInformationSetAction<S, ActionBuildContext>,
    InfoSetConversionContext: TensorEncoding,
    ActionBuildContext: TensorDecoding + TensorIndexI64Encoding,
> TensorboardSupport<S> for PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetConversionContext, ActionBuildContext, >
{
    /// Creates [`tboard::EventWriter`]. Initially policy does not use `tensorboard` directory to store epoch
    /// training results (like entropy, policy loss, value loss). However, you cen provide it with directory
    /// to create tensorboard files.
    fn add_tboard_directory(&mut self, directory_path: &std::path::Path) -> Result<(), AmfiteatrError<S>> {
        self.base.add_tboard_directory(directory_path)
    }
    fn t_write_scalar(&mut self, step: i64, tag: &str, value: f32) -> Result<bool, AmfiteatrError<S>> {
        self.base.t_write_scalar(step, tag, value)
    }
}
impl <
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding> + MaskingInformationSetAction<S, ActionEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding,
> PolicyHelperA2C<S> for PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
    where <S as Scheme>::ActionType:
    ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type InfoSet = InfoSet;
    type InfoSetConversionContext = InfoSetEncoding;
    type ActionConversionContext = ActionEncoding;
    type NetworkOutput = TensorActorCritic;
    type Config = ConfigA2C;

    fn config(&self) -> &Self::Config {
        self.base.config()
    }

    fn optimizer_mut(&mut self) -> &mut Optimizer {
        self.base.optimizer_mut()
    }

    fn network(&self) -> &NeuralNet<Self::NetworkOutput> {
        self.base.network()
    }

    fn tboard_writer(&mut self) -> Option<&mut EventWriter<std::fs::File>> {
        self.base.tboard_writer()
    }

    fn global_learning_step(&self) -> i64 {
        self.base.global_learning_step()
    }

    fn set_global_learning_step(&mut self, step: i64) {
        self.base.set_global_learning_step(step)
    }

    fn info_set_encoding(&self) -> &Self::InfoSetConversionContext {
        self.base.info_set_encoding()
    }

    fn action_encoding(&self) -> &Self::ActionConversionContext {
        self.base.action_encoding()
    }

    fn dist(&self, info_set: &Self::InfoSet, network_output: &Self::NetworkOutput) -> Result<<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, AmfiteatrError<S>> {
        /*
        let softmax = network_output.actor.f_softmax(-1, tch::Kind::Float)
            .map_err(|e| TensorError::from_tch_with_context(e, "PPO distribution (softmax)".into()))?;
        */
        //println!("Network output: {}", &network_output.actor);
        let masks = info_set.try_build_mask(self.action_encoding())?.to_device(self.network().device());
        //println!("Masks: {}", masks);
        let masked_actor = network_output.actor.f_where_self(&masks, &Tensor::from(f32::MIN).to_device(self.network().device()))
            .map_err(|e| TensorError::from_tch_with_context(e, format!("A2C masking actor network output masks: {masks}, actor: {}", &network_output.actor)))?;
        //println!("Masked actor: {}", masked_actor);
        let softmax = masked_actor.f_softmax(-1, tch::Kind::Float)
            .map_err(|e| TensorError::from_tch_with_context(e, "A2C distribution (softmax)".into()))?;
        /*
        let product = softmax.f_where_self(&masks, &Tensor::from(0.0))
            .map_err(|e| TensorError::from_tch_with_context(e, "PPO distribution (softmax * mask)".into())
            )?;

        Ok(product)
         */

        #[cfg(feature = "log_debug")]
        {
            if softmax.nonzero().size()[0] == 0{
                log::warn!("Distribution of action is all zeros!, policy dist: {softmax}, masks: {masks}.");
            }
        }
        Ok(softmax)
    }

    fn is_action_masking_supported(&self) -> bool {
        true
    }

    fn generate_action_masks(&self, information_set: &Self::InfoSet) -> Result<<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, AmfiteatrError<S>> {
        information_set.try_build_mask(self.action_encoding())
    }

    fn is_exploration_on(&self) -> bool {
        self.base.is_exploration_on()
    }

    fn try_action_from_choice_tensor(&self, choice_tensor: &<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType) -> Result<S::ActionType, AmfiteatrError<S>> {
        self.base.try_action_from_choice_tensor(choice_tensor)
    }

    fn vectorize_action_and_create_category_mask(&self, action: &S::ActionType) -> Result<(<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, <Self::NetworkOutput as ActorCriticOutput>::ActionTensorType), AmfiteatrError<S>> {
        self.base.vectorize_action_and_create_category_mask(action)
    }

    fn batch_get_logprob_entropy_critic(&self, info_set_batch: &Tensor, action_param_batches: &<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType, action_category_mask_batches: Option<&<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType>, action_forward_mask_batches: Option<&<Self::NetworkOutput as ActorCriticOutput>::ActionTensorType>) -> Result<(Tensor, Tensor, Tensor), AmfiteatrError<S>> {
        self.base.batch_get_logprob_entropy_critic(info_set_batch, action_param_batches, action_category_mask_batches, action_forward_mask_batches)
    }
}

impl <
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding> + MaskingInformationSetAction<S, ActionEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding,
> Policy<S> for PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
    where <S as Scheme>::ActionType:
    ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type InfoSetType = InfoSet;

    fn select_action(&self, state: &Self::InfoSetType) -> Result<S::ActionType, AmfiteatrError<S>> {
        self.a2c_select_action(state)
    }
}

impl <
    
    S: Scheme,
    InfoSet: InformationSet<S> + Debug + ContextEncodeTensor<InfoSetEncoding> + MaskingInformationSetAction<S, ActionEncoding>,
    InfoSetEncoding: TensorEncoding,
    ActionEncoding: TensorDecoding + TensorIndexI64Encoding,
> LearningNetworkPolicyGeneric<S> for PolicyMaskingDiscreteA2C< S, InfoSet, InfoSetEncoding, ActionEncoding>
    where <S as Scheme>::ActionType:
    ContextDecodeIndexI64<ActionEncoding> + ContextEncodeIndexI64<ActionEncoding>{
    type Summary = LearnSummary;



    fn switch_explore(&mut self, enabled: bool) {
        self.base.switch_explore(enabled)
    }

    fn train_generic<R: Fn(&AgentStepView<S, <Self as Policy<S>>::InfoSetType>) -> Tensor>(&mut self, trajectories: &[AgentTrajectory<S, <Self as Policy<S>>::InfoSetType>], reward_f: R)
                                                                                             -> Result<Self::Summary, AmfiteatrRlError<S>> {
        if self.base.cyclic_replay_buffer.is_some(){
            Ok(self.a2c_train_on_trajectories_with_replay_buffer(trajectories, reward_f)?)
        } else {
            Ok(self.a2c_train_on_trajectories(trajectories, reward_f)?)
        }
    }

    fn set_gradient_tracing(&mut self, enabled: bool) {
        self.base.set_gradient_tracing(enabled)

    }
}