INFO:__main__:Dummy data generated successfully.
INFO:__main__:Initializing attack: DeepFool
ERROR:__main__:Type error while initializing 'DeepFool': DeepFool requires an estimator derived from <class 'art.estimators.estimator.BaseEstimator'> and <class 'art.estimators.classification.classifier.ClassGradientsMixin'>, the provided classifier is an instance of <class 'NoneType'> and is derived from (<class 'object'>,).. Check parameter compatibility.
ERROR:__main__:Failed to apply adversarial attack: DeepFool requires an estimator derived from <class 'art.estimators.estimator.BaseEstimator'> and <class 'art.estimators.classification.classifier.ClassGradientsMixin'>, the provided classifier is an instance of <class 'NoneType'> and is derived from (<class 'object'>,).
INFO:__main__:All adversarial attack tests completed.

# Mapping from attack class names to actual classes
attack_class_mapping: Dict[str, AttackType] = {
    'AdversarialPatch': AdversarialPatch,
    'AdversarialPatchNumpy': AdversarialPatchNumpy,
    'AdversarialPatchTensorFlowV2': AdversarialPatchTensorFlowV2,
    'AdversarialPatchPyTorch': AdversarialPatchPyTorch,
    'AdversarialTexturePyTorch': AdversarialTexturePyTorch,
    'CarliniWagnerASR': CarliniWagnerASR,
    'AutoAttack': AutoAttack,
    'AutoProjectedGradientDescent': AutoProjectedGradientDescent,
    'AutoConjugateGradient': AutoConjugateGradient,
    'BrendelBethgeAttack': BrendelBethgeAttack,
    'BoundaryAttack': BoundaryAttack,
    'CompositeAdversarialAttackPyTorch': CompositeAdversarialAttackPyTorch,
    'CarliniL2Method': CarliniL2Method,
    'CarliniLInfMethod': CarliniLInfMethod,
    'CarliniL0Method': CarliniL0Method,
    'DecisionTreeAttack': DecisionTreeAttack,
    'DeepFool': DeepFool,
    'DPatch': DPatch,
    'RobustDPatch': RobustDPatch,
    'ElasticNet': ElasticNet,
    'FastGradientMethod': FastGradientMethod,
    'FrameSaliencyAttack': FrameSaliencyAttack,
    'FeatureAdversariesNumpy': FeatureAdversariesNumpy,
    'FeatureAdversariesPyTorch': FeatureAdversariesPyTorch,
    'FeatureAdversariesTensorFlowV2': FeatureAdversariesTensorFlowV2,
    'GeoDA': GeoDA,
    'GRAPHITEBlackbox': GRAPHITEBlackbox,
    'GRAPHITEWhiteboxPyTorch': GRAPHITEWhiteboxPyTorch,
    'HighConfidenceLowUncertainty': HighConfidenceLowUncertainty,
    'HopSkipJump': HopSkipJump,
    'ImperceptibleASR': ImperceptibleASR,
    'ImperceptibleASRPyTorch': ImperceptibleASRPyTorch,
    'BasicIterativeMethod': BasicIterativeMethod,
    'LaserAttack': LaserAttack,
    'LowProFool': LowProFool,
    'MomentumIterativeMethod': MomentumIterativeMethod,
    'NewtonFool': NewtonFool,
    'OverloadPyTorch': OverloadPyTorch,
    'MalwareGDTensorFlow': MalwareGDTensorFlow,
    'PixelAttack': PixelAttack,
    'ProjectedGradientDescent': ProjectedGradientDescent,
    'ProjectedGradientDescentNumpy': ProjectedGradientDescentNumpy,
    'ProjectedGradientDescentPyTorch': ProjectedGradientDescentPyTorch,
    'ProjectedGradientDescentTensorFlowV2': ProjectedGradientDescentTensorFlowV2,
    'OverTheAirFlickeringPyTorch': OverTheAirFlickeringPyTorch,
    'SaliencyMapMethod': SaliencyMapMethod,
    'ShadowAttack': ShadowAttack,
    'ShapeShifter': ShapeShifter,
    'SimBA': SimBA,
    'SpatialTransformation': SpatialTransformation,
    'SquareAttack': SquareAttack,
    'ThresholdAttack': ThresholdAttack,
    'UniversalPerturbation': UniversalPerturbation,
    'TargetedUniversalPerturbation': TargetedUniversalPerturbation,
    'VirtualAdversarialMethod': VirtualAdversarialMethod,
    'Wasserstein': Wasserstein,
    'ZooAttack': ZooAttack,
    'SignOPTAttack': SignOPTAttack,
}



def get_attack_parameters(attack_name: str) -> Dict[str, Any]:
    """
    Retrieve the parameters for the specified attack.

    Args:
        attack_name (str): The name of the attack.

    Returns:
        Dict[str, Any]: A dictionary of parameters for the attack.
    """
    params = parameters.get(attack_name, {}).copy()
    if not params:
        logger.warning(f"No parameters found for attack '{attack_name}'. Using empty defaults.")
    return params


