// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.provider;

import com.google.inject.Inject;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.log.LogLevel;
import com.yahoo.net.HostName;

import com.yahoo.system.ProcessExecuter;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdmin;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperationsImpl;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepositoryImpl;
import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorImpl;
import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
import com.yahoo.vespa.hosted.node.admin.util.Environment;

import java.time.Clock;
import java.time.Duration;
import java.util.function.Function;
import java.util.logging.Logger;

import static com.yahoo.vespa.defaults.Defaults.getDefaults;

/**
 * Set up node admin for production.
 *
 * @author dybis
 */
public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
    private static final int WEB_SERVICE_PORT = getDefaults().vespaWebServicePort();
    private static final Duration NODE_AGENT_SCAN_INTERVAL = Duration.ofSeconds(30);
    private static final Duration NODE_ADMIN_CONVERGE_STATE_INTERVAL = Duration.ofSeconds(30);

    private final Logger log = Logger.getLogger(NodeAdminProvider.class.getName());
    private final NodeAdminStateUpdater nodeAdminStateUpdater;

    @Inject
    public NodeAdminProvider(Docker docker, MetricReceiverWrapper metricReceiver) {
        log.log(LogLevel.INFO, objectToString() + ": Creating object");
        try {
            Clock clock = Clock.systemUTC();
            String dockerHostHostName = HostName.getLocalhost();
            ProcessExecuter processExecuter = new ProcessExecuter();
            Environment environment = new Environment();

            ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(environment.getConfigServerHosts());
            NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor, WEB_SERVICE_PORT);
            Orchestrator orchestrator = new OrchestratorImpl(requestExecutor, WEB_SERVICE_PORT);
            DockerOperations dockerOperations = new DockerOperationsImpl(docker, environment, processExecuter);

            StorageMaintainer storageMaintainer = new StorageMaintainer(docker, processExecuter, metricReceiver, environment, clock);
            AclMaintainer aclMaintainer = new AclMaintainer(dockerOperations, nodeRepository, dockerHostHostName);

            Function<String, NodeAgent> nodeAgentFactory =
                    (hostName) -> new NodeAgentImpl(hostName, nodeRepository, orchestrator, dockerOperations,
                            storageMaintainer, aclMaintainer, environment, clock, NODE_AGENT_SCAN_INTERVAL);
            NodeAdmin nodeAdmin = new NodeAdminImpl(dockerOperations, nodeAgentFactory, storageMaintainer, aclMaintainer,
                    metricReceiver, clock);

            nodeAdminStateUpdater = new NodeAdminStateUpdater(nodeRepository, orchestrator, storageMaintainer, nodeAdmin,
                    dockerHostHostName, clock, NODE_ADMIN_CONVERGE_STATE_INTERVAL);
            nodeAdminStateUpdater.start();
        } catch (Exception e) {
            throw e;
        }
    }

    @Override
    public NodeAdminStateUpdater get() {
        return nodeAdminStateUpdater;
    }

    @Override
    public void deconstruct() {
        log.log(LogLevel.INFO, objectToString() + ": Stop called");

        nodeAdminStateUpdater.stop();
        log.log(LogLevel.INFO, objectToString() + ": Stop complete");
    }

    private String objectToString() {
        return this.getClass().getSimpleName() + "@" + Integer.toString(System.identityHashCode(this));
    }
}

@@ -198,16 +198,97 @@
 Inject;%0A
+import com.yahoo.concurrent.lock.Lock;%0Aimport com.yahoo.concurrent.lock.Locking;%0A
 import c
@@ -2330,16 +2330,50 @@
 Updater;
+%0A    private final Lock classLock;
 %0A%0A    @I
@@ -2458,16 +2458,33 @@
 Receiver
+, Locking locking
 ) %7B%0A    
@@ -2551,25 +2551,170 @@
 ject
-%22);%0A        try %7B
+, acquiring lock...%22);%0A        classLock = locking.lock(this.getClass());%0A        try %7B%0A            log.log(LogLevel.INFO, objectToString() + %22: Lock acquired%22);%0A
 %0A   
@@ -4370,24 +4370,55 @@
 eption e) %7B%0A
+            classLock.close();%0A
             
@@ -4765,16 +4765,114 @@
 plete%22);
+%0A%0A        classLock.close();%0A        log.log(LogLevel.INFO, objectToString() + %22: Lock released%22);
 %0A    %7D%0A%0A

Revert "Remove node-admin locks"