/*
 * Copyright 2013-present the original author or authors.
 *
 * Licensed 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
 *
 *      https://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 com.alibaba.cloud.nacos.parser;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.alibaba.cloud.nacos.utils.StringUtils;
import org.jspecify.annotations.Nullable;

import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

/**
 * Nacos-specific loader, If need to support other methods of parsing,you need to do the
 * following steps:
 * <p>
 * 1.inherit {@link AbstractPropertySourceLoader};<br>
 * 2. define the file{@code spring.factories} and append
 * {@code org.springframework.boot.env.PropertySourceLoader=..};<br>
 * 3.the last step validate.
 * </p>
 * Notice the use of {@link NacosByteArrayResource} .
 *
 * @author zkz
 */
public abstract class AbstractPropertySourceLoader implements PropertySourceLoader {

	/**
	 * symbol: dot.
	 */
	static final String DOT = ".";

	/**
	 * Prevent interference with other loaders.Nacos-specific loader, unless the reload
	 * changes it.
	 * @param name the root name of the property source. If multiple documents are loaded
	 * an additional suffix should be added to the name for each source loaded.
	 * @param resource the resource to load
	 * @return if the resource can be loaded
	 */
	protected boolean canLoad(String name, Resource resource) {
		return resource instanceof NacosByteArrayResource;
	}

	/**
	 * Load the resource into one or more property sources. Implementations may either
	 * return a list containing a single source, or in the case of a multi-document format
	 * such as yaml a source for each document in the resource.
	 * @param name the root name of the property source. If multiple documents are loaded
	 * an additional suffix should be added to the name for each source loaded.
	 * @param resource the resource to load
	 * @return a list property sources
	 * @throws IOException if the source cannot be loaded
	 */
	@Override
	public List<PropertySource<?>> load(String name, Resource resource)
			throws IOException {
		if (!canLoad(name, resource)) {
			return Collections.emptyList();
		}
		return this.doLoad(name, resource);
	}

	/**
	 * Load the resource into one or more property sources. Implementations may either
	 * return a list containing a single source, or in the case of a multi-document format
	 * such as yaml a source for each document in the resource.
	 * @param name the root name of the property source. If multiple documents are loaded
	 * an additional suffix should be added to the name for each source loaded.
	 * @param resource the resource to load
	 * @return a list property sources
	 * @throws IOException if the source cannot be loaded
	 */
	protected abstract List<PropertySource<?>> doLoad(String name, Resource resource)
			throws IOException;

	protected void flattenedMap(Map<String, Object> result, Map<String, Object> dataMap,
			@Nullable String parentKey) {

		Assert.state(org.springframework.util.StringUtils.hasText(parentKey), "parentKey must not be empty");

		if (dataMap == null || dataMap.isEmpty()) {
			return;
		}
		Set<Entry<String, Object>> entries = dataMap.entrySet();
		for (Entry<String, Object> entry : entries) {
			String key = entry.getKey();
			Object value = entry.getValue();

			String fullKey = StringUtils.isEmpty(parentKey) ? key : key.startsWith("[")
					? parentKey.concat(key) : parentKey.concat(DOT).concat(key);

			if (value instanceof Map map) {
				flattenedMap(result, map, fullKey);
				continue;
			}
			else if (value instanceof Collection collection) {
				int count = 0;
				for (Object object : collection) {
					flattenedMap(result,
							Collections.singletonMap("[" + (count++) + "]", object),
							fullKey);
				}
				continue;
			}

			result.put(fullKey, value);
		}
	}

}
