/*
 * 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.fit.internal;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.uima.resource.ResourceInitializationException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

/**
 * INTERNAL API - Utility methods to locate and access uimaFIT meta data.
 */
public final class MetaDataUtil {

  private MetaDataUtil() {
    // No instances
  }

  /**
   * Scan patterns from manifest files and from the specified system property.
   *
   * @param aType
   *          the type of metadata to scan for
   * @return array or all patterns found.
   * @throws ResourceInitializationException
   *           if there was a problem resolving the metadata locations from the patterns
   */
  public static String[] scanImportsAndManifests(MetaDataType aType)
          throws ResourceInitializationException {
    ArrayList<String> patterns = new ArrayList<>();

    // Scan auto-import locations
    for (String property : getImportProperties(aType)) {
      patterns.addAll(Arrays.asList(System.getProperty(property, "").split(";")));
    }

    // Scan manifest
    for (String mfUrl : resolve(getManifestLocations(aType))) {
      InputStream is = null;
      try {
        is = new URL(mfUrl).openStream();
        List<? extends String> lines = IOUtils.readLines(is);
        patterns.addAll(lines);
      } catch (IOException e) {
        throw new ResourceInitializationException(e);
      } finally {
        IOUtils.closeQuietly(is);
      }
    }

    return patterns.toArray(new String[patterns.size()]);
  }

  /**
   * Resolve a list of patterns to a set of URLs.
   *
   * @param patterns
   *          the patterns to resolve
   * @return an array of locations.
   * @throws ResourceInitializationException
   *           if the locations could not be resolved.
   */
  public static String[] resolve(String... patterns) throws ResourceInitializationException {
    Set<String> locations = new HashSet<>();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    try {
      // Scan auto-import locations. Using a set to avoid scanning a pattern twice.
      for (String pattern : new TreeSet<>(Arrays.asList(patterns))) {
        String p = pattern.trim();
        if (p.length() == 0) {
          continue;
        }
        for (Resource r : resolver.getResources(pattern)) {
          locations.add(r.getURL().toString());
        }
      }
      return locations.toArray(new String[locations.size()]);
    } catch (IOException e) {
      throw new ResourceInitializationException(e);
    }
  }

  /**
   * Get manifest locations for the specified type.
   *
   * @param aType
   *          the type of metadata to scan for
   * @return the manifest locations for this kind of metadata to scan
   */
  public static String[] getManifestLocations(MetaDataType aType) {
    List<String> locations = new ArrayList<>();
    switch (aType) {
      case FS_INDEX:
        locations.add("classpath*:META-INF/org.apache.uima.fit/fsindexes.txt");
        break;
      case TYPE_SYSTEM:
        locations.add("classpath*:META-INF/org.apache.uima.fit/types.txt");
        break;
      case TYPE_PRIORITIES:
        locations.add("classpath*:META-INF/org.apache.uima.fit/typepriorities.txt");
        break;
    }

    return locations.toArray(new String[locations.size()]);
  }

  /**
   * Get system properties indicating which locations to scan for descriptions of the given type. A
   * list of locations may be given separated by ";".
   *
   * @param aType
   *          the type of metadata to scan for
   * @return the locations for this kind of metadata to scan
   */
  public static String[] getImportProperties(MetaDataType aType) {
    List<String> locations = new ArrayList<>();
    switch (aType) {
      case FS_INDEX:
        locations.add("org.apache.uima.fit.fsindex.import_pattern");
        break;
      case TYPE_SYSTEM:
        locations.add("org.apache.uima.fit.type.import_pattern");
        break;
      case TYPE_PRIORITIES:
        locations.add("org.apache.uima.fit.typepriorities.import_pattern");
        break;
    }

    return locations.toArray(new String[locations.size()]);
  }

  /**
   * Get all currently accessible descriptor locations for the given type.
   *
   * @param aType
   *          the type of metadata to scan for
   * @return an array of locations.
   * @throws ResourceInitializationException
   *           if the locations could not be resolved.
   */
  public static String[] scanDescriptors(MetaDataType aType)
          throws ResourceInitializationException {
    String[] locations1 = resolve(scanImportsAndManifests(aType));
    String[] locations2 = LegacySupport.getInstance().scanTypeDescriptors(aType);
    return (String[]) ArrayUtils.addAll(locations1, locations2);
  }
}
