// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "ISO15118_chargerImpl.hpp"
#include "log.hpp"
#include "sdp.hpp"
#include "tools.hpp"
#include "v2g_ctx.hpp"
#include <algorithm>
#include <string.h>
#include <string_view>

const std::string CERTS_SUB_DIR = "certs"; // relativ path of the certs

using namespace std::chrono_literals;
using BidiMode = types::iso15118::SaeJ2847BidiMode;

namespace module {
namespace charger {

void ISO15118_chargerImpl::init() {
    if (!v2g_ctx) {
        dlog(DLOG_LEVEL_ERROR, "v2g_ctx not created");
        return;
    }

    /* Configure if_name and auth_mode */
    v2g_ctx->if_name = mod->config.device.data();
    dlog(DLOG_LEVEL_DEBUG, "if_name %s", v2g_ctx->if_name);

    /* Configure hlc_protocols */
    if (mod->config.supported_DIN70121 == true) {
        v2g_ctx->supported_protocols |= (1 << V2G_PROTO_DIN70121);
        supp_app_protocols_secc.app_protocols.push_back(types::iso15118::SupportedAppProtocol::DIN70121);
    }
    if (mod->config.supported_ISO15118_2 == true) {
        v2g_ctx->supported_protocols |= (1 << V2G_PROTO_ISO15118_2013);
        supp_app_protocols_secc.app_protocols.push_back(types::iso15118::SupportedAppProtocol::ISO15118D2);
    }

    /* Configure tls_security */
    if (mod->config.tls_security == "force") {
        v2g_ctx->tls_security = TLS_SECURITY_FORCE;
        dlog(DLOG_LEVEL_DEBUG, "tls_security force");
    } else if (mod->config.tls_security == "allow") {
        v2g_ctx->tls_security = TLS_SECURITY_ALLOW;
        dlog(DLOG_LEVEL_DEBUG, "tls_security allow");
    } else {
        v2g_ctx->tls_security = TLS_SECURITY_PROHIBIT;
        dlog(DLOG_LEVEL_DEBUG, "tls_security prohibit");
    }

    v2g_ctx->terminate_connection_on_failed_response = mod->config.terminate_connection_on_failed_response;

    v2g_ctx->tls_key_logging = mod->config.tls_key_logging;
    v2g_ctx->tls_key_logging_path = mod->config.tls_key_logging_path;

    if (mod->config.tls_key_logging == true) {
        dlog(DLOG_LEVEL_DEBUG, "tls-key-logging enabled (path: %s)", mod->config.tls_key_logging_path.c_str());
    }

    v2g_ctx->network_read_timeout_tls = mod->config.tls_timeout;

    /* Configure if the contract certificate chain should be verified locally */
    v2g_ctx->session.verify_contract_cert_chain = mod->config.verify_contract_cert_chain;

    v2g_ctx->session.auth_timeout_eim = mod->config.auth_timeout_eim;
    v2g_ctx->session.auth_timeout_pnc = mod->config.auth_timeout_pnc;

    v2g_ctx->supported_vas_services_per_provider.reserve(mod->r_iso15118_vas.size());

    for (size_t i = 0; i < mod->r_iso15118_vas.size(); i++) {
        auto& supported_vas_services = v2g_ctx->supported_vas_services_per_provider.emplace_back();

        this->mod->r_iso15118_vas.at(i)->subscribe_offered_vas(
            [&supported_vas_services](const types::iso15118_vas::OfferedServices& offered_services) {
                for (auto service : offered_services.services) {

                    const auto id = static_cast<uint16_t>(service.service_id);
                    if (id == V2G_SERVICE_ID_CHARGING) {
                        continue;
                    }
                    supported_vas_services.push_back(id);

                    iso2_ServiceType vas_service{};
                    init_iso2_ServiceType(&vas_service);
                    vas_service.ServiceID = id;
                    if (service.service_name.has_value()) {
                        strncpy_to_v2g(vas_service.ServiceName.characters, sizeof(vas_service.ServiceName.characters),
                                       &vas_service.ServiceName.charactersLen, service.service_name.value());
                        vas_service.ServiceName_isUsed = 1;
                    }
                    if (service.service_scope.has_value()) {
                        strncpy_to_v2g(vas_service.ServiceScope.characters, sizeof(vas_service.ServiceScope.characters),
                                       &vas_service.ServiceScope.charactersLen, service.service_scope.value());
                        vas_service.ServiceScope_isUsed = 1;
                    }
                    vas_service.FreeService = service.free_service.value_or(true);

                    if (id == V2G_SERVICE_ID_CERTIFICATE) {
                        vas_service.ServiceCategory = iso2_serviceCategoryType_ContractCertificate;
                    } else if (id == V2G_SERVICE_ID_INTERNET) {
                        vas_service.ServiceCategory = iso2_serviceCategoryType_Internet;
                        strncpy_to_v2g(vas_service.ServiceName.characters, sizeof(vas_service.ServiceName.characters),
                                       &vas_service.ServiceName.charactersLen, "InternetAccess");
                        vas_service.ServiceName_isUsed = true;
                    } else {
                        vas_service.ServiceCategory = iso2_serviceCategoryType_OtherCustom;
                    }

                    if (not add_service_to_service_list(v2g_ctx, vas_service)) {
                        break;
                    }
                }
            });
    }
}

void ISO15118_chargerImpl::ready() {
    publish_supported_app_protocols_secc(supp_app_protocols_secc);
}

void ISO15118_chargerImpl::handle_setup(types::iso15118::EVSEID& evse_id,
                                        types::iso15118::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) {

    uint8_t len = evse_id.evse_id.length();
    if (len < iso2_EVSEID_CHARACTER_SIZE) {
        memcpy(v2g_ctx->evse_v2g_data.evse_id.bytes, reinterpret_cast<uint8_t*>(evse_id.evse_id.data()), len);
        v2g_ctx->evse_v2g_data.evse_id.bytesLen = len;
    } else {
        dlog(DLOG_LEVEL_WARNING, "EVSEID_CHARACTER_SIZE exceeded (received: %u, max: %u)", len,
             iso2_EVSEID_CHARACTER_SIZE);
    }

    v2g_ctx->debugMode = debug_mode;

    if (sae_j2847_mode == BidiMode::V2H || sae_j2847_mode == BidiMode::V2G) {
        struct iso2_ServiceType sae_service;

        init_iso2_ServiceType(&sae_service);

        sae_service.FreeService = 1;
        sae_service.ServiceCategory = iso2_serviceCategoryType_OtherCustom;

        if (sae_j2847_mode == BidiMode::V2H) {
            sae_service.ServiceID = 28472;
            strncpy_to_v2g(sae_service.ServiceName.characters, sizeof(sae_service.ServiceName.characters),
                           &sae_service.ServiceName.charactersLen, "SAE J2847/2 V2H");
            sae_service.ServiceName_isUsed = true;
        } else {
            sae_service.ServiceID = 28473;
            strncpy_to_v2g(sae_service.ServiceName.characters, sizeof(sae_service.ServiceName.characters),
                           &sae_service.ServiceName.charactersLen, "SAE J2847/2 V2G");
            sae_service.ServiceName_isUsed = true;
        }

        add_service_to_service_list(v2g_ctx, sae_service);
    }
}

void ISO15118_chargerImpl::handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) {
    if (physical_values.ac_nominal_voltage.has_value()) {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_nominal_voltage,
                                      physical_values.ac_nominal_voltage.value(), 1, iso2_unitSymbolType_V);
        v2g_ctx->basic_config.evse_ac_nominal_voltage = physical_values.ac_nominal_voltage.value();
    }

