/**
 * Copyright 2011-2015 John Ericksen
 *
 * Licensed 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.androidtransfuse.bootstrap;

import org.androidtransfuse.scope.Scope;
import org.androidtransfuse.scope.ScopeKey;
import org.androidtransfuse.scope.Scopes;
import org.androidtransfuse.util.GeneratedCodeRepository;
import org.androidtransfuse.util.Namer;
import org.androidtransfuse.util.Providers;
import org.androidtransfuse.util.TransfuseRuntimeException;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

/**
 * @author John Ericksen
 */
public final class Bootstraps {

    public static final String BOOTSTRAPS_INJECTOR_PACKAGE = "org.androidtransfuse.bootstrap";
    public static final String BOOTSTRAPS_INJECTOR_NAME = Namer.name("Bootstraps").append("Factory").build();
    public static final String BOOTSTRAPS_INJECTOR_METHOD = "inject";
    public static final String BOOTSTRAPS_INJECTOR_GET = "get";
    public static final String IMPL_EXT = "Bootstrap";

    private Bootstraps(){
        //private utility constructor
    }

    @SuppressWarnings("unchecked")
    public static <T> void inject(T input){
        Class<T> clazz = (Class<T>) input.getClass();
        getInjector(clazz).inject(input);
    }

    @SuppressWarnings("unchecked")
    public static <T> BootstrapInjector<T> getInjector(Class<T> clazz){
        String injectorClassName = Namer.name(clazz.getName()).append(IMPL_EXT).build();
        try {
            Class<T> bootstrapClass = (Class<T>) Class.forName(injectorClassName);
            return (BootstrapInjector<T>) bootstrapClass.newInstance();
        } catch (InstantiationException e) {
            throwUnableToFindBootstrapClass(injectorClassName, clazz);
        } catch (IllegalAccessException e) {
            throwUnableToFindBootstrapClass(injectorClassName, clazz);
        } catch (ClassNotFoundException e) {
            throwUnableToFindBootstrapClass(injectorClassName, clazz);
        }
        return null;
    }

    public static <T> void throwUnableToFindBootstrapClass(String injectorClassName, Class<T> clazz) {
        throw new TransfuseRuntimeException("Unable to find generated @Bootstrap class for " + clazz +
                ", verify that your class is configured properly and that the @Bootstrap generated class " +
                injectorClassName +
                " is generated by Transfuse.");
    }

    public interface BootstrapInjector<T>{

        void inject(T input);

        void inject(Scopes scopes, T input);

        <S> BootstrapInjector<T> add(Class<? extends Annotation> scope, ScopeKey<S> bindType, S instance);
    }

    public abstract static class BootstrapsInjectorAdapter<T> implements BootstrapInjector<T>{

        public static final String SCOPE_SINGLETONS_METHOD = "scopeSingletons";

        private final Map<Class<? extends Annotation> , Map<ScopeKey, Object>> scoped = new HashMap<Class<? extends Annotation> , Map<ScopeKey, Object>>();

        public <S> BootstrapInjector<T> add(Class<? extends Annotation> scope, ScopeKey<S> bindType, S instance){
            if(!scoped.containsKey(scope)){
                scoped.put(scope, new HashMap<ScopeKey, Object>());
            }
            scoped.get(scope).put(bindType, instance);
            return this;
        }

        @SuppressWarnings("unchecked")
        protected void scopeSingletons(Scopes scopes){
            for (Map.Entry<Class<? extends Annotation>, Map<ScopeKey, Object>> scopedEntry : scoped.entrySet()) {
                Scope scope = scopes.getScope(scopedEntry.getKey());

                if(scope != null){
                    for (Map.Entry<ScopeKey, Object> scopingEntry : scopedEntry.getValue().entrySet()) {
                        scope.getScopedObject(scopingEntry.getKey(), Providers.of(scopingEntry.getValue()));
                    }
                }
            }
        }
    }
}
