/*
 * 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.activemq.artemis.cli.commands;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.activemq.artemis.cli.CLIException;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

public class InstallAbstract extends InputAbstract {

   protected static final String LOG_DIRNAME = "log";
   protected static final String OOM_DUMP_FILENAME = "oom_dump.hprof";

   @Parameters(description = "The instance directory to hold the broker's configuration and data. Path must be writable.")
   protected File directory;

   @Option(names = "--etc", description = "Directory where ActiveMQ configuration is located. Paths can be absolute or relative to artemis.instance directory. Default: etc.")
   protected String etc = "etc";

   @Option(names = "--data", description = "Directory where ActiveMQ data are stored. Paths can be absolute or relative to artemis.instance directory. Default: data.")
   protected String data = "data";

   @Option(names = "--home", description = "Directory where ActiveMQ Artemis is installed.")
   protected File home;

   @Option(names = "--encoding", description = "The encoding that text files should use. Default: UTF-8.")
   protected String encoding = "UTF-8";

   @Option(names = "--windows", description = "Force Windows script creation. Default: based on your actual system.")
   protected boolean windows = false;

   @Option(names = {"--cygwin", "--linux"}, description = "Force Linux or Cygwin script creation. Default: based on your actual system.")
   protected boolean nix = false;

   @Option(names = "--java-options", description = "Extra Java options to be passed to the profile.")
   protected List<String> javaOptions;

   @Option(names = "--java-utility-options", description = "Extra Java options to be passed to the utility profile.")
   protected List<String> javaUtilityOptions;

   @Option(names = "--java-memory", description = "Define the -Xmx memory parameter for the broker. Default: 2G.")
   protected String javaMemory = "2G";

   // "calculated" during run
   protected boolean IS_WINDOWS;
   // "calculated" during run
   protected boolean IS_NIX;

   protected String getJavaOptions() {
      StringBuilder builder = new StringBuilder();
      if (javaOptions != null) {
         javaOptions.forEach(s -> builder.append(s).append(" "));
      }
      return builder.toString();
   }

   protected String getJavaUtilityOptions() {
      StringBuilder builder = new StringBuilder();
      if (javaUtilityOptions != null) {
         javaUtilityOptions.forEach(s -> builder.append(s).append(" "));
      }
      return builder.toString();
   }

   public String getEncoding() {
      return encoding;
   }

   public void setEncoding(String encoding) {
      this.encoding = encoding;
   }

   public File getInstance() {
      return directory;
   }

   public void setInstance(File directory) {
      this.directory = directory;
   }

   public File getHome() {
      if (home == null) {
         home = new File(getBrokerHome());
      }
      return home;
   }

   public File getDirectory() {
      return directory;
   }

   public InstallAbstract setDirectory(File directory) {
      this.directory = directory;
      return this;
   }

   public boolean isWindows() {
      return windows;
   }

   public InstallAbstract setWindows(boolean windows) {
      this.windows = windows;
      return this;
   }

   public boolean isNix() {
      return nix;
   }

   public InstallAbstract setNix(boolean nix) {
      this.nix = nix;
      return this;
   }

   public Object run(ActionContext context) throws Exception {
      IS_NIX = false;
      IS_WINDOWS = false;
      if (nix) {
         IS_NIX = true;
         return null;
      }
      if (windows) {
         IS_WINDOWS = true;
         return null;
      }
      if ("cygwin".equals(System.getenv("OSTYPE"))) {
         IS_NIX = true;
         return null;
      }
      if (System.getProperty("os.name").toLowerCase().trim().startsWith("win")) {
         IS_WINDOWS = true;
         return null;
      }
      // Fallback to *nix
      IS_NIX = true;
      return null;
   }

   protected static String applyFilters(String content, Map<String, String> filters) {
      if (filters != null) {
         for (Map.Entry<String, String> entry : filters.entrySet()) {
            content = replace(content, entry.getKey(), entry.getValue());
         }
      }
      return content;
   }

   protected static String replace(String content, String key, String value) {
      return content.replaceAll(Pattern.quote(key), Matcher.quoteReplacement(value));
   }

   protected static void copy(InputStream is, OutputStream os) throws IOException {
      byte[] buffer = new byte[1024 * 4];
      int c = is.read(buffer);
      while (c >= 0) {
         os.write(buffer, 0, c);
         c = is.read(buffer);
      }
   }

   protected void write(String source,
                      File target,
                      Map<String, String> filters,
                      boolean unixTarget, boolean force) throws Exception {
      if (target.exists() && !force) {
         throw new CLIException(String.format("The file '%s' already exists.  Use --force to overwrite.", target));
      }

      String content = readTextFile(source, filters);

      if (content == null) {
         new Exception(source + " not found").printStackTrace();
      }

      // and then writing out in the new target encoding..  Let's also replace \n with the values
      // that is correct for the current platform.
      String separator = unixTarget && IS_NIX ? "\n" : System.lineSeparator();
      content = content.replaceAll("\\r?\\n", Matcher.quoteReplacement(separator));
      ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes(encoding));
      try (FileOutputStream fout = new FileOutputStream(target)) {
         copy(in, fout);
      }
   }

   protected static String readTextFile(String source, Map<String, String> filters) throws IOException {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      try (InputStream in = openStream(source)) {
         if (in == null) {
            throw new IOException("could not find resource " + source);
         }
         copy(in, out);
      }
      return applyFilters(new String(out.toByteArray(), StandardCharsets.UTF_8), filters);
   }

   protected void write(String source, boolean force) throws IOException {
      File target = new File(directory, source);
      if (target.exists() && !force) {
         throw new RuntimeException(String.format("The file '%s' already exists.  Use --force to overwrite.", target));
      }
      try (FileOutputStream fout = new FileOutputStream(target)) {
         try (InputStream in = openStream(source)) {
            copy(in, fout);
         }
      }
   }

   protected static InputStream openStream(String source) {
      return InputAbstract.class.getResourceAsStream(source);
   }
}