    if (physical_values.dc_current_regulation_tolerance.has_value()) {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_current_regulation_tolerance,
                                      physical_values.dc_current_regulation_tolerance.value(), 1,
                                      iso2_unitSymbolType_A);
        v2g_ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used = 1;
    }

    if (physical_values.dc_peak_current_ripple.has_value()) {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_peak_current_ripple,
                                      physical_values.dc_peak_current_ripple.value(), 1, iso2_unitSymbolType_A);
    }

    if (physical_values.dc_energy_to_be_delivered.has_value()) {
        populate_physical_value(&v2g_ctx->evse_v2g_data.evse_energy_to_be_delivered,
                                physical_values.dc_energy_to_be_delivered.value(), iso2_unitSymbolType_Wh);
        v2g_ctx->evse_v2g_data.evse_energy_to_be_delivered_is_used = 1;
    }
}

void ISO15118_chargerImpl::handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
                                                bool& supported_certificate_service,
                                                bool& central_contract_validation_allowed) {
    if (not v2g_ctx->hlc_pause_active) {
        v2g_ctx->evse_v2g_data.payment_option_list.clear();
        if (not payment_options.empty()) {
            const auto max_payment_options =
                std::min(static_cast<size_t>(iso2_paymentOptionType_2_ARRAY_SIZE), payment_options.size());
            for (size_t i = 0; i < max_payment_options; i++) {
                const auto& payment_option = payment_options.at(i);
                if (payment_option == types::iso15118::PaymentOption::ExternalPayment) {
                    v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_ExternalPayment);
                } else if (payment_option == types::iso15118::PaymentOption::Contract) {
                    v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_Contract);
                } else {
                    dlog(DLOG_LEVEL_WARNING, "Unable to configure PaymentOption %s",
                         types::iso15118::payment_option_to_string(payment_option).c_str());
                }
            }
        }

        if (payment_options.empty() or v2g_ctx->evse_v2g_data.payment_option_list.empty()) {
            dlog(DLOG_LEVEL_ERROR, "No valid PaymentOptions configured, falling back to ExternalPayment");
            v2g_ctx->evse_v2g_data.payment_option_list.clear();
            v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_ExternalPayment);
        }
    }

    const auto pnc_enabled =
        std::find(v2g_ctx->evse_v2g_data.payment_option_list.begin(), v2g_ctx->evse_v2g_data.payment_option_list.end(),
                  iso2_paymentOptionType_Contract) != v2g_ctx->evse_v2g_data.payment_option_list.end();

    using state_t = tls::Server::state_t;
    const auto tls_server_state = v2g_ctx->tls_server->state();

    const auto tls_server_available =
        (tls_server_state == state_t::init_complete or tls_server_state == state_t::running);

    if (pnc_enabled and supported_certificate_service and tls_server_available) {
        // For setting "Certificate" in ServiceList in ServiceDiscoveryRes
        struct iso2_ServiceType cert_service;

        init_iso2_ServiceType(&cert_service);

        const int16_t cert_parameter_set_id[] = {1}; // parameter-set-ID 1: "Installation" service. TODO: Support of the
                                                     // "Update" service (parameter-set-ID 2)

        cert_service.FreeService = 1; // true
        cert_service.ServiceID = 2;   // as defined in ISO 15118-2
        cert_service.ServiceCategory = iso2_serviceCategoryType_ContractCertificate;
        strncpy_to_v2g(cert_service.ServiceName.characters, sizeof(cert_service.ServiceName.characters),
                       &cert_service.ServiceName.charactersLen, "Certificate");
        cert_service.ServiceName_isUsed = true;

        add_service_to_service_list(v2g_ctx, cert_service, cert_parameter_set_id,
                                    sizeof(cert_parameter_set_id) / sizeof(cert_parameter_set_id[0]));
    } else {
        // Make sure Certificate service is not in ServiceList when pnc is not possible
        remove_service_from_service_list_if_exists(v2g_ctx, V2G_SERVICE_ID_CERTIFICATE);
    }

    // Reset authorization that may have been provided after the TCP was closed, potentially causing
    // authorization to be reused in the next session. Resetting it here prevents that.
    v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso2_EVSEProcessingType_Ongoing;

    v2g_ctx->evse_v2g_data.central_contract_validation_allowed = central_contract_validation_allowed;
}

