/**
 * Copyright (C) the original author or authors.
 *
 * 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 ninja.lifecycle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.spi.DefaultBindingScopingVisitor;
import com.google.inject.spi.ProviderInstanceBinding;

/**
 * Implementation of the lifecycle service
 */
public class LifecycleServiceImpl implements LifecycleService {
    private static final Logger log = LoggerFactory.getLogger(LifecycleServiceImpl.class);
    private final Injector injector;
    private final LifecycleRegister lifecycleRegister;
    private final LifecycleSupport lifecycleSupport;
    private volatile State state = State.STOPPED;
    private volatile long startTime;

    @Inject
    public LifecycleServiceImpl(Injector injector, LifecycleRegister lifecycleRegister, LifecycleSupport lifecycleSupport) {
        this.lifecycleRegister = lifecycleRegister;
        this.injector = injector;
        this.lifecycleSupport = lifecycleSupport;
    }

    @Override
    public void start() {
        startTime = System.currentTimeMillis();
        log.info("Starting Ninja application...");
        state = State.STARTING;
        // First, ensure that all singleton scoped bindings are instantiated, so that they can be started.  It is not
        // until they are instantiated that LifecycleSupport has an opportunity to register them.
        for (final Binding binding : injector.getBindings().values()) {
            binding.acceptScopingVisitor(new DefaultBindingScopingVisitor() {
                @Override
                public Object visitEagerSingleton() {
                    injector.getInstance(binding.getKey());
                    return null;
                }

                @Override
                public Object visitScope(Scope scope) {
                    if (scope.equals(Scopes.SINGLETON)) {
                        Object target = injector.getInstance(binding.getKey());
                        if (binding instanceof ProviderInstanceBinding) {
                            Provider providerInstance = ((ProviderInstanceBinding) binding).getProviderInstance();
                            if (providerInstance instanceof ProviderMethod) {
                                // @Provides methods don't get picked up by TypeListeners, so we need to manually register them
                                if (lifecycleSupport.hasLifecycleMethod(target.getClass())) {
                                    lifecycleSupport.registerLifecycle(target);
                                }
                            }
                        }
                    }
                    return null;
                }

            });
        }
        lifecycleRegister.start();
        long time = System.currentTimeMillis() - startTime;
        log.info("Ninja application started in {}ms", time);
        state = lifecycleRegister.isStarted() ? State.STARTED : State.STOPPED;
    }

    @Override
    public void stop() {
        long start = System.currentTimeMillis();
        log.info("Stopping Ninja application...");
        state = State.STOPPING;
        lifecycleRegister.stop();
        long time = System.currentTimeMillis() - start;
        log.info("Ninja application stopped in {}ms", time);
        state = lifecycleRegister.isStarted() ? State.STARTED : State.STOPPED;
    }

    @Override
    public boolean isStarted() {
        return lifecycleRegister.isStarted();
    }

    @Override
    public State getState() {
        return state;
    }

    public long getUpTime() {
        if (isStarted()) {
            return System.currentTimeMillis() - startTime;
        } else {
            return 0;
        }
    }
}
