/**
 * Copyright 2011-2021 Asakusa Framework Team.
 *
 * 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
 *
 *     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 com.asakusafw.windgate.core;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The parameter list.
 * @since 0.2.2
 * @version 0.9.0
 */
public class ParameterList {

    static final WindGateLogger WGLOG = new WindGateCoreLogger(ParameterList.class);

    static final Logger LOG = LoggerFactory.getLogger(ParameterList.class);

    private static final Pattern VARIABLE = Pattern.compile("\\$\\{(.*?)\\}");

    private static final char SEPARATOR_DEFAULT_VALUE = '-';

    private final Map<String, String> parameters;

    /**
     * Creates a new empty instance.
     * @throws IllegalArgumentException if any parameter is {@code null}
     */
    public ParameterList() {
        this(Collections.emptyMap());
    }

    /**
     * Creates a new instance.
     * @param parameters the key value pairs
     * @throws IllegalArgumentException if any parameter is {@code null}
     */
    public ParameterList(Map<String, String> parameters) {
        if (parameters == null) {
            throw new IllegalArgumentException("parameters must not be null"); //$NON-NLS-1$
        }
        this.parameters = Collections.unmodifiableMap(new TreeMap<>(parameters));
    }


    /**
     * Returns parameters as key value pairs.
     * @return the parameters
     */
    public Map<String, String> getPairs() {
        return parameters;
    }

    /**
     * Replaces parameters in the target string.
     * The parameters are represented as <code>${variable-name}</code>.
     * @param string target string
     * @param strict {@code true} to keep undefined parameters,
     *     or {@code false} to raise an exception
     * @return replaced string
     * @throws IllegalArgumentException if undefined parameters exist on strict mode,
     *     or any parameters contain {@code null}
     */
    public String replace(String string, boolean strict) {
        if (string == null) {
            throw new IllegalArgumentException("string must not be null"); //$NON-NLS-1$
        }
        StringBuilder buf = new StringBuilder();
        int start = 0;
        Matcher matcher = VARIABLE.matcher(string);
        while (matcher.find(start)) {
            String name = matcher.group(1);
            String replacement = resolve(name);
            if (replacement == null) {
                if (strict) {
                    WGLOG.error("E99001",
                            name);
                    throw new IllegalArgumentException(MessageFormat.format(
                            "parameter \"{0}\" is not defined in the list: {1}",
                            name,
                            this));
                } else {
                    buf.append(string.substring(start, matcher.start() + 1));
                }
                start = matcher.start() + 1;
            } else {
                buf.append(string.substring(start, matcher.start()));
                buf.append(replacement);
                start = matcher.end();
            }
        }
        buf.append(string.substring(start));
        return buf.toString();
    }

    private String resolve(String placeholder) {
        String name;
        String defaultValue;
        int defaultAt = placeholder.indexOf(SEPARATOR_DEFAULT_VALUE);
        if (defaultAt < 0) {
            name = placeholder;
            defaultValue = null;
        } else {
            name = placeholder.substring(0, defaultAt);
            defaultValue = placeholder.substring(defaultAt + 1);
        }
        String replacement = parameters.get(name);
        if (replacement != null) {
            return replacement;
        }
        return defaultValue;
    }

    @Override
    public String toString() {
        return parameters.toString();
    }
}
