JsonNcCracCreationParameters.java

/*
 * Copyright (c) 2024, 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.data.crac.io.nc.parameters;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.auto.service.AutoService;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.crac.api.parameters.JsonCracCreationParameters;

import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
 */
@AutoService(JsonCracCreationParameters.ExtensionSerializer.class)
public class JsonNcCracCreationParameters implements JsonCracCreationParameters.ExtensionSerializer<NcCracCreationParameters> {

    private static final String CAPACITY_CALCULATION_REGION_EIC_CODE = "capacity-calculation-region-eic-code";
    private static final String TSOS_WHICH_DO_NOT_USE_PATL_IN_FINAL_STATE = "tsos-which-do-not-use-patl-in-final-state";
    private static final String CURATIVE_INSTANTS = "curative-instants";
    private static final String NAME = "name";
    private static final String APPLICATION_TIME = "application-time";
    private static final String BORDERS = "borders";
    private static final String EIC = "eic";
    private static final String DEFAULT_FOR_TSO = "default-for-tso";
    private static final String RESTRICTED_CURATIVE_BATCHES_PER_TSO = "restricted-curative-batches-per-tso";
    private static final String TIMESTAMP = "timestamp";

    @Override
    public void serialize(NcCracCreationParameters ncParameters, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        serializeCapacityCalculationRegionEicCode(ncParameters.getCapacityCalculationRegionEicCode(), jsonGenerator);
        serializeTsosWhichDoNotUsePatlInFinalState(ncParameters.getTsosWhichDoNotUsePatlInFinalState(), jsonGenerator);
        serializeCurativeInstants(ncParameters.getCurativeInstants(), jsonGenerator);
        serializeBorders(ncParameters.getBorders(), jsonGenerator);
        serializeRestrictedCurativeBatchesPerTso(ncParameters.getRestrictedCurativeBatchesPerTso(), jsonGenerator);
        serializeTimestamp(ncParameters.getTimestamp(), jsonGenerator);
        jsonGenerator.writeEndObject();
    }

    @Override
    public NcCracCreationParameters deserializeAndUpdate(JsonParser jsonParser, DeserializationContext deserializationContext, NcCracCreationParameters parameters) throws IOException {
        while (!jsonParser.nextToken().isStructEnd()) {
            switch (jsonParser.getCurrentName()) {
                case CAPACITY_CALCULATION_REGION_EIC_CODE:
                    jsonParser.nextToken();
                    parameters.setCapacityCalculationRegionEicCode(jsonParser.readValueAs(String.class));
                    break;
                case TSOS_WHICH_DO_NOT_USE_PATL_IN_FINAL_STATE:
                    jsonParser.nextToken();
                    parameters.setTsosWhichDoNotUsePatlInFinalState(jsonParser.readValueAs(new TypeReference<HashSet<String>>() { }));
                    break;
                case CURATIVE_INSTANTS:
                    jsonParser.nextToken();
                    parameters.setCurativeInstants(deserializeCurativeInstants(jsonParser));
                    break;
                case BORDERS:
                    jsonParser.nextToken();
                    parameters.setBorders(deserializeBorders(jsonParser));
                    break;
                case RESTRICTED_CURATIVE_BATCHES_PER_TSO:
                    jsonParser.nextToken();
                    parameters.setRestrictedCurativeBatchesPerTso(deserializeRestrictedCurativeBatchesPerTso(jsonParser));
                    break;
                case TIMESTAMP:
                    jsonParser.nextToken();
                    parameters.setTimestamp(OffsetDateTime.parse(jsonParser.readValueAs(String.class)));
                    break;
                default:
                    throw new OpenRaoException("Unexpected field: " + jsonParser.getCurrentName());
            }
        }

        return parameters;
    }

    @Override
    public String getExtensionName() {
        return "NcCracCreatorParameters";
    }

    @Override
    public String getCategoryName() {
        return "crac-creation-parameters";
    }

    @Override
    public Class<? super NcCracCreationParameters> getExtensionClass() {
        return NcCracCreationParameters.class;
    }

    private void serializeCapacityCalculationRegionEicCode(String eicCode, JsonGenerator jsonGenerator) throws IOException {
        jsonGenerator.writeStringField(CAPACITY_CALCULATION_REGION_EIC_CODE, eicCode);
    }

    private void serializeTsosWhichDoNotUsePatlInFinalState(Set<String> usePatlInFinalState, JsonGenerator jsonGenerator) throws IOException {
        jsonGenerator.writeFieldName(TSOS_WHICH_DO_NOT_USE_PATL_IN_FINAL_STATE);
        jsonGenerator.writeStartArray();
        usePatlInFinalState.forEach(tso -> {
            try {
                jsonGenerator.writeString(tso);
            } catch (IOException e) {
                throwSerializationError(TSOS_WHICH_DO_NOT_USE_PATL_IN_FINAL_STATE, e);
            }
        });
        jsonGenerator.writeEndArray();
    }