void ISO15118_chargerImpl::handle_bpt_setup(types::iso15118::BptSetup& bpt_config) {
    EVLOG_warning << "Ignoring handle_bpt_setup call";
}

void ISO15118_chargerImpl::handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) {

    const auto max_export_current =
        capabilities.nominal_max_export_current_A.value_or(capabilities.max_export_current_A);
    const auto min_export_current =
        capabilities.nominal_min_export_current_A.value_or(capabilities.min_export_current_A);
    const auto max_export_power = capabilities.nominal_max_export_power_W.value_or(capabilities.max_export_power_W);
    const auto max_export_voltage =
        capabilities.nominal_max_export_voltage_V.value_or(capabilities.max_export_voltage_V);
    const auto min_export_voltage =
        capabilities.nominal_min_export_voltage_V.value_or(capabilities.min_export_voltage_V);

    populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.max_current, max_export_current, 1,
                                  iso2_unitSymbolType_A);
    populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.min_current, min_export_current, 1,
                                  iso2_unitSymbolType_A);
    populate_physical_value(&v2g_ctx->evse_v2g_data.power_capabilities.max_power,
                            static_cast<uint32_t>(max_export_power), iso2_unitSymbolType_W);
    populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.max_voltage, max_export_voltage, 1,
                                  iso2_unitSymbolType_V);
    populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.min_voltage, min_export_voltage, 1,
                                  iso2_unitSymbolType_V);
}

