/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */

package com.scytl.products.oscore.logging.core.formatter;

import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.domain.LogEvent;
import com.scytl.products.oscore.logging.api.domain.LogField;
import com.scytl.products.oscore.logging.api.domain.TransactionInfo;
import com.scytl.products.oscore.logging.api.exceptions.LoggingException;
import com.scytl.products.oscore.logging.api.formatter.MessageFormatter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Implementation of {@link MessageFormatter} that builds the log message
 * respecting the splunk format and complying with the Common Criteria.
 */
public class SplunkFormatter implements MessageFormatter {

    private final static char SEPARATOR = '|';

    private static final Pattern pipeEscapedPattern = Pattern.compile("\\|");

    private static final String pipeReplacement = "(p)";

    private final static char ADDITIONAL_VALUES = '"';

    private final static char BLANK = ' ';

    private final static char EQUAL = '=';

    private final static char DEFAULT_VALUE = '-';

    private final String application;

    private final String component;

    private final TransactionInfoProvider transactionInfoProvider;

    private Set<LogField> requiredFields = new HashSet<>();

    public SplunkFormatter(final String application, final String component,
            final TransactionInfoProvider transactionInfoProvider) {
        this.application = application;
        this.component = component;
        this.transactionInfoProvider = transactionInfoProvider;

    }

    public SplunkFormatter(final String application, final String component,
                           final TransactionInfoProvider transactionInfoProvider,
                           final LogField...requiredFields) {
        this.application = application;
        this.component = component;
        this.transactionInfoProvider = transactionInfoProvider;
        if (requiredFields != null ) {
            for(LogField field : requiredFields) {
                if (null != field) {
                    this.requiredFields.add(field);
                }
            }
        }
    }


    /**
     * @see MessageFormatter#buildMessage(LogContent)
     */
    @Override
    public String buildMessage(final LogContent logContent) {

        final StringBuilder message = new StringBuilder();

        final TransactionInfo transactionInfo = transactionInfoProvider.get();

        final LogEvent logEvent = logContent.getLogEvent();
        //let's assume for now these fields are not null
        message.append(transactionInfo.getTrackId());
        message.append(SEPARATOR);
        message.append(escapePipe(transactionInfo.getServerIpAddress()));
        message.append(SEPARATOR);
        message.append(escapePipe(transactionInfo.getClientIpAddress()));
        message.append(SEPARATOR);
        message.append(escapePipe(transactionInfo.getTenant()));
        message.append(SEPARATOR);
        message.append(escapePipe(application));
        message.append(SEPARATOR);
        message.append(escapePipe(component));
        message.append(SEPARATOR);
        message.append(escapePipe(logEvent.getLayer()));
        message.append(SEPARATOR);
        message.append(escapePipe(logEvent.getAction()));
        message.append(SEPARATOR);

        appendOptionalField(LogField.OBJECTTYPE, escapePipe(logContent.getObjectType()), message);
        message.append(SEPARATOR);

        appendOptionalField(LogField.OBJECTID, escapePipe(logContent.getObjectId()), message);
        message.append(SEPARATOR);

        message.append(escapePipe(logEvent.getOutcome()));
        message.append(SEPARATOR);

        appendOptionalField(LogField.USER, escapePipe(logContent.getUser()), message);
        message.append(SEPARATOR);

        appendOptionalField(LogField.ELECTIONEVENT, escapePipe(logContent.getElectionEvent()), message);
        message.append(SEPARATOR);

        message.append(escapePipe(logEvent.getInfo()));
        message.append(SEPARATOR);

        final Map<String, String> additionalInfoMap = logContent.getAdditionalInfo();

        if (additionalInfoMap != null) {
            final Iterator<Entry<String, String>> it = additionalInfoMap.entrySet().iterator();

            while (it.hasNext()) {

                final Entry<String, String> pair = it.next();

                message.append(escapePipe(pair.getKey())).append(EQUAL);
                message.append(ADDITIONAL_VALUES).append(escapePipe(pair.getValue())).append(ADDITIONAL_VALUES);
                message.append(BLANK);
            }
        }

        return message.toString();
    }

    private String escapePipe(String info) {
        if (info != null) {
            return pipeEscapedPattern.matcher(info).replaceAll(pipeReplacement);
        }
        return null;
    }

    private void appendOptionalField(LogField field, String fieldValue, StringBuilder sb) {
        if(requiredFields.contains(field) && fieldValue == null) {
            throw new LoggingException(
                String.format("Field '%s' is defined as required and is missing a value",
                    field.getFieldName()));
        } else if (fieldValue == null) {
            sb.append(String.valueOf(DEFAULT_VALUE));
        } else {
            sb.append(fieldValue);
        }
    }
}
