/*
 * Copyright (c) 2015 Spotify AB.
 *
 * 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 com.spotify.heroic;

import com.spotify.heroic.HeroicCore.Builder;
import com.spotify.heroic.args4j.CmdLine;
import com.spotify.heroic.reflection.ResourceException;
import com.spotify.heroic.reflection.ResourceFileLoader;
import com.spotify.heroic.reflection.ResourceInstance;
import eu.toolchain.async.Transform;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class HeroicService {
    public static interface Configuration {
        void configure(HeroicCore.Builder builder, Parameters parameters);
    }

    public static final String CONFIGURATION_RESOURCE =
        "com.spotify.heroic.HeroicService.Configuration";

    /**
     * Default configuration path.
     */
    public static final Path DEFAULT_CONFIG = Paths.get("heroic.yml");

    public static void main(final String[] args) throws Exception {
        main(args, null);
    }

    public static void main(final String[] args, final Configuration configuration)
        throws Exception {
        HeroicLogging.configure();

        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                try {
                    log.error("Uncaught exception in thread {}, exiting...", t, e);
                } finally {
                    System.exit(1);
                }
            }
        });

        final Parameters params = parseArguments(args);

        if (params == null) {
            System.exit(0);
            return;
        }

        final HeroicCore.Builder builder = HeroicCore.builder();

        configureBuilder(builder, params);

        try {
            loadResourceConfigurations(builder, params);
        } catch (final ResourceException e) {
            log.error("Failed to load configurators from resource: {}", e.getMessage(), e);
            throw e;
        }

        final HeroicCore core = builder.build();

        final HeroicCoreInstance instance;

        try {
            instance = core.newInstance();
        } catch (final Exception e) {
            log.error("Failed to create Heroic instance", e);
            System.exit(1);
            return;
        }

        try {
            instance.start().get();
        } catch (final Exception e) {
            log.error("Failed to start Heroic instance", e);
            System.exit(1);
            return;
        }

        final Thread shutdown = new Thread(instance::shutdown);
        shutdown.setName("heroic-shutdown-hook");

        /* setup shutdown hook */
        Runtime.getRuntime().addShutdownHook(shutdown);

        /* block until core stops */
        instance.join().get();

        log.info("Shutting down, bye bye!");
        System.exit(0);
    }

    private static void loadResourceConfigurations(final Builder builder, final Parameters params)
        throws ResourceException {
        final ClassLoader loader = Configuration.class.getClassLoader();

        final List<ResourceInstance<Configuration>> configs =
            ResourceFileLoader.loadInstances(CONFIGURATION_RESOURCE, loader, Configuration.class);

        for (final ResourceInstance<Configuration> config : configs) {
            config.invoke(new Transform<Configuration, Void>() {
                @Override
                public Void transform(Configuration result) throws Exception {
                    result.configure(builder, params);
                    return null;
                }
            });
        }
    }

    private static void configureBuilder(
        final HeroicCore.Builder builder, final Parameters params
    ) {
        final Path config = getConfigPath(params.extra);

        // setup as a service accepting http requests.
        builder.setupService(true);

        // do not require a shell server by default.
        builder.setupShellServer(false);

        if (config != null) {
            builder.configPath(config);
        }

        if (params.id != null) {
            builder.id(params.id);
        }

        if (params.port != null) {
            builder.port(params.port);
        }

        if (params.host != null) {
            builder.host(params.host);
        }

        if (params.startupPing != null) {
            builder.startupPing(params.startupPing);
        }

        if (params.startupId != null) {
            builder.startupId(params.startupId);
        }

        for (final String profile : params.profiles) {
            builder.profile(setupProfile(profile));
        }

        builder.parameters(ExtraParameters.ofList(params.parameters));
        builder.modules(HeroicModules.ALL_MODULES);
    }

    private static HeroicProfile setupProfile(final String profile) {
        final HeroicProfile p = HeroicModules.PROFILES.get(profile);

        if (p == null) {
            throw new IllegalArgumentException("No such profile: " + profile);
        }

        log.info("Enabling Profile: {}", profile);
        return p;
    }

    private static Path getConfigPath(final List<String> extra) {
        if (extra.size() > 0) {
            return Paths.get(extra.get(0));
        }

        if (Files.isRegularFile(DEFAULT_CONFIG)) {
            return DEFAULT_CONFIG;
        }

        return null;
    }

    private static Parameters parseArguments(final String[] args) {
        final Parameters params = new Parameters();

        final CmdLineParser parser = CmdLine.createParser(params);

        try {
            parser.parseArgument(args);
        } catch (final CmdLineException e) {
            log.error("Error parsing arguments", e);
            return null;
        }

        if (params.help) {
            parser.printUsage(System.out);
            System.out.println();
            HeroicModules.printAllUsage(System.out, "-p");
            return null;
        }

        return params;
    }

    // @formatter:off
    @ToString
    @Data
    public static class Parameters {
        @Option(name = "-P", aliases = {"--profile"},
                usage = "Activate a pre-defined profile instead of a configuration file. Profiles" +
                        " are pre-defined configurations, useful for messing around with the " +
                        "system.")
        private List<String> profiles = new ArrayList<>();

        @Option(name = "--port", usage = "Port number to bind to")
        private Integer port = null;

        @Option(name = "--host", usage = "Host to bind to")
        private String host = null;

        @Option(name = "--id", usage = "Heroic identifier")
        private String id = null;

        @Option(name = "-h", aliases = {"--help"}, help = true, usage = "Display help.")
        private boolean help;

        @Option(name = "--startup-ping",
                usage = "Send a JSON frame to the given URI containing information about this " +
                        "host after it has started.")
        private String startupPing;

        @Option(name = "--startup-id", usage = "Explicit id of a specific startup instance.")
        private String startupId;

        @Option(name = "-X", usage = "Define an extra parameter", metaVar = "<key>=<value>")
        private List<String> parameters = new ArrayList<>();

        @Argument
        private List<String> extra = new ArrayList<>();
    }
    // @formatter:on
}

@@ -7020,17 +7020,17 @@
 .out, %22-
-p
+P
 %22);%0A    

Fix HeroicService param in help doc