void ISO15118_chargerImpl::handle_authorization_response(
    types::authorization::AuthorizationStatus& authorization_status,
    types::authorization::CertificateStatus& certificate_status) {

    v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = static_cast<uint8_t>(iso2_EVSEProcessingType_Finished);

    if (authorization_status != types::authorization::AuthorizationStatus::Accepted) {
        v2g_ctx->session.authorization_rejected = true;
    }

    if (v2g_ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) {
        v2g_ctx->session.certificate_status = certificate_status;
    }
}

void ISO15118_chargerImpl::handle_ac_contactor_closed(bool& status) {
    /* signal changes to possible waiters, according to man page, it never returns an error code */
    pthread_mutex_lock(&v2g_ctx->mqtt_lock);
    v2g_ctx->contactor_is_closed = status;
    pthread_cond_signal(&v2g_ctx->mqtt_cond);
    /* unlock */
    pthread_mutex_unlock(&v2g_ctx->mqtt_lock);
}

void ISO15118_chargerImpl::handle_dlink_ready(bool& value) {
    sdp_set_dlink_ready(v2g_ctx, value);

    // If dlink becomes not ready (false), stop TCP connection in the read thread
    if (!value) {
        v2g_ctx->is_connection_terminated = true;
    }
}

void ISO15118_chargerImpl::handle_cable_check_finished(bool& status) {
    if (status == true) {
        v2g_ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = (uint8_t)iso2_EVSEProcessingType_Finished;
    } else {
        v2g_ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = (uint8_t)iso2_EVSEProcessingType_Ongoing;
    }
}

void ISO15118_chargerImpl::handle_receipt_is_required(bool& receipt_required) {
    v2g_ctx->evse_v2g_data.receipt_required = (int)receipt_required;
}

void ISO15118_chargerImpl::handle_stop_charging(bool& stop) {
    // FIXME we need to use locks on v2g-ctx in all commands as they are running in different threads

    if (stop) {
        // spawn new thread to not block command handler
        std::thread([stop] {
            // try to gracefully shutdown charging session
            v2g_ctx->evse_v2g_data.evse_notification = iso2_EVSENotificationType_StopCharging;
            memset(v2g_ctx->evse_v2g_data.evse_status_code, iso2_DC_EVSEStatusCodeType_EVSE_Shutdown,
                   sizeof(v2g_ctx->evse_v2g_data.evse_status_code));

            int i;
            bool timeout_reached = true;
            // allow 10 seconds for graceful shutdown
            for (i = 0; i < 10; i++) {
                if (v2g_ctx->is_connection_terminated) {
                    timeout_reached = false;
                    break;
                }
                std::this_thread::sleep_for(1s);
            }

            // If it did not stop within timeout, stop session using FAILED reply
            if (timeout_reached) {
                v2g_ctx->stop_hlc = stop;
            }
        }).detach();
    } else {
        v2g_ctx->stop_hlc = false;
    }
}

