/*
 * 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.karaf.main;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.nio.channels.FileLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.felix.utils.properties.TypedProperties;
import org.apache.karaf.util.locks.FileLockUtils;
import org.osgi.framework.launch.Framework;

public class InstanceHelper {

    static void updateInstancePid(final File karafHome, final File karafBase, final boolean isStartingInstance) {
        try {
            final String instanceName = System.getProperty("karaf.name");
            final String pid = isStartingInstance ? getPid() : "0";

            if (instanceName != null) {
                String storage = System.getProperty("karaf.instances");
                if (storage == null) {
                    throw new Exception("System property 'karaf.instances' is not set. \n" +
                            "This property needs to be set to the full path of the instance.properties file.");
                }
                File storageFile = new File(storage);
                final File propertiesFile = new File(storageFile, "instance.properties");
                if (!propertiesFile.getParentFile().exists()) {
                    try {
                        if (!propertiesFile.getParentFile().mkdirs()) {
                            throw new Exception("Unable to create directory " + propertiesFile.getParentFile());
                        }
                    } catch (SecurityException se) {
                        throw new Exception(se.getMessage());
                    }
                }
                // don't instance.properties if we're stopping and can't acquire lock
                if (!isStartingInstance) {
                    boolean proceed = true;
                    try (RandomAccessFile raf = new RandomAccessFile(propertiesFile, "rw")) {
                        FileLock lock = raf.getChannel().tryLock();
                        if (lock == null) {
                            proceed = false;
                        } else {
                            lock.release();
                        }
                    }
                    // if proceed is true than we got the lock or OverlappingFileLockException
                    // but we may proceed in either case

                    if (!proceed) {
                        // we didn't acquire lock, it may mean that root container is holding the lock when
                        // stopping the child
                        return;
                    }
                }
                FileLockUtils.execute(propertiesFile, (TypedProperties props) -> {
                    if (props.isEmpty()) {
                        // it's the first instance running, so we consider as root
                        props.put("count", "1");
                        props.put("item.0.name", instanceName);
                        props.put("item.0.loc", karafBase.getAbsolutePath());
                        props.put("item.0.pid", pid);
                        props.put("item.0.root", "true");
                    } else {
                        int count = Integer.parseInt(props.get("count").toString());
                        for (int i = 0; i < count; i++) {
                            String name = props.get("item." + i + ".name").toString();
                            if (name.equals(instanceName)) {
                                props.put("item." + i + ".pid", pid);
                                return;
                            }
                        }
                        // it's not found, let assume it's the root instance, so 0
                        props.put("item.0.name", instanceName);
                        props.put("item.0.pid", pid);
                    }
                }, true);
            }
        } catch (Exception e) {
            System.err.println("Unable to update instance pid: " + e.getMessage());
        }
    }

    /* KARAF-5798: consistent way to obtain the process ID */
    private static String getPid() {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        /*
         * RuntimeMXBean#getName() makes no guarantees about the name, so use a
         * regex to match the common(?) "pid@host" format
         */
        Pattern pattern = Pattern.compile("^([0-9]+)@.+$");
        Matcher matcher = pattern.matcher(name);
        return matcher.matches() ? matcher.group(1) : name;
     }

    /* KARAF-5798: now called by Main#launch() */
    static void writePid(String pidFile) {
        try {
            if (pidFile != null) {
                int pid = Integer.parseInt(getPid());
                Writer w = new OutputStreamWriter(new FileOutputStream(pidFile));
                w.write(Integer.toString(pid));
                w.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static AutoCloseable setupShutdown(ConfigProperties config, Framework framework) {
        try {
            int port = config.shutdownPort;
            String host = config.shutdownHost;
            String portFile = config.portFile;
            final String shutdown = config.shutdownCommand;
            if (port >= 0) {
                ServerSocket shutdownSocket = new ServerSocket(port, 1, InetAddress.getByName(host));
                if (port == 0) {
                    port = shutdownSocket.getLocalPort();
                }
                if (portFile != null) {
                    File portF = new File(portFile);
                    portF.getParentFile().mkdirs();
                    Writer w = new OutputStreamWriter(new FileOutputStream(portF));
                    w.write(Integer.toString(port));
                    w.close();
                }
                ShutdownSocketThread thread = new ShutdownSocketThread(shutdown, shutdownSocket, framework);
                thread.start();
                return thread;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