def get_attack_class(attack_name: str) -> AttackType:
    """
    Retrieve the attack class based on the attack name.

    Args:
        attack_name (str): The name of the attack.

    Returns:
        AttackType: The corresponding attack class.

    Raises:
        ValueError: If the attack name is not recognized.
    """
    attack_class = attack_class_mapping.get(attack_name)
    if not attack_class:
        raise ValueError(f"Attack '{attack_name}' is not recognized or not supported.")
    return attack_class





def update_attack_parameters(
    existing_params: Dict[str, Any], new_params: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Update existing attack parameters with new parameters provided by the user.
    Ensures that each parameter is applied only once and handles changes.

    Args:
        existing_params (Dict[str, Any]): The current attack parameters.
        new_params (Dict[str, Any]): The new parameters to apply.

    Returns:
        Dict[str, Any]: The updated attack parameters.

    Raises:
        ValueError: If a parameter in new_params is invalid or conflicts occur.
    """
    updated_params = existing_params.copy()
    for key, value in new_params.items():
        if key in updated_params:
            logger.info(
                f"Updating parameter '{key}': {updated_params[key]} -> {value}."
            )
        else:
            logger.info(f"Adding new parameter '{key}': {value}.")
        updated_params[key] = value
    return updated_params


def apply_attack(
    attack_name: str,
    classifier: Any,
    x: np.ndarray,
    y: Optional[np.ndarray] = None,
    new_params: Optional[Dict[str, Any]] = None,
) -> np.ndarray:
    """
    Apply a single adversarial attack with the ability to update attack parameters.

    Args:
        attack_name (str): The name of the attack to apply.
        classifier (Any): The classifier to attack.
        x (np.ndarray): Input data.
        y (Optional[np.ndarray]): True labels.
        new_params (Optional[Dict[str, Any]]): New parameters to update the attack.

    Returns:
        np.ndarray: Adversarial examples generated by the attack.

    Raises:
        ValueError: If the attack is not implemented for the classifier or parameters are invalid.
        TypeError: If there's a type mismatch during attack initialization.
        NotImplementedError: If the attack is not supported.
        Exception: For any other errors during the attack process.
    """
    logger.info(f"Initializing attack: {attack_name}")
    try:
        attack_class = get_attack_class(attack_name)
        if attack_class is None:
            raise NotImplementedError(
                f"The attack '{attack_name}' is not supported."
            )

        params = get_attack_parameters(attack_name)

        if new_params:
            params = update_attack_parameters(params, new_params)
        if "estimator" in list(str(params.keys())):
            params['estimator']=classifier
        elif 'classifier' in list(str(params.keys())):   
            params['classifier']=classifier
        
        # Handle special parameters if necessary
        # For specific attacks, modify parameters accordingly
        if attack_name == "AutoAttack":
            params["estimator_orig"] = classifier
        elif attack_name == "AdversarialPatchPyTorch":
            # Ensure 'estimator' is correctly set
            params["estimator"] = classifier

        # Initialize the attack with the classifier and parameters
        # Pass 'estimator' as a keyword argument to avoid conflicts
        attack = attack_class(**params)

        # Generate adversarial examples
        adversarial_examples = attack.generate(x=x, y=y)

        logger.info(f"Attack '{attack_name}' performed successfully.")
        return adversarial_examples

    except ValueError as ve:
        logger.error(f"Value error: {ve}")
        raise
    except TypeError as te:
        logger.error(
            f"Type error while initializing '{attack_name}': {te}. Check parameter compatibility."
        )
        raise
    except NotImplementedError as nie:
        logger.warning(str(nie))
        raise
    except Exception as e:
        logger.error(f"Unexpected error during '{attack_name}': {e}")
        raise

# attacktest1.py

import logging
import numpy as np
from attackgenerator import  initialize_model, initialize_classifier, generate_dummy_data

# Configure logging (optional if already configured in attackgenerator.py)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def main() -> None:
    """Main function to execute the end-to-end attack tests."""
    logger.info("Starting end-to-end adversarial attack tests.")

    # Initialize model
    num_classes = 1000
    model = initialize_model(num_classes=num_classes)
    logger.info("Model initialized successfully.")

    # Initialize classifier
    input_shape = (3, 32, 32)
    clip_values = (0.0, 1.0)
    classifier = initialize_classifier(
        model=model,
        input_shape=input_shape,
        nb_classes=num_classes,
        clip_values=clip_values,
    )
    logger.info("Classifier initialized successfully.")

    # Generate dummy data
    inputs, labels = generate_dummy_data(batch_size=32)
    logger.info("Dummy data generated successfully.")

    # Specify the attack name
    selected_attack = 'DeepFool'  # Change as needed

    try:
        adversarial_x = apply_attack(
            attack_name=selected_attack,
            classifier=classifier,
            x=inputs,
            y=labels
        )
        print(adversarial_x.shape)
        logger.info("Adversarial attack applied successfully.")
    except Exception as e:
        logger.error(f"Failed to apply adversarial attack: {e}")

    logger.info("All adversarial attack tests completed.")


if __name__ == "__main__":
    main()