/*
 * Copyright 2013-2022 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
 *
 *      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 net.logstash.logback.marker;

import java.io.IOException;
import java.util.Objects;

import net.logstash.logback.argument.StructuredArgument;
import net.logstash.logback.argument.StructuredArguments;
import net.logstash.logback.composite.loggingevent.ArgumentsJsonProvider;
import net.logstash.logback.composite.loggingevent.LogstashMarkersJsonProvider;

import com.fasterxml.jackson.core.JsonGenerator;
import org.slf4j.Marker;

/**
 * A {@link Marker} OR {@link StructuredArgument} that appends
 * a single field into the JSON event.
 * <p>
 *
 * When writing to the JSON data (via {@link ArgumentsJsonProvider} or {@link LogstashMarkersJsonProvider}):
 * <ul>
 * <li>Field names are written via {@link #writeFieldName(JsonGenerator)},
 *     which just uses {@link #fieldName} is used as the field name</li>
 * <li>Values are written via {@link #writeFieldValue(JsonGenerator)},
 *     which subclasses must override</li>
 * </ul>
 * <p>
 *
 * When writing to a String (when used as a {@link StructuredArgument} to the event's formatted message),
 * the {@link #messageFormatPattern} is used to construct the string output.
 * {@link #getFieldName()} will be substituted in {0} in the {@link #messageFormatPattern}.
 * {@link #getFieldValue()} will be substituted in {1} in the {@link #messageFormatPattern}.
 * Subclasses must override {@link #getFieldValue()} to provide the field value to include.
 */
@SuppressWarnings("serial")
public abstract class SingleFieldAppendingMarker extends LogstashMarker implements StructuredArgument {

    public static final String MARKER_NAME_PREFIX = LogstashMarker.MARKER_NAME_PREFIX + "APPEND_";

    /**
     * Name of the field to append.
     *
     * Note that the value of the field is provided by subclasses via {@link #writeFieldValue(JsonGenerator)}.
     */
    private final String fieldName;

    /**
     * Pattern to use when appending the field/value in {@link #toString()}.
     * <p>
     * {@link #getFieldName()} will be substituted in {0}.
     * {@link #getFieldValue()} will be substituted in {1}.
     */
    private final String messageFormatPattern;

    public SingleFieldAppendingMarker(String markerName, String fieldName) {
        this(markerName, fieldName, StructuredArguments.DEFAULT_KEY_VALUE_MESSAGE_FORMAT_PATTERN);
    }

    public SingleFieldAppendingMarker(String markerName, String fieldName, String messageFormatPattern) {
        super(markerName);
        this.fieldName = Objects.requireNonNull(fieldName, "fieldName must not be null");
        this.messageFormatPattern = Objects.requireNonNull(messageFormatPattern, "messageFormatPattern must not be null");
    }

    public String getFieldName() {
        return fieldName;
    }

    public void writeTo(JsonGenerator generator) throws IOException {
        writeFieldName(generator);
        writeFieldValue(generator);
    }

    /**
     * Writes the field name to the generator.
     * 
     * @param generator the generator to write JSON
     * @throws IOException if an I/O error occurs
     */
    protected void writeFieldName(JsonGenerator generator) throws IOException {
        generator.writeFieldName(getFieldName());
    }

    /**
     * Writes the field value to the generator.
     * 
     * @param generator the generator to write JSON
     * @throws IOException if an I/O error occurs
     */
    protected abstract void writeFieldValue(JsonGenerator generator) throws IOException;

    @Override
    public String toStringSelf() {
        final String fieldValueString = StructuredArguments.toString(getFieldValue());
        /*
         * Optimize for commonly used messageFormatPattern
         */
        if (StructuredArguments.VALUE_ONLY_MESSAGE_FORMAT_PATTERN.equals(messageFormatPattern)) {
            return fieldValueString;
        }
        if (StructuredArguments.DEFAULT_KEY_VALUE_MESSAGE_FORMAT_PATTERN.equals(messageFormatPattern)) {
            return getFieldName()
                    + "="
                    + fieldValueString;
        }
        /*
         * Custom messageFormatPattern
         */
        return MessageFormatCache.INSTANCE.getMessageFormat(this.messageFormatPattern)
                .format(new Object[] {getFieldName(), fieldValueString});
    }

    /**
     * Return the value that should be included in the output of {@link #toString()}.
     * 
     * @return the field value
     */
    protected abstract Object getFieldValue();

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (!(obj instanceof SingleFieldAppendingMarker)) {
            return false;
        }

        SingleFieldAppendingMarker other = (SingleFieldAppendingMarker) obj;
        return this.fieldName.equals(other.fieldName);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + super.hashCode();
        result = prime * result + this.fieldName.hashCode();
        return result;
    }
}
