/* UtilLib_Servlet - Copyright (c) 2010-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.servlets.handlers;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.time.Instant;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.json.JSONWriter;
import net.mapeadores.util.json.JsonProducer;
import net.mapeadores.util.request.JsonType;
import net.mapeadores.util.request.RequestConstants;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.request.RequestUtils;
import net.mapeadores.util.servlets.ResponseHandler;


/**
 *
 * @author Vincent Calame
 */
public class JsonResponseHandler implements ResponseHandler, JsonType {

    public final static String ALL_ORIGIN = "*";
    private final JsonProducer jsonProducer;
    private Instant lastModified = ResponseHandler.LASTMODIFIED_NOCACHE;
    private final short jsonType;
    private final String callbackFunctionName;
    private String accessControlOrigin;
    private boolean allowCredentials = false;

    /**
     * JsonProducer peut être nul. Dans ce cas, l'objet Json renvoyé est
     * {"noOutput":true}
     */
    public JsonResponseHandler(@Nullable JsonProducer jsonProducer, short jsonType, @Nullable String callbackFunctionName) {
        this.jsonProducer = jsonProducer;
        switch (jsonType) {
            case JSON_P_TYPE:
                this.jsonType = JSON_P_TYPE;
                if (callbackFunctionName == null) {
                    this.callbackFunctionName = "callback";
                } else {
                    this.callbackFunctionName = callbackFunctionName;
                }
                break;
            case JSON_IN_HTML_TYPE:
                this.callbackFunctionName = callbackFunctionName;
                this.jsonType = JSON_IN_HTML_TYPE;
                break;
            default:
                this.callbackFunctionName = callbackFunctionName;
                if (callbackFunctionName != null) {
                    this.jsonType = JSON_P_TYPE;
                } else {
                    this.jsonType = JSON_TYPE;
                }
        }
    }

    @Override
    public Instant getLastModified() {
        return lastModified;
    }

    public JsonResponseHandler lastModified(Instant lastModified) {
        this.lastModified = lastModified;
        return this;
    }

    public JsonResponseHandler accessControl(String origin) {
        accessControlOrigin = origin;
        return this;
    }

    public JsonResponseHandler allowCredentials(boolean allowCredentials) {
        this.allowCredentials = allowCredentials;
        return this;
    }

    public JsonResponseHandler giveAccess(RequestMap requestMap) {
        Object sourceObject = requestMap.getSourceObject();
        if ((sourceObject != null) && (sourceObject instanceof HttpServletRequest)) {
            HttpServletRequest httpRequest = (HttpServletRequest) sourceObject;
            String origin = httpRequest.getHeader("origin");
            accessControl(origin);
        } else {
            accessControl(JsonResponseHandler.ALL_ORIGIN);
        }
        allowCredentials(true);
        return this;
    }

    @Override
    public void handleResponse(HttpServletResponse response) throws IOException {
        if (accessControlOrigin != null) {
            response.setHeader("Access-Control-Allow-Origin", accessControlOrigin);
            if (!accessControlOrigin.equals(ALL_ORIGIN)) {
                response.setHeader("Vary", "Origin");
            }
        }
        if (allowCredentials) {
            response.setHeader("Access-Control-Allow-Credentials", "true");
        }
        response.setContentType(RequestUtils.jsonTypeToContentType(jsonType));
        OutputStream outputStream = response.getOutputStream();
        try (BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"))) {
            boolean embedded = (jsonType == JSON_IN_HTML_TYPE);
            boolean callback = (callbackFunctionName != null);
            if (embedded) {
                bufWriter.write("<textarea>");
            }
            if (callback) {
                bufWriter.write(callbackFunctionName);
                bufWriter.write("(");
            }
            if (jsonProducer != null) {
                jsonProducer.writeJson(bufWriter);
            } else {
                writeNull(bufWriter);
            }
            if (callback) {
                bufWriter.write(");");
            }
            if (embedded) {
                bufWriter.write("</textarea>");
            }
        }
    }

    private void writeNull(Writer writer) throws IOException {
        JSONWriter jw = new JSONWriter(writer);
        jw.object();
        {
            jw.key("noOutput")
                    .value(true);
        }
        jw.endObject();
    }

    public static JsonResponseHandler init(@Nullable JsonProducer jsonProducer, short jsonType, @Nullable String callbackFunctionName) {
        return new JsonResponseHandler(jsonProducer, jsonType, callbackFunctionName);
    }

    public static JsonResponseHandler build(@Nullable JsonProducer jsonProducer, RequestMap requestMap) {
        short jsonType = RequestUtils.getJsonType(requestMap);
        String callback = requestMap.getParameter(RequestConstants.CALLBACK_PARAMETER);
        return new JsonResponseHandler(jsonProducer, jsonType, callback);
    }

}
