/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.module.extension.internal.loader.java.type;

import static org.mule.runtime.api.util.collection.Collectors.toImmutableMap;
import static org.mule.runtime.extension.api.ExtensionConstants.SCHEDULING_STRATEGY_PARAMETER_NAME;
import static org.mule.runtime.extension.api.ExtensionConstants.TLS_PARAMETER_NAME;
import static org.mule.runtime.extension.api.ExtensionConstants.TRANSACTIONAL_ACTION_PARAMETER_NAME;
import static org.mule.runtime.extension.api.ExtensionConstants.TRANSACTIONAL_TYPE_PARAMETER_NAME;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_NAMESPACE;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_PREFIX;
import static org.mule.runtime.internal.dsl.DslConstants.SCHEDULING_STRATEGY_ELEMENT_IDENTIFIER;
import static org.mule.runtime.internal.dsl.DslConstants.TLS_CONTEXT_ELEMENT_IDENTIFIER;
import static org.mule.runtime.internal.dsl.DslConstants.TLS_CRL_FILE_ELEMENT_IDENTIFIER;
import static org.mule.runtime.internal.dsl.DslConstants.TLS_CUSTOM_OCSP_RESPONDER_ELEMENT_IDENTIFIER;
import static org.mule.runtime.internal.dsl.DslConstants.TLS_PREFIX;
import static org.mule.runtime.internal.dsl.DslConstants.TLS_STANDARD_REVOCATION_CHECK_ELEMENT_IDENTIFIER;

import org.mule.runtime.api.meta.model.ParameterDslConfiguration;
import org.mule.runtime.api.tls.TlsContextFactory;
import org.mule.runtime.api.tx.TransactionType;
import org.mule.runtime.core.api.source.scheduler.Scheduler;
import org.mule.runtime.extension.api.declaration.type.DefaultExtensionsTypeLoaderFactory;
import org.mule.runtime.extension.api.property.QNameModelProperty;
import org.mule.runtime.extension.api.tx.OperationTransactionalAction;
import org.mule.runtime.extension.api.tx.SourceTransactionalAction;
import org.mule.runtime.module.extension.api.loader.java.type.Type;
import org.mule.runtime.module.extension.internal.loader.java.type.runtime.TypeWrapper;

import com.google.common.collect.ImmutableMap;

import java.util.Map;
import java.util.Optional;

import javax.xml.namespace.QName;

/**
 * Mapping for types considered of "Infrastructure", of the {@link Class} of the infrastructure type and the {@link String} name
 * of it.
 *
 * @since 4.0
 */
public final class InfrastructureTypeMapping {

  public static final String TLS_NAMESPACE_URI = "http://www.mulesoft.org/schema/mule/tls";

  private static Map<Class<?>, InfrastructureType> MAPPING = ImmutableMap.<Class<?>, InfrastructureType>builder()
      .put(TlsContextFactory.class, new InfrastructureType(TLS_PARAMETER_NAME, 8))
      .put(SourceTransactionalAction.class, new InfrastructureType(TRANSACTIONAL_ACTION_PARAMETER_NAME, 6))
      .put(OperationTransactionalAction.class, new InfrastructureType(TRANSACTIONAL_ACTION_PARAMETER_NAME, 7))
      .put(TransactionType.class, new InfrastructureType(TRANSACTIONAL_TYPE_PARAMETER_NAME, 9))
      .put(Scheduler.class, new InfrastructureType(SCHEDULING_STRATEGY_PARAMETER_NAME, 10))
      .build();

  private static Map<Type, InfrastructureType> TYPE_MAPPING = MAPPING.entrySet()
      .stream()
      .collect(toImmutableMap(entry -> new TypeWrapper(entry.getKey(),
                                                       new DefaultExtensionsTypeLoaderFactory()
                                                           .createTypeLoader(InfrastructureTypeMapping.class.getClassLoader())),
                              Map.Entry::getValue));