void ISO15118_chargerImpl::handle_pause_charging(bool& pause) {
    EVLOG_warning << "Pause initialized by the charger is not supported in DIN70121 and ISO15118-2";
}

void ISO15118_chargerImpl::handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) {

    switch (mode) {
    case types::iso15118::NoEnergyPauseMode::PauseAfterPrecharge:
        v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::AfterCableCheckPreCharge;
        break;

    case types::iso15118::NoEnergyPauseMode::PauseBeforeCableCheck:
        v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::BeforeCableCheck;
        break;

    case types::iso15118::NoEnergyPauseMode::AllowEvToIgnorePause:
        v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::AllowEvToIgnorePause;
        break;
    }
}

bool ISO15118_chargerImpl::handle_update_supported_app_protocols(
    types::iso15118::SupportedAppProtocols& supported_app_protocols) {
    bool rv{true};
    v2g_ctx->supported_protocols = 0;

    if (supported_app_protocols.app_protocols.empty()) {
        dlog(DLOG_LEVEL_WARNING, "No supported app protocols configured");
        return true;
    }

    std::string configured_protocols;

    for (const auto& protocol : supported_app_protocols.app_protocols) {
        if (!configured_protocols.empty()) {
            configured_protocols += ", ";
        }
        configured_protocols += types::iso15118::supported_app_protocol_to_string(protocol);
    }

    dlog(DLOG_LEVEL_INFO, "Configured charging protocols: [%s]", configured_protocols.c_str());

    for (const auto& protocol : supported_app_protocols.app_protocols) {
        // Check if the supported app protocol is in the SECC list
        const bool allowed = std::find(this->supp_app_protocols_secc.app_protocols.begin(),
                                       this->supp_app_protocols_secc.app_protocols.end(),
                                       protocol) != this->supp_app_protocols_secc.app_protocols.end();

        if (!allowed) {
            dlog(DLOG_LEVEL_WARNING, "Skip unsupported app protocol: %s",
                 types::iso15118::supported_app_protocol_to_string(protocol).c_str());
            rv = false;
            continue;
        }
        /* Configure supported app bitmask. This bitmark is used in the supportedAppHandshake handle
           to select the protocol */
        switch (protocol) {
        case types::iso15118::SupportedAppProtocol::DIN70121:
            v2g_ctx->supported_protocols |= (1 << V2G_PROTO_DIN70121);
            break;
        case types::iso15118::SupportedAppProtocol::ISO15118D2:
            v2g_ctx->supported_protocols |= (1 << V2G_PROTO_ISO15118_2013);
            break;
        case types::iso15118::SupportedAppProtocol::ISO15118D20:
        default:
            dlog(DLOG_LEVEL_WARNING, "Unsupported app protocol: %s",
                 types::iso15118::supported_app_protocol_to_string(protocol).c_str());
            rv = false;
        }
    }

    if (v2g_ctx->supported_protocols == 0) {
        dlog(DLOG_LEVEL_WARNING, "No supported app protocols provided");
        rv = false;
    }
    return rv;
}

