// SPDX-License-Identifier: Apache-2.0
// Copyright chargebyte GmbH and Contributors to EVerest
#ifndef DATASTORE_HPP
#define DATASTORE_HPP

#include "GenericInfoStore.hpp"
#include "SessionInfo.hpp"
#include <atomic>
#include <bitset>
#include <chrono>
#include <condition_variable>
#include <everest/logging.hpp>
#include <type_traits>

namespace RPCDataTypes = types::json_rpc_api;

namespace data {

enum class EVSEStatusField {
    ActiveConnectorIndex,
    ChargingAllowed,
    State,
    ErrorPresent,
    ChargeProtocol,
    ChargingDurationS,
    ChargedEnergyWh,
    DischargedEnergyWh,
    Available,
    Count
};

template <typename T> constexpr auto to_underlying_value(T value) {
    return static_cast<std::underlying_type_t<T>>(value);
}

static constexpr auto NUMBER_OF_EVSE_STATUS_FIELDS = to_underlying_value(EVSEStatusField::Count);

static_assert(NUMBER_OF_EVSE_STATUS_FIELDS == to_underlying_value(EVSEStatusField::Available) + 1,
              "NUMBER_OF_EVSE_STATUS_FIELDS should be in sync with EVSEStatusField enum definition");

class ChargerInfoStore : public GenericInfoStore<RPCDataTypes::ChargerInfoObj> {
public:
    // we currently don't get this info from the system yet, so allow setting to unknown
    void set_unknown();
};

class ChargerErrorsStore : public GenericInfoStore<std::vector<types::json_rpc_api::ErrorObj>> {
public:
    void add_error(const types::json_rpc_api::ErrorObj& error);
    void clear_error(const types::json_rpc_api::ErrorObj& error);
};

class EVSEInfoStore : public GenericInfoStore<RPCDataTypes::EVSEInfoObj> {
public:
    void set_supported_energy_transfer_modes(
        const std::vector<types::json_rpc_api::EnergyTransferModeEnum>& supported_energy_transfer_modes);
    void set_index(int32_t index);
    void set_id(const std::string& id);
    void set_available_connectors(const std::vector<RPCDataTypes::ConnectorInfoObj>& connectors);
    void set_available_connector(types::json_rpc_api::ConnectorInfoObj& available_connector);
    void set_is_ac_transfer_mode(bool is_ac);
    bool get_is_ac_transfer_mode() const;
    int32_t get_index();
    std::vector<types::json_rpc_api::ConnectorInfoObj> get_available_connectors() const;

private:
    std::atomic<bool> is_ac_transfer_mode;
};

class EVSEStatusStore : public GenericInfoStore<RPCDataTypes::EVSEStatusObj> {
private:
    std::bitset<NUMBER_OF_EVSE_STATUS_FIELDS> field_status{0};

    std::condition_variable cv_current_limit_applied;
    std::mutex mtx_current_limit_applied;
    float requested_current_limit{0.0f};
    float configured_current_limit{0.0f};

    void update_data_is_valid();
    void set_field_status(EVSEStatusField field);
    // Internal method to set the current limit in the AC charge parameters without checking for pending requests
    void set_ac_charge_param_evse_current_limit_internal(float max_current);

public:
    EVSEStatusStore();

    // Set the active connector index
    void set_active_connector_index(int32_t active_connector_index);
    // set the charging allowed flag
    void set_charging_allowed(bool charging_allowed);
    // set the EVSE state
    void set_state(types::json_rpc_api::EVSEStateEnum state);

    // set EVSE errors
    void set_error_present(const bool error_present);
    // set the charge protocol
    void set_charge_protocol(types::json_rpc_api::ChargeProtocolEnum charge_protocol);
    // set the charging duration in seconds
    void set_charging_duration_s(int32_t charging_duration_s);
    // set the charged energy in Wh
    void set_charged_energy_wh(float charged_energy_wh);
    // set the discharged energy in Wh
    void set_discharged_energy_wh(float discharged_energy_wh);
    // set the available flag
    void set_available(bool available);
    // set the AC charge parameters
    void set_ac_charge_param(const std::optional<RPCDataTypes::ACChargeParametersObj>& ac_charge_param);
    // set the DC charge parameters
    void set_dc_charge_param(const std::optional<RPCDataTypes::DCChargeParametersObj>& dc_charge_param);
    // set the AC charge status
    void set_ac_charge_status(const std::optional<RPCDataTypes::ACChargeStatusObj>& ac_charge_status);
    // set the DC charge status
    void set_dc_charge_status(const std::optional<RPCDataTypes::DCChargeStatusObj>& dc_charge_status);
    // set the display parameters
    void set_display_parameters(const std::optional<RPCDataTypes::DisplayParametersObj>& display_parameters);
    // set the AC max phase count in the AC charge parameters
    void set_ac_charge_param_evse_max_phase_count(int32_t phase_count);
    // set the AC current limit and notify any waiting request threads
    void set_ac_charge_param_evse_max_current(float current_limit);
    // wait until the current limit is applied or timeout occurs
    bool wait_until_current_limit_applied(float current_limit, std::chrono::milliseconds timeout_ms);

    types::json_rpc_api::EVSEStateEnum get_state() const;
    std::optional<RPCDataTypes::ACChargeParametersObj> get_ac_charge_param();
    std::optional<RPCDataTypes::DCChargeParametersObj> get_dc_charge_param();
};
class HardwareCapabilitiesStore : public GenericInfoStore<RPCDataTypes::HardwareCapabilitiesObj> {};
class MeterDataStore : public GenericInfoStore<RPCDataTypes::MeterDataObj> {};

// This is the data store for a single EVSE. An EVSE can have multiple connectors.
struct DataStoreEvse {
    EVSEInfoStore evseinfo;
    EVSEStatusStore evsestatus;
    MeterDataStore meterdata;
    HardwareCapabilitiesStore hardwarecapabilities;
    SessionInfoStore sessioninfo;
};

// This is the main data store for the charger. A charger can have multiple EVSEs, each with multiple connectors.
// For more information see 3-Tier model definition of OCPP 2.0.
struct DataStoreCharger {
    ChargerInfoStore chargerinfo;
    ChargerErrorsStore chargererrors;
    std::string everest_version;
    std::vector<std::unique_ptr<DataStoreEvse>> evses;

    // get the EVSE data with a specific id
    data::DataStoreEvse* get_evse_store(const int32_t evse_index) {
        if (evses.empty()) {
            EVLOG_error << "No EVSEs found in the data store.";
            return nullptr;
        }

        for (const auto& evse : evses) {
            const auto tmp_index = evse->evseinfo.get_index();
            if (tmp_index == evse_index) {
                return evse.get();
            }
        }
        EVLOG_error << "EVSE index " << evse_index << " not found in data store.";
        return nullptr;
    }
};

} // namespace data

#endif // DATASTORE_HPP
