/*
 * 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 org.apache.meecrowave.cdi;

import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.logging.jul.Log4j2Logger;
import org.apache.meecrowave.logging.openwebbeans.Log4j2LoggerFactory;
import org.apache.meecrowave.logging.tomcat.Log4j2Log;
import org.apache.meecrowave.openwebbeans.KnownClassesFilter;
import org.apache.meecrowave.openwebbeans.OWBTomcatWebScannerService;
import org.apache.openwebbeans.se.OWBContainer;
import org.apache.openwebbeans.se.OWBInitializer;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.spi.ScannerService;
import org.apache.xbean.finder.filter.Filter;

import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toSet;

public class MeecrowaveSeContainerInitializer extends OWBInitializer {
    static { // todo: see if we can not do it statically but also means we lazy load OWB which can require some OWB rework
        System.setProperty("java.util.logging.manager",
                System.getProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"));
        System.setProperty("openwebbeans.logging.factory",
                System.getProperty("openwebbeans.logging.factory", Log4j2LoggerFactory.class.getName()));
        System.setProperty("org.apache.cxf.Logger",
                System.getProperty("org.apache.cxf.Logger", Log4j2Logger.class.getName()));
        System.setProperty("org.apache.tomcat.Logger",
                System.getProperty("org.apache.tomcat.Logger", Log4j2Log.class.getName()));
    }

    private Meecrowave.Builder builder = new Meecrowave.Builder();

    @Override
    public SeContainerInitializer addProperty(final String s, final Object o) {
        if (Meecrowave.Builder.class.isInstance(o)) {
            builder = Meecrowave.Builder.class.cast(o);
            return this;
        }

        final String setter = "set" + Character.toUpperCase(s.charAt(0)) + s.substring(1);
        final Class<? extends Meecrowave.Builder> builderClass = builder.getClass();
        final Optional<Method> setterOpt = Stream.of(builderClass.getMethods())
                .filter(m -> m.getName().equals(setter) && m.getParameterCount() == 1)
                .findFirst();
        if (!setterOpt.isPresent()) {
            super.addProperty(s, o);
            // todo: log or do we assume delegate will ?
            return this;
        }

        try {
            builderClass.getMethod(setter, o.getClass()).invoke(builder, o);
        } catch (final NoSuchMethodException nsme) {
            if (Integer.class.isInstance(o)) {
                try {
                    builderClass.getMethod(setter, int.class).invoke(builder, o);
                } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                    throw new IllegalArgumentException(nsme);
                }
            } else if (Long.class.isInstance(o)) {
                try {
                    builderClass.getMethod(setter, long.class).invoke(builder, o);
                } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                    throw new IllegalArgumentException(nsme);
                }
            } else if (Boolean.class.isInstance(o)) {
                try {
                    builderClass.getMethod(setter, boolean.class).invoke(builder, o);
                } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                    throw new IllegalArgumentException(nsme);
                }
            } else {
                throw new IllegalArgumentException(nsme);
            }
        } catch (final IllegalAccessException | InvocationTargetException ex) {
            throw new IllegalArgumentException(ex);
        }
        return this;
    }

    @Override
    protected void addCustomServices(final Map<String, Object> services) {
        final Set<String> forced = this.scannerService.configuredClasses().stream().map(Class::getName).collect(toSet());
        services.put(Filter.class.getName(), new KnownClassesFilter() { // override it to make programmatic configuration working OOTB
            @Override
            public boolean accept(final String name) {
                return forced.contains(name) || super.accept(name);
            }
        });
    }

    @Override
    protected SeContainer newContainer(final WebBeansContext context) {
        final Meecrowave meecrowave = new Meecrowave(builder);
        return new OWBContainer(context, meecrowave) {
            {
                meecrowave.bake();
            }

            @Override
            protected void doClose() {
                meecrowave.close();
            }
        };
    }

    @Override
    protected ScannerService getScannerService() {
        return new OWBTomcatWebScannerService(scannerService);
    }
}
