/*
 * 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.uima.resource.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.uima.UIMAFramework;
import org.apache.uima.resource.ExternalResourceDependency;
import org.apache.uima.resource.Resource;
import org.apache.uima.resource.ResourceConfigurationException;
import org.apache.uima.resource.ResourceCreationSpecifier;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.resource.Resource_ImplBase;
import org.apache.uima.resource.metadata.ConfigurationGroup;
import org.apache.uima.resource.metadata.ConfigurationParameter;
import org.apache.uima.resource.metadata.ConfigurationParameterDeclarations;
import org.apache.uima.resource.metadata.ResourceManagerConfiguration;
import org.apache.uima.resource.metadata.ResourceMetaData;
import org.apache.uima.resource.metadata.impl.MetaDataObject_impl;
import org.apache.uima.resource.metadata.impl.PropertyXmlInfo;
import org.apache.uima.resource.metadata.impl.XmlizationInfo;

/**
 * Reference implementation of {@link ResourceCreationSpecifier}.
 * 
 */
public class ResourceCreationSpecifier_impl extends MetaDataObject_impl
        implements ResourceCreationSpecifier {

  private String mImplementationName;

  private String mFrameworkImplementation;

  private ResourceMetaData mMetaData;

  private List<ExternalResourceDependency> mExternalResourceDependencies = new ArrayList<>();

  private ResourceManagerConfiguration mResourceManagerConfiguration;

  static final long serialVersionUID = 7946890459654653436L;

  /**
   * @see ResourceCreationSpecifier#getFrameworkImplementation()
   */
  @Override
  public String getFrameworkImplementation() {
    return mFrameworkImplementation;
  }

  /**
   * @see ResourceCreationSpecifier#setFrameworkImplementation(java.lang.String)
   */
  @Override
  public void setFrameworkImplementation(String aFrameworkImplementation) {
    mFrameworkImplementation = aFrameworkImplementation;
  }

  /**
   * @see ResourceCreationSpecifier#getImplementationName()
   */
  @Override
  public String getImplementationName() {
    return mImplementationName;
  }

  /**
   * @see ResourceCreationSpecifier#setImplementationName(java.lang.String)
   */
  @Override
  public void setImplementationName(String aImplementationName) {
    mImplementationName = aImplementationName;

  }

  /**
   * @see ResourceCreationSpecifier#getMetaData()
   */
  @Override
  public ResourceMetaData getMetaData() {
    return mMetaData;
  }

  /**
   * Sets the MetaData for this <code>ResourceCreationSpecifier_impl</code>. Users should not do
   * this, so this method is not published through the <code>ResourceCreationSpecifier</code>
   * interface.
   * 
   * @param aMetaData
   *          metadata to assign
   */
  @Override
  public void setMetaData(ResourceMetaData aMetaData) {
    mMetaData = aMetaData;
  }

  /**
   * @see org.apache.uima.analysis_engine.AnalysisEngineDescription#getExternalResourceDependencies()
   */
  @Override
  public ExternalResourceDependency[] getExternalResourceDependencies() {
    ExternalResourceDependency[] result = new ExternalResourceDependency[mExternalResourceDependencies
            .size()];
    mExternalResourceDependencies.toArray(result);
    return result;
  }

  /**
   * @see org.apache.uima.analysis_engine.AnalysisEngineDescription#setExternalResourceDependencies(ExternalResourceDependency[])
   */
  @Override
  public void setExternalResourceDependencies(ExternalResourceDependency[] aDependencies) {
    // can't just clear the ArrayList since that breaks clone(). Create a new list.
    mExternalResourceDependencies = new ArrayList<>();
    if (aDependencies != null) {
      for (int i = 0; i < aDependencies.length; i++) {
        mExternalResourceDependencies.add(aDependencies[i]);
      }
    }
  }

  /**
   * @see org.apache.uima.analysis_engine.AnalysisEngineDescription#getExternalResourceDependency(java.lang.String)
   */
  @Override
  public ExternalResourceDependency getExternalResourceDependency(String aKey) {
    for (ExternalResourceDependency dep : mExternalResourceDependencies) {
      if (aKey.equals(dep.getKey()))
        return dep;
    }
    return null;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceCreationSpecifier#getResourceManagerConfiguration()
   */
  @Override
  public ResourceManagerConfiguration getResourceManagerConfiguration() {
    return mResourceManagerConfiguration;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.apache.uima.resource.ResourceCreationSpecifier#setResourceManagerConfiguration(org.apache.
   * uima.resource.metadata.ResourceManagerConfiguration)
   */
  @Override
  public void setResourceManagerConfiguration(
          ResourceManagerConfiguration aResourceManagerConfiguration) {
    mResourceManagerConfiguration = aResourceManagerConfiguration;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceCreationSpecifier#doFullValidation()
   */
  @Override
  public void doFullValidation() throws ResourceInitializationException {
    doFullValidation(UIMAFramework.newDefaultResourceManager());
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.apache.uima.resource.ResourceCreationSpecifier#doFullValidation(org.apache.uima.resource.
   * ResourceManager)
   */
  @Override
  public void doFullValidation(ResourceManager aResourceManager)
          throws ResourceInitializationException {
    // try to instantiate dummy resource - this checks config params
    // and resources
    DummyResource dummy = new DummyResource();
    Map<String, Object> params = new HashMap<>();
    params.put(Resource.PARAM_RESOURCE_MANAGER, aResourceManager);
    dummy.initialize(this, params);

    // subclasses should override to check CAS creation and
    // loading of the user-supplied CAS as appropriate
  }

  /**
   * Determines if the AnalysisEngineDescription is valid. An exception is thrown if it is not
   * valid. This should be called from this Analysis Engine's initialize method. Note this does not
   * check configuration parameter settings - that must be done by an explicit call to
   * validateConfigurationParameterSettings.
   * 
   * @throws ResourceInitializationException
   *           if <code>aDesc</code> is invalid
   * @throws ResourceConfigurationException
   *           if the configuration parameter settings in <code>aDesc</code> are invalid
   */
  @Override
  public final void validate()
          throws ResourceInitializationException, ResourceConfigurationException {
    validate(UIMAFramework.newDefaultResourceManager());
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceCreationSpecifier#validate(org.apache.uima.resource.
   * ResourceManager)
   */
  @Override
  public void validate(ResourceManager aResourceManager)
          throws ResourceInitializationException, ResourceConfigurationException {
    // Validate configuration parameter declarations (but not settings)
    validateConfigurationParameters(aResourceManager);
  }

  /**
   * Validates configuration parameters within this Resource, and throws an exception if they are
   * not valid.
   * <p>
   * This method checks to make sure that there are no duplicate configuration group names or
   * duplicate parameter names within groups. For aggregates, it also checks that parameter
   * overrides are valid, and logs a warning for parameters with no overrides. (For primitives,
   * there should be no overrides.)
   * 
   * @param aResourceManager
   *          used to resolve import by name. This is necessary to validate configuration parameter
   *          overrides.
   * 
   * @throws ResourceInitializationException
   *           if the configuration parameters are invalid
   */
  protected void validateConfigurationParameters(ResourceManager aResourceManager)
          throws ResourceInitializationException {
    ConfigurationParameterDeclarations cfgParamDecls = getMetaData()
            .getConfigurationParameterDeclarations();
    ConfigurationParameter[] params = cfgParamDecls.getConfigurationParameters();
    if (params.length > 0) {
      // check for duplicate names
      checkForDuplicateParameterNames(params);
      checkForInvalidParameterOverrides(params, null, aResourceManager);
    } else {
      ConfigurationParameter[] commonParams = cfgParamDecls.getCommonParameters();
      // check for duplicates in common params
      Set<String> commonParamNames = new HashSet<>();
      if (commonParams != null) {
        for (int i = 0; i < commonParams.length; i++) {
          if (!commonParamNames.add(commonParams[i].getName())) {
            throw new ResourceInitializationException(
                    ResourceInitializationException.DUPLICATE_CONFIGURATION_PARAMETER_NAME,
                    new Object[] { commonParams[i].getName(), getMetaData().getName(),
                        commonParams[i].getSourceUrlString() });
          }
        }
      }
      // check for duplicates in groups
      ConfigurationGroup[] groups = cfgParamDecls.getConfigurationGroups();
      if (groups != null) {
        Map<String, Set<String>> groupToParamSetMap = new HashMap<>(); // map from group name to
                                                                       // HashSet of param names
        // in that group
        for (int i = 0; i < groups.length; i++) {
          String[] names = groups[i].getNames();
          for (int j = 0; j < names.length; j++) {
            Set<String> paramNamesInGroup = groupToParamSetMap.get(names[j]);
            if (paramNamesInGroup == null) {
              // first time we've seen this group. create an entry and add common params
              paramNamesInGroup = new HashSet<>(commonParamNames);
            }

            // check for duplicate parameter names
            ConfigurationParameter[] paramsInGroup = groups[i].getConfigurationParameters();
            if (paramsInGroup != null) {
              for (int k = 0; k < paramsInGroup.length; k++) {
                if (!paramNamesInGroup.add(paramsInGroup[k].getName())) {
                  throw new ResourceInitializationException(
                          ResourceInitializationException.DUPLICATE_CONFIGURATION_PARAMETER_NAME,
                          new Object[] { paramsInGroup[k].getName(), getMetaData().getName(),
                              paramsInGroup[k].getSourceUrlString() });
                }
              }
            }
            checkForInvalidParameterOverrides(paramsInGroup, names[j], aResourceManager);
            if (commonParams != null) {
              checkForInvalidParameterOverrides(commonParams, names[j], aResourceManager);
            }
          }
        }
      }
    }
  }

  /**
   * Checks for duplicate parameter names and throws an exception if any are found.
   * 
   * @param aParams
   *          an array of ConfigurationParameters
   * 
   * @throws ResourceInitializationException
   *           if there is a duplicate parameter name in the arrays
   */
  protected void checkForDuplicateParameterNames(ConfigurationParameter[] aParams)
          throws ResourceInitializationException {
    Set<String> paramNames = new HashSet<>();
    for (int i = 0; i < aParams.length; i++) {
      if (!paramNames.add(aParams[i].getName())) {
        throw new ResourceInitializationException(
                ResourceInitializationException.DUPLICATE_CONFIGURATION_PARAMETER_NAME,
                new Object[] { aParams[i].getName(), getMetaData().getName(),
                    aParams[i].getSourceUrlString() });
      }
    }
  }

  /**
   * Checks parameter overrides and throws a ResourceInitializationException if they are invalid.
   * Note that since overrides are invalid in primitive components, this default implementation
   * throws an exception for ANY parameter override it finds. AnalysisEngineDescription_impl
   * overrides this method to correctly handle parameter overrides in aggregates.
   * 
   * @param aParams
   *          an array of ConfigurationParameters
   * @param aGroupName
   *          name of groups in which these parameters are contained. Null if no group
   * @param aResourceManager
   *          used to resolve imports by name.
   * 
   * @throws ResourceInitializationException
   *           if there is an invalid parameter override declaration
   */
  protected void checkForInvalidParameterOverrides(ConfigurationParameter[] aParams,
          String aGroupName, ResourceManager aResourceManager)
          throws ResourceInitializationException {
    for (int i = 0; i < aParams.length; i++) {
      String[] overrides = aParams[i].getOverrides();
      if (overrides.length > 0) {
        throw new ResourceInitializationException(
                ResourceInitializationException.PARAM_OVERRIDE_IN_PRIMITIVE,
                new Object[] { aParams[i].getName(), getMetaData().getName(),
                    aParams[i].getSourceUrlString() });
      }
    }
  }

  @Override
  protected XmlizationInfo getXmlizationInfo() {
    return XMLIZATION_INFO;
  }

  static final private XmlizationInfo XMLIZATION_INFO = new XmlizationInfo(
          "resourceCreationSpecifier",
          new PropertyXmlInfo[] { new PropertyXmlInfo("frameworkImplementation"),
              new PropertyXmlInfo("implementationName"), new PropertyXmlInfo("metaData", null),
              new PropertyXmlInfo("externalResourceDependencies"),
              new PropertyXmlInfo("externalResources"),
              new PropertyXmlInfo("resourceManagerConfiguration", null), });

  // used by doFullValidation
  private static class DummyResource extends Resource_ImplBase {
  }
}
