RaoResultArchiveManager.java

/*
 * Copyright (c) 2025, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package com.powsybl.openrao.searchtreerao.marmot.results;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.openrao.commons.TemporalData;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.raoresult.api.InterTemporalRaoResult;
import com.powsybl.openrao.data.raoresult.api.RaoResult;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author Thomas Bouquet {@literal <thomas.bouquet at rte-france.com>}
 * @author Roxane Chen {@literal <roxane.chen at rte-france.com>}
 */
public final class RaoResultArchiveManager {
    private static final String INDIVIDUAL_RAO_RESULT_NAME_TEMPLATE_PROPERTY = "inter-temporal-rao-result.export.filename-template";
    private static final String INTER_TEMPORAL_RAO_RESULT_SUMMARY_FILENAME_PROPERTY = "inter-temporal-rao-result.export.summary-filename";
    private static final String INTER_TEMPORAL_RAO_RESULT_PREVENTIVE_ONLY = "inter-temporal-rao-result.export.preventive-only";
    private static final String DEFAULT_INDIVIDUAL_RAO_RESULT_NAME_TEMPLATE = "'raoResult_'yyyyMMddHHmm'.json'";
    private static final String DEFAULT_INTER_TEMPORAL_RAO_RESULT_SUMMARY_FILENAME = "interTemporalRaoSummary.json";

    private RaoResultArchiveManager() {
    }

    // default export format is JSON, see later to set export format
    public static void exportAndZipResults(ZipOutputStream zipOutputStream, InterTemporalRaoResult interTemporalRaoResult, TemporalData<Crac> cracs, Properties properties) throws IOException {
        String jsonFileNameTemplate = getIndividualRaoResultFilenameTemplate(properties);
        String summaryFilename = getSummaryFilename(properties);
        for (OffsetDateTime timestamp : interTemporalRaoResult.getTimestamps()) {
            addRaoResultToZipArchive(timestamp, zipOutputStream, interTemporalRaoResult.getIndividualRaoResult(timestamp), cracs.getData(timestamp).orElseThrow(), properties, jsonFileNameTemplate);
        }
        List<Instant> instants = cracs.getDataPerTimestamp().values().iterator().next().getSortedInstants();
        addSummaryToZipArchive(zipOutputStream, interTemporalRaoResult, summaryFilename, jsonFileNameTemplate, instants, exportOnlyPreventiveResults(properties));
    }

    private static void addRaoResultToZipArchive(OffsetDateTime timestamp, ZipOutputStream zipOutputStream, RaoResult raoResult, Crac crac, Properties properties, String jsonFileNameTemplate) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        raoResult.write("JSON", crac, properties, byteArrayOutputStream);
        addEntryToZipArchive(timestamp.format(DateTimeFormatter.ofPattern(jsonFileNameTemplate)), zipOutputStream, byteArrayOutputStream);
        byteArrayOutputStream.close();
    }

    private static void addSummaryToZipArchive(ZipOutputStream zipOutputStream, InterTemporalRaoResult interTemporalRaoResult, String summaryFilename, String jsonFileNameTemplate, List<Instant> instants, boolean preventiveOnly) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            ObjectMapper objectMapper = JsonUtil.createObjectMapper();
            SimpleModule module = new JsonInterTemporalRaoResultSerializerModule(jsonFileNameTemplate, preventiveOnly ? List.of(instants.get(0)) : instants);
            objectMapper.registerModule(module);
            ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
            writer.writeValue(byteArrayOutputStream, interTemporalRaoResult);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        addEntryToZipArchive(summaryFilename, zipOutputStream, byteArrayOutputStream);
        byteArrayOutputStream.close();
    }

    private static void addEntryToZipArchive(String entryName, ZipOutputStream zipOutputStream, ByteArrayOutputStream byteArrayOutputStream) throws IOException {
        ZipEntry entry = new ZipEntry(entryName);
        zipOutputStream.putNextEntry(entry);
        addOutputStreamContentToZipAndClose(zipOutputStream, byteArrayOutputStream);
        byteArrayOutputStream.close();
        zipOutputStream.closeEntry();
    }

    private static void addOutputStreamContentToZipAndClose(ZipOutputStream zipOutputStream, ByteArrayOutputStream byteArrayOutputStream) throws IOException {
        byte[] bytes = new byte[1024];
        int length;
        InputStream is = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        while ((length = is.read(bytes)) >= 0) {
            zipOutputStream.write(bytes, 0, length);
        }
        is.close();
    }

    private static String getSummaryFilename(Properties properties) {
        return properties.getProperty(INTER_TEMPORAL_RAO_RESULT_SUMMARY_FILENAME_PROPERTY, DEFAULT_INTER_TEMPORAL_RAO_RESULT_SUMMARY_FILENAME);
    }

    private static String getIndividualRaoResultFilenameTemplate(Properties properties) {
        return properties.getProperty(INDIVIDUAL_RAO_RESULT_NAME_TEMPLATE_PROPERTY, DEFAULT_INDIVIDUAL_RAO_RESULT_NAME_TEMPLATE);
    }

    private static boolean exportOnlyPreventiveResults(Properties properties) {
        return Boolean.parseBoolean(properties.getProperty(INTER_TEMPORAL_RAO_RESULT_PREVENTIVE_ONLY));
    }
}