    private void serializeCurativeInstants(Map<String, Integer> curativeInstants, JsonGenerator jsonGenerator) throws IOException {
        jsonGenerator.writeFieldName(CURATIVE_INSTANTS);
        jsonGenerator.writeStartArray();
        curativeInstants.forEach((name, applicationTime) -> {
            try {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(NAME, name);
                jsonGenerator.writeNumberField(APPLICATION_TIME, applicationTime);
                jsonGenerator.writeEndObject();
            } catch (IOException e) {
                throwSerializationError(CURATIVE_INSTANTS, e);
            }
        });
        jsonGenerator.writeEndArray();
    }

    private void serializeRestrictedCurativeBatchesPerTso(Map<String, Set<String>> restrictedCurativeBatchesPerTso, JsonGenerator jsonGenerator) throws IOException {
        jsonGenerator.writeFieldName(RESTRICTED_CURATIVE_BATCHES_PER_TSO);
        jsonGenerator.writeStartObject();
        restrictedCurativeBatchesPerTso.forEach((operator, curativeInstants) -> {
            try {
                jsonGenerator.writeFieldName(operator);
                jsonGenerator.writeStartArray();
                curativeInstants.forEach(curativeInstant -> {
                    try {
                        jsonGenerator.writeString(curativeInstant);
                    } catch (IOException e) {
                        throwSerializationError(RESTRICTED_CURATIVE_BATCHES_PER_TSO, e);
                    }
                });
                jsonGenerator.writeEndArray();
            } catch (IOException e) {
                throwSerializationError(RESTRICTED_CURATIVE_BATCHES_PER_TSO, e);
            }
        });
        jsonGenerator.writeEndObject();
    }

    private void serializeBorders(Set<Border> borders, JsonGenerator jsonGenerator) throws IOException {
        jsonGenerator.writeFieldName(BORDERS);
        jsonGenerator.writeStartArray();
        borders.forEach(border -> {
            try {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(NAME, border.name());
                jsonGenerator.writeStringField(EIC, border.eic());
                jsonGenerator.writeStringField(DEFAULT_FOR_TSO, border.defaultForTso());
                jsonGenerator.writeEndObject();
            } catch (IOException e) {
                throwSerializationError(BORDERS, e);
            }
        });
        jsonGenerator.writeEndArray();
    }

    private void serializeTimestamp(OffsetDateTime timestamp, JsonGenerator jsonGenerator) throws IOException {
        if (timestamp != null) {
            jsonGenerator.writeStringField(TIMESTAMP, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").format(timestamp));
        }
    }

    @Override
    public NcCracCreationParameters deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        return deserializeAndUpdate(jsonParser, deserializationContext, new NcCracCreationParameters());
    }

    private Map<String, Integer> deserializeCurativeInstants(JsonParser jsonParser) throws IOException {
        Map<String, Integer> curativeInstants = new HashMap<>();
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            String name = null;
            Integer applicationTime = null;
            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
                if (NAME.equals(jsonParser.getCurrentName())) {
                    name = jsonParser.nextTextValue();
                } else if (APPLICATION_TIME.equals(jsonParser.getCurrentName())) {
                    applicationTime = jsonParser.nextIntValue(0);
                } else {
                    throw new OpenRaoException("Unexpected field in %s: %s".formatted(CURATIVE_INSTANTS, jsonParser.getCurrentName()));
                }
            }
            if (name == null || applicationTime == null) {
                throw new OpenRaoException("Incomplete data for curative instant; please provide both a %s and an %s".formatted(NAME, APPLICATION_TIME));
            }
            curativeInstants.put(name, applicationTime);
        }
        return curativeInstants;
    }

    private Map<String, Set<String>> deserializeRestrictedCurativeBatchesPerTso(JsonParser jsonParser) throws IOException {
        Map<String, Set<String>> restrictedCurativeBatchesPerTso = new HashMap<>();
        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String operator = jsonParser.currentName();
            jsonParser.nextToken();
            String[] instants = jsonParser.readValueAs(String[].class);
            restrictedCurativeBatchesPerTso.put(operator, new HashSet<>(List.of(instants)));
        }
        return restrictedCurativeBatchesPerTso;
    }

    private Set<Border> deserializeBorders(JsonParser jsonParser) throws IOException {
        Set<Border> borders = new HashSet<>();
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            Map<String, String> borderData = new HashMap<>();
            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
                borderData.put(jsonParser.getCurrentName(), jsonParser.nextTextValue());
            }
            borders.add(new Border(borderData.get(NAME), borderData.get(EIC), borderData.get(DEFAULT_FOR_TSO)));
        }
        return borders;
    }

    private static void throwSerializationError(String nonSerializableField, IOException e) {
        throw new OpenRaoException("Could not serialize " + nonSerializableField + " map. Reason: " + e.getMessage());
    }
}