/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nifi.processors.standard.relp.response;

import org.apache.nifi.processors.standard.relp.frame.RELPFrame;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * The data portion of a RELPFrame for a response:
 *
 * RSP-CODE [SP HUMANMSG] LF [CMDDATA]
 *
 */
public class RELPResponse {

    public static final int OK = 200;
    public static final int ERROR = 500;

    public static final String RSP_CMD = "rsp";

    private final long txnr;
    private final int code;
    private final String message;
    private final String data;

    public RELPResponse(final long txnr, final int code) {
        this(txnr, code, null, null);
    }

    public RELPResponse(final long txnr, final int code, final String message, final String data) {
        this.txnr = txnr;
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * Creates a RELPFrame where the data portion will contain this response.
     *
     * @param charset the character set to encode the response
     *
     * @return a RELPFrame for for this response
     */
    public RELPFrame toFrame(final Charset charset) {
        final StringBuilder builder = new StringBuilder();
        builder.append(code);

        if (message != null && !message.isEmpty()) {
            builder.append((char) RELPFrame.SEPARATOR);
            builder.append(message);
        }

        if (data != null) {
            builder.append((char) RELPFrame.DELIMITER);
            builder.append(data);
        }

        final byte[] data = builder.toString().getBytes(charset);

        return new RELPFrame.Builder()
                .txnr(txnr).command(RSP_CMD)
                .dataLength(data.length).data(data)
                .build();
    }

    /**
     * Utility method to create a response to an open request.
     *
     * @param txnr the transaction number of the open request
     * @param offers the accepted offers
     *
     * @return the RELPResponse for the given open request
     */
    public static RELPResponse open(final long txnr, final Map<String, String> offers) {
        int i = 0;
        final StringBuilder sb = new StringBuilder();
        for (final Map.Entry<String, String> entry : offers.entrySet()) {
            if (i > 0) {
                sb.append((char) RELPFrame.DELIMITER);
            }

            sb.append(entry.getKey());

            if (entry.getValue() != null) {
                sb.append('=');
                sb.append(entry.getValue());
            }
            i++;
        }

        return new RELPResponse(txnr, OK, "OK", sb.toString());
    }

    /**
     * Utility method to create a default "OK" response.
     *
     * @param txnr the transaction number being responded to
     *
     * @return a RELPResponse with a 200 code and a message of "OK"
     */
    public static RELPResponse ok(final long txnr) {
        return new RELPResponse(txnr, OK, "OK", null);
    }

    /**
     * Utility method to create a default "ERROR" response.
     *
     * @param txnr the transaction number being responded to
     *
     * @return a RELPResponse with a 500 code and a message of "ERROR"
     */
    public static RELPResponse error(final long txnr) {
        return error(txnr, "ERROR");
    }

    /**
     * Utility method to create a default "ERROR" response if the server event queue is full.
     *
     * @param txnr the transaction number being responded to
     *
     * @return a RELPResponse with a 500 code and a message of "SERVER FULL"
     */
    public static RELPResponse serverFullError(final long txnr) {
        return error(txnr, "SERVER FULL");
    }

    private static RELPResponse error(final long txnr, final String message) {
        return new RELPResponse(txnr, ERROR, message, null);
    }

    /**
     * Parses the provided data into a Map of offers.
     *
     * @param data the data portion of a RELPFrame for an "open" command
     * @param charset the charset to decode the data
     *
     * @return a Map of offers, or an empty Map if no data is provided
     */
    public static Map<String, String> parseOffers(final byte[] data, final Charset charset) {
        final Map<String, String> offers = new HashMap<>();
        if (data == null || data.length == 0) {
            return offers;
        }

        final String dataStr = new String(data, charset);
        final String[] splits = dataStr.split("[" + (char) RELPFrame.DELIMITER + "]");

        for (final String split : splits) {
            final String[] fields = split.split( "=", 2);
            if (fields.length > 1 ) {
                offers.put(fields[0], fields[1]);
            } else {
                offers.put(fields[0], fields[0]);
            }
        }

        return offers;
    }
}