void ISO15118_chargerImpl::handle_update_energy_transfer_modes(
    std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
    if (v2g_ctx->hlc_pause_active) {
        return;
    }

    uint16_t& energyArrayLen =
        (v2g_ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.arrayLen);
    iso2_EnergyTransferModeType* energyArray =
        v2g_ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array;
    energyArrayLen = 0;

    v2g_ctx->is_dc_charger = true;

    const auto max_supported_energy_modes =
        std::min(static_cast<size_t>(iso2_EnergyTransferModeType_6_ARRAY_SIZE), supported_energy_transfer_modes.size());

    for (size_t i = 0; i < max_supported_energy_modes; i++) {
        const auto& mode = supported_energy_transfer_modes.at(i);
        switch (mode) {
        case types::iso15118::EnergyTransferMode::AC_single_phase_core:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_AC_single_phase_core;
            v2g_ctx->is_dc_charger = false;
            break;
        case types::iso15118::EnergyTransferMode::AC_three_phase_core:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_AC_three_phase_core;
            v2g_ctx->is_dc_charger = false;
            break;
        case types::iso15118::EnergyTransferMode::DC_core:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_core;
            break;
        case types::iso15118::EnergyTransferMode::DC_extended:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_extended;
            break;
        case types::iso15118::EnergyTransferMode::DC_combo_core:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_combo_core;
            break;
        case types::iso15118::EnergyTransferMode::DC_unique:
            energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_unique;
            break;
        case types::iso15118::EnergyTransferMode::DC_ACDP_BPT:
        case types::iso15118::EnergyTransferMode::AC_BPT:
        case types::iso15118::EnergyTransferMode::AC_BPT_DER:
        case types::iso15118::EnergyTransferMode::DC_BPT:
            dlog(DLOG_LEVEL_INFO, "Ignoring bidirectional SupportedEnergyTransferMode");
        default:
            if (energyArrayLen == 0) {

                dlog(DLOG_LEVEL_WARNING, "Unable to configure SupportedEnergyTransferMode %s",
                     types::iso15118::energy_transfer_mode_to_string(mode).c_str());
            }
            break;
        }
    }

    if (mod->config.supported_DIN70121 and not v2g_ctx->is_dc_charger) {
        v2g_ctx->supported_protocols &= ~(1 << V2G_PROTO_DIN70121);
        dlog(DLOG_LEVEL_WARNING, "Removed DIN70121 from the list of supported protocols as AC is enabled");
    }
}

void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) {
    v2g_ctx->basic_config.evse_ac_current_limit = max_current;
}

void ISO15118_chargerImpl::handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) {
    static bool warning_shown = false;
    if (not warning_shown) {
        EVLOG_warning << "Ignoring handle_update_ac_parameters call";
        warning_shown = true;
    }
}

void ISO15118_chargerImpl::handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) {
    if (v2g_ctx->basic_config.evse_ac_nominal_voltage > 0.0f) {
        v2g_ctx->basic_config.evse_ac_nominal_current =
            maximum_limits.charge_power.total / v2g_ctx->basic_config.evse_ac_nominal_voltage;
    } else {
        EVLOG_error << "Cannot compute nominal current: nominal voltage is 0";
    }
}

void ISO15118_chargerImpl::handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) {
    static bool warning_shown = false;
    if (not warning_shown) {
        EVLOG_warning << "Ignoring handle_update_ac_minimum_limits call";
        warning_shown = true;
    }
}

void ISO15118_chargerImpl::handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) {
    static bool warning_shown = false;
    if (not warning_shown) {
        EVLOG_warning << "Ignoring handle_update_ac_target_values call";
        warning_shown = true;
    }
}

void ISO15118_chargerImpl::handle_update_ac_present_power(types::units::Power& present_power) {
    static bool warning_shown = false;
    if (not warning_shown) {
        EVLOG_warning << "Ignoring handle_update_ac_present_power call";
        warning_shown = true;
    }
}

void ISO15118_chargerImpl::handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) {
    if (maximum_limits.evse_maximum_current_limit > 300.) {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_current_limit,
                                      maximum_limits.evse_maximum_current_limit, 1, iso2_unitSymbolType_A);
    } else {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_current_limit,
                                      maximum_limits.evse_maximum_current_limit, 2, iso2_unitSymbolType_A);
    }
    v2g_ctx->evse_v2g_data.evse_maximum_current_limit_is_used = 1;

    populate_physical_value(&v2g_ctx->evse_v2g_data.evse_maximum_power_limit, maximum_limits.evse_maximum_power_limit,
                            iso2_unitSymbolType_W);
    v2g_ctx->evse_v2g_data.evse_maximum_power_limit_is_used = 1;

    populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_voltage_limit,
                                  maximum_limits.evse_maximum_voltage_limit, 1, iso2_unitSymbolType_V);
    v2g_ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used = 1;
}

