package alien4cloud.tosca.context;

import java.lang.reflect.Method;
import java.util.Set;

import org.alien4cloud.tosca.model.CSARDependency;
import org.alien4cloud.tosca.model.templates.Topology;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import com.google.common.collect.Sets;

import lombok.extern.slf4j.Slf4j;

/**
 * This aspect executes for ToscaContextual methods and ensure that a ToscaContext is defined (or creates one if not).
 */
@Slf4j
@Aspect
@Component
public class ToscaContextualAspect {

    @Around("@annotation(alien4cloud.tosca.context.ToscaContextual)")
    public Object ensureContext(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method m = ms.getMethod();
        boolean requireNew = m.getAnnotation(ToscaContextual.class).requiresNew();

        boolean initContext = false;
        ToscaContext.Context existingContext = ToscaContext.get();
        if (requireNew || existingContext == null) {
            initContext = true;
        }
        try {
            // try to find dependencies from parameters
            if (initContext) {
                Set<CSARDependency> dependencies = findDependencies(joinPoint.getArgs());
                log.debug("Initializing Tosca Context with dependencies {}", dependencies);
                ToscaContext.init(dependencies);
            }
            return joinPoint.proceed();
        } finally {
            if (initContext) {
                log.debug("Destroying Tosca Context");
                ToscaContext.destroy();
            }
            if (existingContext != null) {
                log.debug("Set back the existing context");
                ToscaContext.set(existingContext);
            }
        }
    }

    private Set<CSARDependency> findDependencies(Object[] args) {
        for (Object arg : args) {
            if (arg instanceof Topology) {
                return ((Topology) arg).getDependencies();
            }
            if (arg instanceof Set) {
                Set set = (Set) arg;
                if (set.size() > 0 && set.iterator().next() instanceof CSARDependency) {
                    return (Set<CSARDependency>) arg;
                }
            }
        }
        return Sets.<CSARDependency> newHashSet();
    }
}
