/*
 * Copyright 2017-2020 original 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 io.micronaut.context;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ObjectUtils;

import java.util.Locale;

/**
 * Abstract {@link MessageSource} implementation that provides basic message interpolation.
 *
 * @author graemerocher
 * @since 1.2
 */
public abstract class AbstractMessageSource implements MessageSource {

    private static final char QUOT = '\'';
    private static final char L_BRACE = '{';
    private static final char R_BRACE = '}';

    @NonNull
    @Override
    public String interpolate(@NonNull String template, @NonNull MessageContext context) {
        ArgumentUtils.requireNonNull("template", template);
        ArgumentUtils.requireNonNull("context", context);

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < template.length(); i++) {
            char c = template.charAt(i);
            if (c == QUOT) {
                int next = i + 1;
                if (next < template.length()) {
                    c = template.charAt(next);
                    if (c == QUOT) {
                        i++;
                        builder.append(QUOT);
                    } else {
                        StringBuilder escaped = new StringBuilder();
                        while (c != QUOT) {
                            escaped.append(c);
                            if (++next < template.length()) {
                                c = template.charAt(next);
                            } else {
                                break;
                            }
                        }
                        if (escaped.length() > 0) {
                            i = next;
                            builder.append(escaped);
                        }
                    }
                }
            } else if (c == L_BRACE) {
                StringBuilder variable = new StringBuilder();
                int next = i + 1;
                if (next < template.length()) {
                    c = template.charAt(next);
                    while (c != R_BRACE) {
                        variable.append(c);
                        if (++next < template.length()) {
                            c = template.charAt(next);
                        } else {
                            break;
                        }
                    }
                    if (variable.length() > 0) {
                        i = next;
                        String var = variable.toString();
                        if (c == R_BRACE) {
                            final Object val = context.getVariables().get(var);
                            if (val != null) {
                                builder.append(val);
                            } else {
                                final String resolved = getMessage(var, context).orElse(var);
                                builder.append(resolved);
                            }
                        } else {
                            builder.append(L_BRACE).append(var);
                        }
                    }
                } else {
                    builder.append(c);
                }
            } else {
                builder.append(c);
            }
        }

        return builder.toString();
    }

    /**
     * Internal key storage.
     */
    protected final class MessageKey {
        final Locale locale;
        final String code;

        /**
         * Default constructor.
         * @param locale The locale
         * @param code The code
         */
        public MessageKey(@NonNull Locale locale, @NonNull String code) {
            this.locale = locale;
            this.code = code;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            MessageKey key = (MessageKey) o;
            return locale.equals(key.locale) &&
                    code.equals(key.code);
        }

        @Override
        public int hashCode() {
            return ObjectUtils.hash(locale, code);
        }
    }
}