void ISO15118_chargerImpl::handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) {

    populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_minimum_current_limit,
                                  static_cast<long long int>(minimum_limits.evse_minimum_current_limit), 1,
                                  iso2_unitSymbolType_A);
    populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_minimum_voltage_limit,
                                  static_cast<long long int>(minimum_limits.evse_minimum_voltage_limit), 1,
                                  iso2_unitSymbolType_V);
}

void ISO15118_chargerImpl::handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) {
    v2g_ctx->evse_v2g_data.evse_isolation_status = (uint8_t)isolation_status;
    v2g_ctx->evse_v2g_data.evse_isolation_status_is_used = 1;
}

void ISO15118_chargerImpl::handle_update_dc_present_values(
    types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) {
    populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_present_voltage,
                                  present_voltage_current.evse_present_voltage, 1, iso2_unitSymbolType_V);

    if (present_voltage_current.evse_present_current.has_value()) {
        populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_present_current,
                                      static_cast<float>(present_voltage_current.evse_present_current.value()), 1,
                                      iso2_unitSymbolType_A);
    }
}

void ISO15118_chargerImpl::handle_update_meter_info(types::powermeter::Powermeter& powermeter) {
    // Signal for ChargingStatus and CurrentDemand that MeterInfo is used
    v2g_ctx->meter_info.meter_info_is_used = 1;
    v2g_ctx->meter_info.meter_reading = powermeter.energy_Wh_import.total;

    if (powermeter.meter_id) {
        uint8_t len = powermeter.meter_id->length();
        if (len < iso2_MeterID_CHARACTER_SIZE) {
            memcpy(v2g_ctx->meter_info.meter_id.bytes, powermeter.meter_id->c_str(), len);
            v2g_ctx->meter_info.meter_id.bytesLen = len;
        } else {
            dlog(DLOG_LEVEL_WARNING, "MeterID_CHARACTER_SIZEexceeded (received: %u, max: %u)", len,
                 iso2_MeterID_CHARACTER_SIZE);
        }
    }
}

void ISO15118_chargerImpl::handle_send_error(types::iso15118::EvseError& error) {
    switch (error) {
    case types::iso15118::EvseError::Error_Contactor:
        break;
    case types::iso15118::EvseError::Error_RCD:
        v2g_ctx->evse_v2g_data.rcd = 1;
        break;
    case types::iso15118::EvseError::Error_UtilityInterruptEvent:
        memset(v2g_ctx->evse_v2g_data.evse_status_code, (int)iso2_DC_EVSEStatusCodeType_EVSE_UtilityInterruptEvent,
               sizeof(v2g_ctx->evse_v2g_data.evse_status_code));
        break;
    case types::iso15118::EvseError::Error_Malfunction:
        memset(v2g_ctx->evse_v2g_data.evse_status_code, (int)iso2_DC_EVSEStatusCodeType_EVSE_Malfunction,
               sizeof(v2g_ctx->evse_v2g_data.evse_status_code));
        break;
    case types::iso15118::EvseError::Error_EmergencyShutdown:
        /* signal changes to possible waiters, according to man page, it never returns an error code */
        pthread_mutex_lock(&v2g_ctx->mqtt_lock);
        v2g_ctx->intl_emergency_shutdown = true;
        pthread_cond_signal(&v2g_ctx->mqtt_cond);
        /* unlock */
        pthread_mutex_unlock(&v2g_ctx->mqtt_lock);
        break;
    default:
        break;
    }
}

void ISO15118_chargerImpl::handle_reset_error() {
    v2g_ctx->evse_v2g_data.rcd = 0;

    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_INIT] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_AUTH] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_PARAMETER] = iso2_DC_EVSEStatusCodeType_EVSE_Ready; // [V2G-DC-453]
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_ISOLATION] =
        iso2_DC_EVSEStatusCodeType_EVSE_IsolationMonitoringActive;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_PRECHARGE] = iso2_DC_EVSEStatusCodeType_EVSE_Ready;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_CHARGE] = iso2_DC_EVSEStatusCodeType_EVSE_Ready;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_WELDING] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
    v2g_ctx->evse_v2g_data.evse_status_code[PHASE_STOP] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;

    // Todo(sl): check if emergency should be cleared here?
}

} // namespace charger
} // namespace module
