/*
 * Copyright (c) 2016-2017 by OpenText Corporation. All Rights Reserved.
 */
package com.opentext.ia.sdk.yaml.configuration;

import java.io.*;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

import org.atteo.evo.inflector.English;
import org.yaml.snakeyaml.Yaml;

import com.opentext.ia.sdk.yaml.core.Value;
import com.opentext.ia.sdk.yaml.core.Visitor;
import com.opentext.ia.sdk.yaml.core.YamlMap;
import com.opentext.ia.sdk.yaml.resource.ResourceResolver;


/**
 * InfoArchive server configuration in YAML format.
 * @since 6.0.0
 */
@SuppressWarnings("unchecked")
public class YamlConfiguration {

  private static final Collection<Class<? extends Visitor>> YAML_NORMALIZATION_CLASSES = Arrays.asList(
      EnsureVersion.class,
      ConvertTopLevelSingularObjectsToSequences.class,
      ConvertTopLevelMapOfMapsToSequences.class,
      InsertDefaultReferences.class,
      ConvertSingularReferenceToSequenceForCollectionReferences.class,
      ConvertNestedMapOfMapsToSequences.class,
      ConvertPdiIndexes.class,
      ConvertIngestIndexes.class,
      ConvertIngestProcessors.class,
      EnsureEnumConstant.class,
      InsertDefaultValues.class,
      AddNamespaceDeclarationsToQueries.class,
      ReplacePdiSchemaNamespaceWithName.class,
      ReplacePdiIndexNamespaceWithUri.class,
      ReplaceIngestIndexNamespaceWithUri.class,
      ReplacePdiResultSchemaNamespaceWithUri.class,
      ReplacePdiYamlWithXml.class,
      ReplaceResultConfigurationHelperYamlWithXml.class,
      ReplaceIngestYamlWithXml.class
  );
  private static final String NAME = "name";

  private final YamlMap yaml;

  /**
   * Load configuration from a file in YAML format.
   * @param yaml The file to load from
   * @throws IOException When an I/O error occurs
   */
  public YamlConfiguration(File yaml) throws IOException {
    this(new FileInputStream(yaml), ResourceResolver.fromFile(yaml));
  }

  /**
   * Load configuration from a stream in YAML format.
   * @param yaml The stream to load from
   * @param resolver How to resolve resources to text
   */
  public YamlConfiguration(InputStream yaml, ResourceResolver resolver) {
    this(parse(yaml), resolver);
  }

  private static YamlMap parse(InputStream yaml) {
    YamlMap result = new YamlMap();
    for (Object data : new Yaml().loadAll(yaml)) {
      result.putAll(new YamlMap(data));
    }
    return result;
  }

  /**
   * Load configuration from a string in YAML format.
   * @param yaml The string to load from
   */
  public YamlConfiguration(String yaml) {
    this(new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), ResourceResolver.none());
  }

  YamlConfiguration(YamlMap yaml, ResourceResolver resolver) {
    this.yaml = yaml;
    normalizations(resolver).forEach(normalizer -> this.yaml.visit(normalizer));
  }

  private Stream<Visitor> normalizations(ResourceResolver resolver) {
    return Stream.concat(
        Stream.of(new InlineExternalContent(resolver)),
        YAML_NORMALIZATION_CLASSES.stream().map(this::newVisitor));
  }

  private Visitor newVisitor(Class<? extends Visitor> type) {
    try {
      Constructor<? extends Visitor> constructor = type.getDeclaredConstructor();
      constructor.setAccessible(true);
      return constructor.newInstance();
    } catch (ReflectiveOperationException e) {
      throw new RuntimeException("Failed to create instance of " + type, e);
    }
  }

  public YamlMap getMap() {
    return yaml;
  }

  /**
   * Returns the name of the application.
   * @return the name of the application
   */
  public String getApplicationName() {
    return nameOfSingle("application");
  }

  private String nameOfSingle(String type) {
    return singleInstanceOf(type).get(NAME).toString();
  }

  private YamlMap singleInstanceOf(String type) {
    List<Value> instances = yaml.get(English.plural(type)).toList();
    if (instances.size() != 1) {
      throw new IllegalArgumentException("Expected 1 " + type + ", but got " + instances.size() + " in:\n" + yaml);
    }
    return instances.get(0).toMap();
  }

  /**
   * Returns the name of the holding.
   * @return the name of the holding
   */
  public String getHoldingName() {
    return nameOfSingle("holding");
  }

  /**
   * Returns the namespace of the PDI.
   * @return the namespace of the PDI
   */
  public String getPdiSchemaName() {
    return pdiSchema().get("name").toString();
  }

  private YamlMap pdiSchema() {
    return singleInstanceOf("pdiSchema");
  }

  /**
   * Returns the contents of the XML Schema for the PDI.
   * @return the contents of the XML Schema for the PDI
   */
  public String getPdiSchema() {
    return pdiSchema().get("content", "text").toString();
  }

  /**
   * Returns the YAML structure.
   */
  @Override
  public String toString() {
    return new Yaml().dump(yaml.getRawData());
  }

}

@@ -931,57 +931,8 @@
 ss,%0A
-      ConvertTopLevelMapOfMapsToSequences.class,%0A
     
@@ -1027,32 +1027,128 @@
 ferences.class,%0A
+      ReplacePdiSchemaNamespaceWithName.class,%0A      ConvertTopLevelMapOfMapsToSequences.class,%0A
       ConvertNes
@@ -1396,55 +1396,8 @@
 ss,%0A
-      ReplacePdiSchemaNamespaceWithName.class,%0A
     

Fix YAML configuration normalization