/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2025, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 *
 *
 */
package net.sourceforge.plantuml.cli;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import net.sourceforge.plantuml.utils.Peeker;
import net.sourceforge.plantuml.utils.PeekerUtils;

public class CliParsed {
	
	
	private final Map<CliFlag, List<Object>> lists = new EnumMap<>(CliFlag.class);
	private final Map<CliFlag, Map<String, String>> maps = new EnumMap<>(CliFlag.class);
	private final List<String> args = new ArrayList<>();

	public void putValue(CliFlag flag, Object value) {
		final List<Object> list = lists.computeIfAbsent(flag, k -> new ArrayList<>());
		list.add(value);
	}

	private void putMapValue(CliFlag flag, String key, String value) {
		final Map<String, String> map = maps.computeIfAbsent(flag, k -> new LinkedHashMap<>());
		map.put(key, value);
	}

	public boolean isTrue(CliFlag flag) {
		if (maps.containsKey(flag))
			return true;

		final List<Object> list = lists.get(flag);

		// Useless: cannot be Boolean.FALSE
		if (list != null && list.size() > 0 && list.get(0) instanceof Boolean)
			return (Boolean) list.get(0);

		return list != null;
	}

	public String getString(CliFlag flag) {
		final List<Object> list = lists.get(flag);
		if (list != null && list.size() > 0)
			return (String) list.get(0);

		return flag.getDefaultValue();
	}

	public Object getObject(CliFlag flag) {
		final List<Object> list = lists.get(flag);
		if (list != null)
			return list.get(0);

		return flag.getDefaultValue();
	}

	public Object getFromType(Class<?> clazz) {
		for (List<Object> list : lists.values())
			if (list.size() == 1) {
				final Object element = list.get(0);
				if (element.getClass() == clazz)
					return element;
			}

		return null;
	}

	public Map<String, String> getMap(CliFlag flag) {
		final Map<String, String> result = maps.get(flag);
		if (result == null)
			return Collections.emptyMap();
		return result;
	}

	public List<Object> getList(CliFlag flag) {
		final List<Object> result = lists.get(flag);
		if (result == null)
			return Collections.emptyList();
		return result;
	}

	public Map<String, String> getMap(CliFlag mapFlag, CliFlag listFlag) {
		final Map<String, String> result = new LinkedHashMap<String, String>(getMap(mapFlag));

		for (Object s : getList(listFlag)) {
			final String keyValue[] = s.toString().split("=");
			if (keyValue.length == 1)
				result.put(keyValue[0], null);
			else if (keyValue.length == 2)
				result.put(keyValue[0], keyValue[1]);
		}

		return Collections.unmodifiableMap(result);

	}

	@Override
	public String toString() {
		final Map<CliFlag, Object> merge = new EnumMap<>(CliFlag.class);
		merge.putAll(lists);
		merge.putAll(maps);
		return merge.toString();
	}

	public CliAction getImmediateAction() {
		for (Entry<CliFlag, List<Object>> entry : lists.entrySet())
			if (entry.getKey().getType() == Arity.UNARY_IMMEDIATE_ACTION && entry.getValue().size() == 1) {
				final Object data = entry.getValue().get(0);
				if (data instanceof CliAction)
					return (CliAction) data;
			}
		return null;
	}

	public void triggerNonImmediateCliAction() {
		for (Entry<CliFlag, List<Object>> entry : lists.entrySet())
			if (entry.getKey().getType() != Arity.UNARY_IMMEDIATE_ACTION && entry.getValue().size() == 1) {
				final Object data = entry.getValue().get(0);
				if (data instanceof CliAction)
					((CliAction) data).runAction();

			}

	}

	public static CliParsed parse(String... args) throws CliParsingException {
		final CliParsed result = new CliParsed();
		for (final Peeker<String> peeker = PeekerUtils.peeker(Arrays.asList(args)); peeker.peek(0) != null; peeker
				.jump()) {

			final String peek0 = peeker.peek(0);
			boolean found = false;
			for (CliFlag flag : CliFlag.values()) {
				if (found || flag.match(peek0) == false)
					continue;

				found = true;
				switch (flag.getType()) {

				case UNARY_BOOLEAN:
					final Object foo = flag.getFoo();
					result.putValue(flag, foo == null ? Boolean.TRUE : foo);
					break;

				case UNARY_IMMEDIATE_ACTION:
					result.putValue(flag, flag.getFoo());
					break;

				case BINARY_NEXT_ARGUMENT_VALUE:
					peeker.jump();
					result.putValue(flag, peeker.peek(0));
					break;

				case UNARY_INLINE_KEY_OR_KEY_VALUE:
					String s = peeker.peek(0).substring(2);
					if (s.length() == 0) {
						peeker.jump();
						s = peeker.peek(0);
					}

					if (s == null)
						throw new CliParsingException(flag, "missing value");

					final int eqIndex = s.indexOf('=');
					final String k = eqIndex < 0 ? s : s.substring(0, eqIndex);
					final String v = eqIndex < 0 ? null : s.substring(eqIndex + 1);
					result.putMapValue(flag, k, v);
					break;

				case UNARY_OPTIONAL_COLON:
					final int colon = peek0.indexOf(':');
					if (colon == -1) {
						result.lists.computeIfAbsent(flag, k1 -> new ArrayList<>());
					} else
						for (String part : peek0.substring(colon + 1).split(":"))
							result.putValue(flag, part);
					break;
				}
			}
			if (found == false)
				result.args.add(peek0);

		}

		return result;

	}

	public List<String> getRemainingArgs() {
		return Collections.unmodifiableList(args);
	}

}
