/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package net.gcolin.di.cdi;

import net.gcolin.di.cdi.internal.BeanDiscover;
import net.gcolin.di.cdi.internal.BeanManagerImpl;
import net.gcolin.di.cdi.internal.Compatibility;
import net.gcolin.di.cdi.internal.Events;
import net.gcolin.di.cdi.internal.event.AfterBeanDiscoveryImpl;
import net.gcolin.di.cdi.internal.event.AfterDeploymentValidationImpl;
import net.gcolin.di.cdi.internal.event.AfterTypeDiscoveryImpl;
import net.gcolin.di.cdi.internal.event.BeforeBeanDiscoveryImpl;
import net.gcolin.di.cdi.internal.event.ProcessAnnotatedTypeImpl;
import net.gcolin.di.cdi.internal.event.ProcessSyntheticAnnotatedTypeImpl;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import javax.enterprise.event.Event;
import javax.enterprise.inject.spi.AfterTypeDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.ObserverMethod;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessSyntheticAnnotatedType;
import javax.servlet.Filter;
import javax.servlet.Servlet;

/**
 * The CDI deployment steps runner.
 * 
 * @author Gaël COLIN
 * @since 1.0
 */
public abstract class CdiDeployment {

  protected BeanDiscover bp;
  protected BeanManagerImpl bm;

  public CdiDeployment(BeanManagerImpl bm) {
    this.bm = bm;
  }

  protected abstract void scan(ClassLoader cl);

  protected List<Class<?>> getClasses() {
    return bp.getAll();
  }

  protected List<Class<?>> getDecorators() {
    return bp.getDecorators();
  }

  protected List<Class<?>> getInterceptors() {
    return bp.getInterceptors();
  }

  protected List<Class<?>> getAlternatives() {
    return bp.getAlternatives();
  }

  protected List<Class<? extends Annotation>> getAlternativesStereotype() {
    return bp.getAlternativesStereotype();
  }

  protected List<Class<? extends Annotation>> getDecoratorsStereotype() {
    return bp.getDecoratorsStereotype();
  }

  protected List<Class<? extends Annotation>> getInterceptorsStereotype() {
    return bp.getInterceptorsStereotype();
  }

  /**
   * Deploy CDI.
   * 
   * @param cl the class loader to look into
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  public void deploy(ClassLoader cl) {
    List<AnnotatedType<?>> types = new ArrayList<>();

    BeforeBeanDiscoveryImpl before = new BeforeBeanDiscoveryImpl(bm);

    Event<ProcessSyntheticAnnotatedType<?>> processSynthetic =
        (Event) bm.getEvents().select(ProcessSyntheticAnnotatedType.class);
    Event<ProcessAnnotatedType<?>> process =
        (Event) bm.getEvents().select(ProcessAnnotatedType.class);

    Consumer<ObserverMethod<?>> processCallback = obs -> {
      for (AnnotatedType<?> type : before.getOtherTypes()) {
        ProcessSyntheticAnnotatedTypeImpl<?> event = new ProcessSyntheticAnnotatedTypeImpl<>(type,
            bm.getExtension((Class) obs.getBeanClass()));
        processSynthetic.fire(event);
        if (!event.isVeto()) {
          types.add(event.getAnnotatedType());
        }
      }
    };

    ((Events<BeforeBeanDiscovery>) bm.getEvents().select(BeforeBeanDiscovery.class)).fire(before,
        (Consumer) processCallback);

    scan(cl);
    for (Class<?> clazz : getClasses()) {
      if ((Compatibility.getXmlRootElement() != null
          && clazz.isAnnotationPresent(Compatibility.getXmlRootElement()))
          || (Compatibility.getXmlAccessorType() != null
              && clazz.isAnnotationPresent(Compatibility.getXmlAccessorType()))
          || Servlet.class.isAssignableFrom(clazz) || Filter.class.isAssignableFrom(clazz)) {
        continue;
      }
      AnnotatedType<?> type = bm.createAnnotatedType(clazz);
      ProcessAnnotatedTypeImpl<?> event = new ProcessAnnotatedTypeImpl<>(type);
      process.fire(event);
      if (!event.isVeto()) {
        types.add(event.getAnnotatedType());
      }
    }

    AfterTypeDiscoveryImpl afterType =
        new AfterTypeDiscoveryImpl(getAlternatives(), getDecorators(), getInterceptors());

    processCallback = obs -> {
      for (AnnotatedType<?> type : afterType.getOtherTypes()) {
        ProcessSyntheticAnnotatedTypeImpl<?> event = new ProcessSyntheticAnnotatedTypeImpl<>(type,
            bm.getExtension((Class) obs.getBeanClass()));
        processSynthetic.fire(event);
        if (!event.isVeto()) {
          types.add(event.getAnnotatedType());
        }
      }
    };

    ((Events<AfterTypeDiscovery>) bm.getEvents().select(AfterTypeDiscovery.class)).fire(afterType,
        (Consumer) processCallback);

    getAlternatives().forEach(x -> bm.addAlternative(x));
    getDecorators().forEach(x -> bm.addDecorator(x));
    getInterceptors().forEach(x -> bm.addInterceptor(x));
    getDecoratorsStereotype().forEach(x -> bm.addDecoratorStereotype(x));
    getAlternativesStereotype().forEach(x -> bm.addAlternativeStereotype(x));
    getInterceptorsStereotype().forEach(x -> bm.addInterceptorStereotype(x));
    types.forEach(x -> bm.addAnnotatedType((AnnotatedType<Object>) x, 0));
    bm.flush();

    bm.fireDefinitionEvent(new AfterBeanDiscoveryImpl(bm, types));
    bm.fireDefinitionEvent(new AfterDeploymentValidationImpl());
  }

}