  public static Optional<InfrastructureType> getInfrastructureType(Type type) {
    return TYPE_MAPPING.entrySet()
        .stream()
        .filter(entry -> entry.getKey().isSameType(type))
        .map(Map.Entry::getValue)
        .findFirst();
  }

  private static Map<String, QNameModelProperty> QNAMES = ImmutableMap.<String, QNameModelProperty>builder()
      .put(SCHEDULING_STRATEGY_PARAMETER_NAME,
           new QNameModelProperty(new QName(CORE_NAMESPACE, SCHEDULING_STRATEGY_ELEMENT_IDENTIFIER, CORE_PREFIX)))
      .put(TLS_PARAMETER_NAME,
           new QNameModelProperty(new QName(TLS_NAMESPACE_URI,
                                            TLS_CONTEXT_ELEMENT_IDENTIFIER,
                                            TLS_PREFIX)))
      .put(TLS_CUSTOM_OCSP_RESPONDER_ELEMENT_IDENTIFIER,
           new QNameModelProperty(new QName(TLS_NAMESPACE_URI,
                                            TLS_CUSTOM_OCSP_RESPONDER_ELEMENT_IDENTIFIER,
                                            TLS_PREFIX)))
      .put(TLS_STANDARD_REVOCATION_CHECK_ELEMENT_IDENTIFIER,
           new QNameModelProperty(new QName(TLS_NAMESPACE_URI,
                                            TLS_STANDARD_REVOCATION_CHECK_ELEMENT_IDENTIFIER,
                                            TLS_PREFIX)))
      .put(TLS_CRL_FILE_ELEMENT_IDENTIFIER,
           new QNameModelProperty(new QName(TLS_NAMESPACE_URI,
                                            TLS_CRL_FILE_ELEMENT_IDENTIFIER,
                                            TLS_PREFIX)))
      .build();

  private static Map<String, ParameterDslConfiguration> DSL_CONFIGURATIONS =
      ImmutableMap.<String, ParameterDslConfiguration>builder()
          .put(SCHEDULING_STRATEGY_PARAMETER_NAME,
               ParameterDslConfiguration.builder()
                   .allowsInlineDefinition(true)
                   .allowTopLevelDefinition(false)
                   .allowsReferences(false)
                   .build())
          .put(TLS_PARAMETER_NAME,
               ParameterDslConfiguration.builder()
                   .allowsInlineDefinition(true)
                   .allowTopLevelDefinition(true)
                   .allowsReferences(true)
                   .build())
          .put(TRANSACTIONAL_ACTION_PARAMETER_NAME,
               ParameterDslConfiguration.builder()
                   .allowsInlineDefinition(false)
                   .allowTopLevelDefinition(false)
                   .allowsReferences(false)
                   .build())
          .put(TRANSACTIONAL_TYPE_PARAMETER_NAME,
               ParameterDslConfiguration.builder()
                   .allowsInlineDefinition(false)
                   .allowTopLevelDefinition(false)
                   .allowsReferences(false)
                   .build())
          .build();

  private static Map<String, String> nameMap =
      MAPPING.entrySet().stream().collect(
                                          toImmutableMap(e -> e.getKey().getName(), e -> e.getValue().getName()));

  public static Map<Class<?>, InfrastructureType> getMap() {
    return MAPPING;
  }

  public static Map<String, String> getNameMap() {
    return nameMap;
  }

  public static Optional<QNameModelProperty> getQName(String name) {
    return Optional.ofNullable(QNAMES.get(name));
  }

  public static Optional<ParameterDslConfiguration> getDslConfiguration(String name) {
    return Optional.ofNullable(DSL_CONFIGURATIONS.get(name));
  }

  private InfrastructureTypeMapping() {}

  public static class InfrastructureType {

    private final String name;
    private final int sequence;

    InfrastructureType(String name, int sequence) {
      this.name = name;
      this.sequence = sequence;
    }

    public String getName() {
      return name;
    }

    public int getSequence() {
      return sequence;
    }
  }
}
