content: /*
   * 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.camel.karavan.generator;
  
  import io.vertx.core.Vertx;
  import io.vertx.core.buffer.Buffer;
  import io.vertx.core.json.JsonArray;
  import io.vertx.core.json.JsonObject;
  import org.apache.camel.catalog.CamelCatalog;
  import org.apache.camel.catalog.DefaultCamelCatalog;
  import org.apache.camel.catalog.VersionHelper;
  import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader;
  
  import java.io.BufferedReader;
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.nio.charset.StandardCharsets;
  import java.nio.file.*;
  import java.util.*;
  import java.util.logging.Logger;
  import java.util.stream.Collectors;
  
  public class AbstractGenerator {
  
      public static final List<String> deprecatedClasses = new ArrayList<>();
  
      Logger LOGGER = Logger.getLogger(AbstractGenerator.class.getName());
      protected static boolean print = false;
      protected final String rootPath;
  
      public AbstractGenerator(String rootPath) {
          this.rootPath = rootPath;
      }
  
      protected void print(String line) {
          if (print) {
              System.out.println(line);
          }
      }
  
      protected Vertx vertx = Vertx.vertx();
  
      protected JsonObject getDefinitions(String source) {
          Buffer buffer = vertx.fileSystem().readFileBlocking(source);
          return new JsonObject(buffer).getJsonObject("items").getJsonObject("definitions");
      }
  
      protected JsonObject getDefinitions() {
          String camelYamlDSL = getCamelYamlDSL();
          return new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("definitions");
      }
  
      protected String getStepNameForClass(String className) {
          if (className.equals("CatchDefinition")) {
              className = "doCatch";
          } else if (className.equals("ConvertBodyDefinition")) {
              className = "convertBodyTo";
          } else if (className.equals("ConvertHeaderDefinition")) {
              className = "convertHeaderTo";
          } else if (className.equals("RouteTemplateParameterDefinition")) {
              className = "templateParameter";
          } else if (className.equals("ConvertVariableDefinition")) {
              className = "convertVariableTo";
          } else if (className.equals("TryDefinition")) {
              className = "doTry";
          } else if (className.equals("LangChain4jTokenizerDefinition")) {
              className = "langChain4j";
          } else if (className.equals("FinallyDefinition")) {
              className = "doFinally";
          } else if (className.equals("ToDynamicDefinition")) {
              className = "toD";
          } else if (className.equals("SamplingDefinition")) {
              className = "sample";
          } else if (className.equals("BeanPropertiesDefinition")) {
              className = "properties";
          } else if (className.equals("BatchResequencerConfig")) {
              className = "batchConfig";
          } else if (className.equals("StreamResequencerConfig")) {
              className = "streamConfig";
          } else if (className.equals("RestSecuritiesDefinition")) {
              className = "securityDefinitions";
          } else if (className.endsWith("Definition")) {
              className = className.substring(0, className.length() - 10);
          } else if ("DfdlDataFormat".equals(className)) {
              return "dfdl";
          } else if (className.endsWith("DataFormat")) {
              return getDataFormatStepNameForClass().get(className);
          } else if (className.endsWith("Expression")) {
              return getExpressionStepNameForClass().get(className);
          }
          return deCapitalize(className);
      }
  
      protected Map<String, String> getDataFormatStepNameForClass() {
          Map<String, String> stepNames = new LinkedHashMap<>();
          JsonObject definitions = getDefinitions();
          JsonObject props = definitions.getJsonObject("org.apache.camel.model.dataformat.DataFormatsDefinition").getJsonObject("properties");
          props.getMap().keySet().forEach(key -> {
              String className = classSimple(props.getJsonObject(key).getString("$ref"));
              stepNames.put(className, key);
          });
          return stepNames;
      }
  
      protected Map<String, String> getExpressionStepNameForClass() {
          Map<String, String> stepNames = new LinkedHashMap<>();
          JsonObject definitions = getDefinitions();
          JsonArray props = definitions.getJsonObject("org.apache.camel.model.language.ExpressionDefinition").getJsonArray("anyOf").getJsonObject(0).getJsonArray("oneOf");
          for (int i = 0; i < props.size(); i++) {
              JsonObject prop = props.getJsonObject(i);
              String key = prop.getJsonObject("properties").getMap().keySet().iterator().next();
              String fullClassName = prop.getJsonObject("properties").getJsonObject(key).getString("$ref");
              String className = classSimple(fullClassName);
              stepNames.put(className, key);
          }
          return stepNames;
      }
  
      private Map<String, JsonObject> getJsonObjectProperties(JsonObject val) {
          Map<String, JsonObject> properties = new LinkedHashMap<>();
          val.getMap().keySet().forEach(s -> {
              JsonObject value = val.getJsonObject(s);
              if (!value.getMap().isEmpty()) {
                  properties.put(s, val.getJsonObject(s));
              } else if (s.equals("expression")) {
                  properties.put(s, JsonObject.of("$ref", "#/items/definitions/org.apache.camel.model.language.ExpressionDefinition"));
              }
          });
          return properties;
      }
  
      protected Map<String, JsonObject> getClassProperties(String stepName, JsonObject obj) {
          Map<String, JsonObject> properties = new LinkedHashMap<>();
          obj.getMap().keySet().forEach(key -> {
              if (key.equals("oneOf")) {
                  JsonObject val = obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties");
                  properties.putAll(getJsonObjectProperties(val));
              } else if (key.equals("properties")) {
                  JsonObject val = obj.getJsonObject("properties");
                  properties.putAll(getJsonObjectProperties(val));
              } else if (key.equals("anyOf")) {
                  JsonArray anyOfs = obj.getJsonArray("anyOf");
                  anyOfs.forEach(o -> {
                      JsonObject ob = (JsonObject) o;
                      JsonArray vals = ob.getJsonArray("oneOf");
                      for (int i = 0; i < vals.size(); i++) {
                          JsonObject data = vals.getJsonObject(i);
                          if (!data.containsKey("not") && data.containsKey("type")) {
                              JsonObject val = data.getJsonObject("properties");
                              properties.putAll(getJsonObjectProperties(val));
                          }
                      }
                  });
              }
          });
          return properties;
      }
  
      protected Comparator<String> getComparator(String stepName) {
          String json = getMetaModel(stepName);
          if (json != null) {
              JsonObject props = new JsonObject(json).getJsonObject("properties");
              List<String> propsLowerCase = props.getMap().keySet().stream().map(String::toLowerCase).toList();
              return Comparator.comparing(e -> {
                  if (propsLowerCase.contains(e.toLowerCase())) return propsLowerCase.indexOf(e.toLowerCase());
                  else return propsLowerCase.size() + 1;
              });
          }
          return Comparator.comparing(s -> 0);
      }
  
  //    protected Map<String, String> getStepNames(){
  //        // Prepare stepNames map
  //        JsonObject definitions = getDefinitions();
  //        Map<String, String> stepNames = getProcessorStepName(new JsonObject(getCamelYamlDSL()).getJsonObject("items").getJsonObject("properties"));
  //        stepNames.putAll(getProcessorStepName(definitions.getJsonObject("org.apache.camel.model.ProcessorDefinition").getJsonObject("properties")));
  //        stepNames.putAll(getProcessorStepName(definitions.getJsonObject("org.apache.camel.model.language.ExpressionDefinition").getJsonObject("properties")));
  //        stepNames.putAll(getProcessorStepName(definitions.getJsonObject("org.apache.camel.model.language.ExpressionDefinition").getJsonObject("properties")));
  //        stepNames.putAll(getProcessorStepName(definitions.getJsonObject("org.apache.camel.model.dataformat.DataFormatsDefinition").getJsonObject("properties")));
  //        return stepNames;
  //    }
  
      protected Map<String, JsonObject> getDslMetadata() {
          // Generate DSL Metadata
          String camelYamlDSL = getCamelYamlDSL();
          JsonObject definitions = new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("definitions");
  
          // Prepare stepNames map
          Map<String, String> stepNames = getProcessorStepNameMap();
  
          Map<String, JsonObject> classProps = new LinkedHashMap<>();
          Map<String, Object> defsMap = new LinkedHashMap<>();
          defsMap.putAll(definitions.getJsonObject("org.apache.camel.model.ProcessorDefinition").getJsonObject("properties").getMap());
          defsMap.putAll(new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("properties").getMap());
  
          classProps.clear();
          defsMap.forEach((s, o) -> {
              String ref = ((Map) o).get("$ref").toString();
              String name = classSimple(ref);
              JsonObject obj = getDefinition(definitions, ref);
              JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
              classProps.put(name, props);
          });
  
          // add additional classes
          getClasses(definitions, "org.apache.camel.model").forEach(s -> {
              String className = classSimple(s);
              if (!stepNames.containsKey(className)) {
                  String stepName = deCapitalize(className.replace("Definition", ""));
                  stepNames.put(className, stepName);
              }
          });
  
          definitions.getMap().forEach((s, o) -> {
              if (s.startsWith("org.apache.camel.model.") && s.endsWith("Definition")) {
                  String name = classSimple(s);
                  JsonObject obj = getDefinition(definitions, s);
                  JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
                  classProps.put(name, props);
              }
          });
  
          return classProps;
      }
  
      protected String getCamelYamlDSL() {
          try {
              InputStream inputStream = YamlRoutesBuilderLoader.class.getResourceAsStream("/schema/camelYamlDsl.json");
              String data = new BufferedReader(new InputStreamReader(inputStream))
                      .lines().collect(Collectors.joining(System.getProperty("line.separator")));
              return data;
          } catch (Exception e) {
              return null;
          }
      }
  
      protected String readFileText(String template) {
          Buffer templateBuffer = vertx.fileSystem().readFileBlocking(template);
          return templateBuffer.toString();
      }
  
      protected void saveFile(String folder, String fileName, String text) {
          folder = rootPath.concat(File.separator).concat(folder);
          Path path = Paths.get(folder);
          try {
              if (!Files.exists(path)) {
                  Files.createDirectories(path);
              }
              File targetFile = Paths.get(folder, fileName).toFile();
              LOGGER.info("Saving file " + targetFile.getAbsolutePath());
              Files.copy(new ByteArrayInputStream(text.getBytes()), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  
      protected void writeFileText(String filePath, String data) throws IOException {
          Files.writeString(Paths.get(rootPath.concat(File.separator).concat(filePath)), data);
      }
  
      protected JsonObject getProperties(JsonObject definitions, String classname) {
          JsonObject root = definitions.getJsonObject(classname);
          JsonObject props = root.getJsonObject("properties");
          JsonArray oneOf = root.getJsonArray("oneOf");
          if (props != null) {
              return props;
          } else {
              return oneOf.getJsonObject(1).getJsonObject("properties");
          }
      }
  
      protected String getPropertyToMapString(JsonObject definitions, String classname) {
          JsonObject root = definitions.getJsonObject(classname);
          JsonArray oneOf = root.getJsonArray("oneOf");
          JsonArray required = root.getJsonArray("required");
          if (oneOf != null && required != null) {
              return required.getString(0);
          }
          return null;
      }
  
      protected String camelize(String name, String separator) {
          return Arrays.stream(name.split(separator)).map(s -> capitalize(s)).collect(Collectors.joining());
      }
  
      protected boolean excludeProperty(String stepName, String name, String attributeType) {
          var hasModelInCatalog = hasModelInCatalog(stepName);
          var hasInCatalog = hasPropertyInCatalogIgnoreCase(stepName, name);
          var clazz = attributeType.contains("|") ? attributeType.split("\\|")[0].trim() : "";
  
          if (hasModelInCatalog
                  && !hasInCatalog
                  && !attributeType.contains("[]")
                  && !attributeType.contains("{}")
                  && !attributeType.contains("Definition")
                  && !attributeType.contains("DataFormat")
                  && !attributeType.contains("FilterConfiguration")
                  && !attributeType.contains("BatchResequencerConfig")
                  && !attributeType.contains("StreamResequencerConfig")
                  && !attributeType.contains("Expression")) {
              return true;
          } else if (getDeprecatedClasses().contains(clazz)) {
              return true;
          }
          return false;
      }
  
      protected String capitalize(String str) {
          return str.length() == 0 ? str
                  : str.length() == 1 ? str.toUpperCase()
                  : str.substring(0, 1).toUpperCase() + str.substring(1);
      }
  
      protected String deCapitalize(String str) {
          return str.length() == 0 ? str
                  : str.length() == 1 ? str.toLowerCase()
                  : str.substring(0, 1).toLowerCase() + str.substring(1);
      }
  
      protected String getMetaModelApp(String name) {
          try {
              InputStream inputStream = CamelCatalog.class.getResourceAsStream("/org/apache/camel/catalog/models-app/" + name + ".json");
              String data = new BufferedReader(new InputStreamReader(inputStream))
                      .lines().collect(Collectors.joining(System.lineSeparator()));
              return data;
          } catch (Exception e) {
              return null;
          }
      }
  
      protected String readBean(String name) {
          InputStream inputStream = DefaultCamelCatalog.class.getResourceAsStream("/org/apache/camel/catalog/beans/" + name + ".json");
          return new BufferedReader(
                  new InputStreamReader(inputStream, StandardCharsets.UTF_8))
                  .lines().collect(Collectors.joining(System.lineSeparator()));
      }
  
      protected String getMetaModel(String name) {
          try {
              InputStream inputStream = CamelCatalog.class.getResourceAsStream("/org/apache/camel/catalog/models/" + name + ".json");
              String data = new BufferedReader(new InputStreamReader(inputStream))
                      .lines().collect(Collectors.joining(System.getProperty("line.separator")));
              return data;
          } catch (Exception e) {
              return null;
          }
      }
  
      protected String getPropertyTypeInCatalog(String model, String property) {
          String json = getMetaModel(model);
          if (json != null) {
              JsonObject props = new JsonObject(json).getJsonObject("properties");
              JsonObject values = props.getJsonObject(property);
              return values != null ? values.getString("type") : null;
          }
          return null;
      }
  
      protected boolean hasPropertyInCatalog(String model, String property) {
          String json = getMetaModel(model);
          if (json != null) {
              JsonObject props = new JsonObject(json).getJsonObject("properties");
              return props.containsKey(property);
          }
          return false;
      }
  
      protected boolean hasPropertyInCatalogIgnoreCase(String model, String property) {
          String json = getMetaModel(model);
          if (json != null) {
              JsonObject props = new JsonObject(json).getJsonObject("properties");
              return props.getMap().keySet().stream().map(String::toLowerCase).anyMatch(k -> Objects.equals(k, property.toLowerCase()));
          }
          return false;
      }
  
      protected boolean hasModelInCatalog(String model) {
          String json = getMetaModel(model);
          return json != null;
      }
  
      protected String getMetaDataFormat(String name) {
          try {
              InputStream inputStream = VersionHelper.class.getResourceAsStream("/org/apache/camel/catalog/models/" + name + ".json");
              String data = new BufferedReader(new InputStreamReader(inputStream))
                      .lines().collect(Collectors.joining(System.getProperty("line.separator")));
              return data;
          } catch (Exception e) {
              return null;
          }
      }
  
  //    protected List<String> listResources(String resourceFolder) {
  //        List<Path> filePaths = new ArrayList<>();
  //        try {
  //            URI uri = VersionHelper.class.getResource(resourceFolder).toURI();
  //            Path myPath;
  //            FileSystem fileSystem = null;
  //            if (uri.getScheme().equals("jar")) {
  //                fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
  //                myPath = fileSystem.getPath(resourceFolder);
  //            } else {
  //                myPath = Paths.get(uri);
  //            }
  //            filePaths = Files.walk(myPath, 10).collect(Collectors.toList());
  //
  //            // Close the file system if opened
  //            if (fileSystem != null) {
  //                fileSystem.close();
  //            }
  //        } catch (URISyntaxException | IOException e) {
  //            e.printStackTrace();
  //        }
  //        return filePaths.stream().map(path -> path.getFileName().toString())
  //                .collect(Collectors.toList());
  //    }
  
      protected String getMetaLanguage(String name) {
          try {
              InputStream inputStream = VersionHelper.class.getResourceAsStream("/org/apache/camel/catalog/models/" + name + ".json");
              String data = new BufferedReader(new InputStreamReader(inputStream))
                      .lines().collect(Collectors.joining(System.getProperty("line.separator")));
              return data;
          } catch (Exception e) {
              return null;
          }
      }
  
      protected boolean isAttributeRef(JsonObject attribute) {
          return attribute.containsKey("$ref");
      }
  
      protected String getAttributeClass(JsonObject attribute) {
          return classSimple(attribute.getString("$ref"));
      }
  
      protected String getAttributeArrayClass(String stepName, JsonObject attribute) {
          if (attribute.containsKey("items")) {
              JsonObject items = attribute.getJsonObject("items");
              if (items.containsKey("$ref")) {
                  return classSimple(items.getString("$ref"));
              } else if (items.containsKey("properties")) {
                  JsonObject properties = items.getJsonObject("properties");
                  return classSimple(properties.getJsonObject(stepName).getString("$ref"));
              } else {
                  return items.getString("type");
              }
          }
          return "string";
      }
  
      protected String classSimple(String classFullName) {
          String[] def = classFullName.split("\\.");
          return def[def.length - 1];
      }
  
      protected List<String> getClasses(JsonObject definitions, String filter) {
          List<String> result = new ArrayList<>();
          definitions.getMap().forEach((s, o) -> {
              if (s.startsWith(filter) && !s.equals("org.apache.camel.dsl.yaml.deserializers.RouteFromDefinitionDeserializer")) {
                  if (!getDeprecatedClasses().contains(classSimple(s))) {
                      result.add(s);
                  }
              }
          });
          return result;
      }
  
      protected Map<String, String> getProcessorStepNameMapForObject(String key, JsonObject jsonObject) {
          Map<String, String> result = new LinkedHashMap<>();
  
          jsonObject.fieldNames().forEach(k -> {
              Object object = jsonObject.getValue(k);
              if (object instanceof JsonObject) {
                  JsonObject value = jsonObject.getJsonObject(k);
                  result.putAll(getProcessorStepNameMapForObject(k, value));
              } else if (object instanceof JsonArray) {
                  JsonArray value = jsonObject.getJsonArray(k);
                  result.putAll(getProcessorStepNameMapForArray(value));
              } else if (object instanceof String && k.equals("$ref") && !object.toString().contains(".deserializers.")) {
                  String ref = jsonObject.getString(k);
                  String className = classSimple(ref);
                  if (!getDeprecatedClasses().contains(className)) {
                      result.put(className, key);
                  }
              }
          });
          return result;
      }
  
      protected Map<String, String> getProcessorStepNameMapForArray(JsonArray jsonArray) {
          Map<String, String> result = new LinkedHashMap<>();
  
          jsonArray.forEach(object -> {
              if (object instanceof JsonObject) {
                  result.putAll(getProcessorStepNameMapForObject(null, (JsonObject) object));
              } else if (object instanceof JsonArray) {
                  result.putAll(getProcessorStepNameMapForArray((JsonArray) object));
              }
          });
          return result;
      }
  
      protected Map<String, String> getProcessorStepNameMap() {
          String camelYamlDSL = getCamelYamlDSL();
          JsonObject definitions = new JsonObject(camelYamlDSL);
          return new LinkedHashMap<>(getProcessorStepNameMapForObject(null, definitions));
      }
  
      protected Map<String, String> getProcessorDefinitionStepNameMap() {
          Map<String, String> result = new LinkedHashMap<>();
          String camelYamlDSL = getCamelYamlDSL();
          JsonObject definitions = new JsonObject(camelYamlDSL);
  
          JsonObject properties = definitions
                  .getJsonObject("items")
                  .getJsonObject("definitions")
                  .getJsonObject("org.apache.camel.model.ProcessorDefinition")
                  .getJsonObject("properties");
  
          properties.getMap().forEach((key, o) -> {
              String ref = ((Map) o).get("$ref").toString();
              String className = classSimple(ref);
              if (!getDeprecatedClasses().contains(className)) {
                  result.put(className, key);
              }
          });
          return result;
      }
  
      protected JsonObject getDefinition(JsonObject definitions, String className) {
          return definitions.getJsonObject(className.replace("#/items/definitions/", ""));
      }
  
      protected boolean isAttributeRefArray(JsonObject attribute) {
          return attribute.containsKey("type") && attribute.getString("type").equals("array");
      }
  
      protected List<String> getDeprecatedClasses() {
          if (deprecatedClasses.isEmpty()) {
              String camelYamlDSL = getCamelYamlDSL();
              JsonObject definitions = new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("definitions");
  
              definitions.getMap().forEach((s, o) -> {
                  if (s.startsWith("org.apache.camel") && !s.equals("org.apache.camel.dsl.yaml.deserializers.RouteFromDefinitionDeserializer")) {
                      JsonObject classObject = definitions.getJsonObject(s);
                      if (classObject.containsKey("deprecated") && classObject.getBoolean("deprecated")) {
                          String className = classSimple(s);
                          deprecatedClasses.add(className);
                      }
                  }
              });
          }
          return deprecatedClasses;
      }
  
      public List<String> listResources(String resourceFolder) {
          List<String> result = new ArrayList<>();
          try {
              URI uri = Objects.requireNonNull(KaravanGenerator.class.getResource(resourceFolder)).toURI();
              Path myPath;
              FileSystem fileSystem = null;
              if (uri.getScheme().equals("jar")) {
                  fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
                  myPath = fileSystem.getPath(resourceFolder);
              } else {
                  myPath = Paths.get(uri);
              }
  
              try (var pathsStream = Files.walk(myPath, 10)) {
                  pathsStream
                          .filter(Files::isRegularFile)
                          .map(path -> path.getFileName().toString())
                          .forEach(result::add);
              } catch (IOException e) {
                  var error = e.getCause() != null ? e.getCause() : e;
                  LOGGER.severe("IOException " + error.getMessage());
              }
              if (fileSystem != null) {
                  fileSystem.close();
              }
          } catch (URISyntaxException | IOException e) {
              var error = e.getCause() != null ? e.getCause() : e;
              LOGGER.severe("URISyntaxException | IOException " + error.getMessage());
          }
          return result;
      }
  
      static void clearDirectory(File directoryToBeDeleted) {
          File[] allContents = directoryToBeDeleted.listFiles();
          if (allContents != null) {
              for (File file : allContents) {
                  if (!file.getName().endsWith("gitignore")) file.delete();
              }
          }
      }
  }
  