/*
 * 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.brooklyn.qa.load;

import static java.lang.String.format;

import java.net.URI;
import java.util.Collection;
import java.util.concurrent.Callable;

import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.entity.proxy.nginx.NginxControllerImpl;
import org.apache.brooklyn.entity.proxy.nginx.NginxSshDriver;
import org.apache.brooklyn.entity.proxy.nginx.UrlMapping;
import org.apache.brooklyn.feed.function.FunctionFeed;
import org.apache.brooklyn.feed.function.FunctionPollConfig;
import org.apache.brooklyn.feed.http.HttpFeed;
import org.apache.brooklyn.feed.http.HttpPollConfig;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.net.Networking;

import com.google.common.base.Functions;

/**
 * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
 */
public class SimulatedNginxControllerImpl extends NginxControllerImpl {

    public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
    
    private HttpFeed httpFeed;
    private FunctionFeed functionFeed;
    
    @Override
    public Class<?> getDriverInterface() {
        return SimulatedNginxSshDriver.class;
    }

    @Override
    public void connectSensors() {
        boolean simulateEntity = getConfig(SIMULATE_ENTITY);
        boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);

        if (!simulateEntity && !simulateExternalMonitoring) {
            super.connectSensors();
            return;
        }

        // From AbstractController.connectSensors
        if (getUrl()==null) {
            sensors().set(MAIN_URI, URI.create(inferUrl()));
            sensors().set(ROOT_URL, inferUrl());
        }
        addServerPoolMemberTrackingPolicy();

        // From NginxController.connectSensors
        ConfigToAttributes.apply(this);

        if (!simulateExternalMonitoring) {
            // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
            String uriToPoll = (simulateEntity) ? "http://localhost:8081" : getAttribute(MAIN_URI).toString();
            
            httpFeed = HttpFeed.builder()
                    .entity(this)
                    .period(getConfig(HTTP_POLL_PERIOD))
                    .baseUri(uriToPoll)
                    .poll(new HttpPollConfig<Boolean>(SERVICE_UP)
                            .onSuccess(Functions.constant(true))
                            .onFailureOrException(Functions.constant(true)))
                    .build();
        }
        
        functionFeed = FunctionFeed.builder()
                .entity(this)
                .period(getConfig(HTTP_POLL_PERIOD))
                .poll(new FunctionPollConfig<Boolean,Boolean>(SERVICE_UP)
                        .callable(new Callable<Boolean>() {
                            @Override
                            public Boolean call() {
                                return true;
                            }}))
                .build();

        // Can guarantee that parent/managementContext has been set
        Group urlMappings = getConfig(URL_MAPPINGS);
        if (urlMappings != null) {
            // Listen to the targets of each url-mapping changing
            subscriptions().subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() {
                    @Override public void onEvent(SensorEvent<Collection<String>> event) {
                        updateNeeded();
                    }
                });

            // Listen to url-mappings being added and removed
            urlMappingsMemberTrackerPolicy = policies().add(PolicySpec.create(UrlMappingsMemberTrackerPolicy.class)
                    .configure("group", urlMappings));
        }
    }

    @Override
    protected void disconnectSensors() {
        super.disconnectSensors();
        if (httpFeed != null) httpFeed.stop();
        if (functionFeed != null) functionFeed.stop();
    }

    public static class SimulatedNginxSshDriver extends NginxSshDriver {
        public SimulatedNginxSshDriver(SimulatedNginxControllerImpl entity, SshMachineLocation machine) {
            super(entity, machine);
        }

        @Override
        public void install() {
            if (entity.getConfig(SKIP_SSH_ON_START)) {
                // no-op
            } else {
                super.install();
            }
        }
        
        @Override
        public void customize() {
            if (entity.getConfig(SKIP_SSH_ON_START)) {
                // no-op
            } else {
                super.customize();
            }
        }
        
        @Override
        public void launch() {
            if (!entity.getConfig(SIMULATE_ENTITY)) {
                super.launch();
                return;
            }
            
            Networking.checkPortsValid(MutableMap.of("httpPort", getPort()));

            if (entity.getConfig(SKIP_SSH_ON_START)) {
                // minimal ssh, so that isRunning will subsequently work
                newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING)
                        .body.append(
                                format("mkdir -p %s/logs", getRunDir()),
                                format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()))
                        .execute();
            } else {
                newScript(MutableMap.of("usePidFile", false), LAUNCHING)
                        .body.append(
                                format("cd %s", getRunDir()),
                                "echo skipping exec of requireExecutable ./sbin/nginx",
                                sudoBashCIfPrivilegedPort(getPort(), format(
                                        "echo skipping exec of nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())),
                                format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()),
                                format("echo $! > "+getPidFile()),
                                format("for i in {1..10}\n" +
                                        "do\n" +
                                        "    test -f %1$s && ps -p `cat %1$s` && exit\n" +
                                        "    sleep 1\n" +
                                        "done\n" +
                                        "echo \"No explicit error launching nginx but couldn't find process by pid; continuing but may subsequently fail\"\n" +
                                        "cat %2$s | tee /dev/stderr",
                                        getPidFile(), getLogFileLocation()))
                        .execute();
            }
        }

        // Use pid file, because just simulating the run of nginx
        @Override
        public void stop() {
            newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING).execute();
        }
    }
}
