/*
 * 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.flink.table.module;

import org.apache.flink.table.api.ValidationException;
import org.apache.flink.table.functions.FunctionDefinition;
import org.apache.flink.util.StringUtils;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.apache.flink.table.descriptors.CoreModuleDescriptorValidator.MODULE_TYPE_CORE;
import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;

/**
 * Responsible for loading/unloading modules, managing their life cycles, and resolving module objects.
 */
public class ModuleManager {
	private LinkedHashMap<String, Module> modules;

	public ModuleManager() {
		this.modules = new LinkedHashMap<>();

		modules.put(MODULE_TYPE_CORE, CoreModule.INSTANCE);
	}

	/**
	 * Load a module under a unique name. Modules will be kept in the loaded order, and new module
	 * will be added to the end.
	 * ValidationException is thrown when there is already a module with the same name.
	 *
	 * @param name name of the module
	 * @param module the module instance
	 */
	public void loadModule(String name, Module module) {
		checkArgument(!StringUtils.isNullOrWhitespaceOnly(name), "name cannot be null or empty string");
		checkNotNull(module, "module cannot be null");

		if (!modules.containsKey(name)) {
			modules.put(name, module);
		} else {
			throw new ValidationException(
				String.format("A module with name %s already exists", name));
		}
	}

	/**
	 * Unload a module with given name.
	 * ValidationException is thrown when there is no module with the given name.
	 *
	 * @param name name of the module
	 */
	public void unloadModule(String name) {
		if (modules.containsKey(name)) {
			modules.remove(name);
		} else {
			throw new ValidationException(
				String.format("No module with name %s exists", name));
		}
	}

	/**
	 * Get names of all modules loaded.
	 *
	 * @return a list of names of modules loaded
	 */
	public List<String> listModules() {
		return new ArrayList<>(modules.keySet());
	}

	/**
	 * Get names of all functions from all modules.
	 *
	 * @return a set of names of registered modules.
	 */
	public Set<String> listFunctions() {
		return modules.values().stream()
				.map(m -> m.listFunctions())
				.flatMap(n -> n.stream())
				.collect(Collectors.toSet());
	}

	/**
	 * Get an optional of {@link FunctionDefinition} by a given name.
	 * Function will be resolved to modules in the loaded order, and the first match will be returned.
	 * If no match is found in all modules, return an optional.
	 *
	 * @param name name of the function
	 * @return an optional of {@link FunctionDefinition}
	 */
	public Optional<FunctionDefinition> getFunctionDefinition(String name) {
		Optional<Module> module = modules.values().stream()
			.filter(p -> p.listFunctions().stream().anyMatch(e -> e.equalsIgnoreCase(name)))
			.findFirst();

		return module.isPresent() ? module.get().getFunctionDefinition(name) : Optional.empty();
	}

}

@@ -994,24 +994,82 @@
 ringUtils;%0A%0A
+import org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;%0A%0A
 import java.
@@ -1135,24 +1135,46 @@
 .util.List;%0A
+import java.util.Map;%0A
 import java.
@@ -1615,16 +1615,99 @@
 nager %7B%0A
+%0A%09private static final Logger LOG = LoggerFactory.getLogger(ModuleManager.class);%0A%0A
 %09private
@@ -2438,16 +2438,99 @@
 module);
+%0A%0A%09%09%09LOG.info(%22Loaded module %7B%7D from class %7B%7D%22, name, module.getClass().getName());
 %0A%09%09%7D els
@@ -2906,16 +2906,58 @@
 e(name);
+%0A%0A%09%09%09LOG.info(%22Unloaded module %7B%7D%22, name);
 %0A%09%09%7D els
@@ -3948,30 +3948,49 @@
 ptional%3C
+Map.Entry%3CString, 
 Module%3E
- module
+%3E result
  = modul
@@ -3984,38 +3984,40 @@
 esult = modules.
-values
+entrySet
 ().stream()%0A%09%09%09.
@@ -4030,16 +4030,27 @@
 (p -%3E p.
+getValue().
 listFunc
@@ -4127,29 +4127,26 @@
 t();%0A%0A%09%09
-return module
+if (result
 .isPrese
@@ -4153,53 +4153,263 @@
 nt()
- ? module.get().getFunctionDefinition(name) :
+) %7B%0A%09%09%09LOG.info(%22Got FunctionDefinition %7B%7D from module %7B%7D%22, name, result.get().getKey());%0A%0A%09%09%09return result.get().getValue().getFunctionDefinition(name);%0A%09%09%7D else %7B%0A%09%09%09LOG.info(%22Cannot find FunctionDefinition %7B%7D from any loaded modules%22, name);%0A%0A%09%09%09return
  Opt
@@ -4423,14 +4423,18 @@
 mpty();%0A
+%09%09%7D%0A
 %09%7D%0A%0A%7D%0A

add logging for loaded modules and functions