// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
/*
 * Charger.h
 *
 *  Created on: 08.03.2021
 *  Author: cornelius
 *
 *  IEC 61851-1 compliant AC/DC high level charging logic
 *
 * This class provides:
 *  1) Hi level state machine that is controlled by a) events from evse_board_support interface
 *     and b) by external commands from higher levels
 *
 * The state machine runs in its own (big) thread. After plugin,
 * The charger waits in state WaitingForAuthentication forever. Send
 * Authenticate()
 * from hi level to start charging. After car is unplugged, it waits in
 * ChargingFinished forever (or in an error state if an error happens during
 * charging).
 */

#ifndef SRC_EVDRIVERS_CHARGER_H_
#define SRC_EVDRIVERS_CHARGER_H_

#include "SessionLog.hpp"
#include "ld-ev.hpp"
#include "utils/thread.hpp"
#include <chrono>
#include <date/date.h>
#include <date/tz.h>
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
#include <generated/interfaces/powermeter/Interface.hpp>
#include <generated/types/authorization.hpp>
#include <generated/types/evse_manager.hpp>
#include <generated/types/units_signed.hpp>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <sigslot/signal.hpp>
#include <string>
#include <vector>

#include "ErrorHandling.hpp"
#include "EventQueue.hpp"
#include "IECStateMachine.hpp"
#include "PersistentStore.hpp"
#include "scoped_lock_timeout.hpp"
#include "utils.hpp"

namespace module {

const std::string IEC62196Type2Cable = "IEC62196Type2Cable";
const std::string IEC62196Type2Socket = "IEC62196Type2Socket";

class Charger {
public:
    Charger(const std::unique_ptr<IECStateMachine>& bsp, const std::unique_ptr<ErrorHandling>& error_handling,
            const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing,
            const std::unique_ptr<PersistentStore>& store,
            const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id);
    ~Charger();

    enum class ChargeMode {
        AC,
        DC
    };

    enum class EvseState {
        Disabled,
        Idle,
        WaitingForAuthentication,
        PrepareCharging,
        Charging,
        ChargingPausedEV,
        ChargingPausedEVSE,
        StoppingCharging,
        Finished,
        T_step_EF,
        T_step_X1,
        SwitchPhases
    };

    enum class HlcTerminatePause {
        Unknown,
        Terminate,
        Pause
    };

    // Public interface to configure Charger
    //
    // Call anytime also during charging, but call setters in this block at
    // least initially once.
    //

    // external input to charger: update max_current and new validUntil
    bool set_max_current(float ampere, std::chrono::time_point<std::chrono::steady_clock> validUntil);
    float get_max_current();

    sigslot::signal<float> signal_max_current;

    void setup(bool has_ventilation, const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent,
               bool ac_enforce_hlc, bool ac_with_soc_timeout, float soft_over_current_tolerance_percent,
               float soft_over_current_measurement_noise_A, const int switch_3ph1ph_delay_s,
               const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms,
               const int _state_F_after_fault_ms, const bool fail_on_powermeter_errors, const bool raise_mrec9,
               const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type,
               const int hlc_charge_loop_without_energy_timeout_s);

    void enable_disable_initial_state_publish();
    bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source);

    // Public interface during charging
    //
    // Call anytime, but mostly used during charging session
    //

    // Returns active session_uuid. Returns empty string if not session is active
    std::string get_session_id() const;

    // call when in state WaitingForAuthentication
    void authorize(bool a, const types::authorization::ProvidedIdToken& token,
                   const types::authorization::ValidationResult& result);
    bool deauthorize();

    bool get_authorized_pnc();
    bool get_authorized_eim();

    // this indicates the charger is done with all of its t_step_XX routines and HLC can now also start charging
    bool get_authorized_eim_ready_for_hlc();
    bool get_authorized_pnc_ready_for_hlc();

    // trigger replug sequence while charging to switch number of phases
    bool switch_three_phases_while_charging(bool n);

    bool pause_charging();
    bool resume_charging();

    bool cancel_transaction(const types::evse_manager::StopTransactionRequest&
                                request); // cancel transaction ahead of time when car is still plugged

    void set_current_drawn_by_vehicle(float l1, float l2, float l3);

    // Signal for EvseEvents
    sigslot::signal<types::evse_manager::SessionEventEnum> signal_simple_event;
    sigslot::signal<std::string> signal_session_resumed_event;
    sigslot::signal<types::evse_manager::StartSessionReason, std::optional<types::authorization::ProvidedIdToken>>
        signal_session_started_event;
    sigslot::signal<types::authorization::ProvidedIdToken> signal_transaction_started_event;
    sigslot::signal<types::evse_manager::StopTransactionReason, std::optional<types::authorization::ProvidedIdToken>>
        signal_transaction_finished_event;
    sigslot::signal<types::evse_manager::ChargingPausedEVSEReasons> signal_charging_paused_evse_event;

    sigslot::signal<> signal_ac_with_soc_timeout;

    sigslot::signal<> signal_dc_supply_off;
    sigslot::signal<types::iso15118::DcEvseMaximumLimits> signal_dc_enforce_target_limits;
    sigslot::signal<> signal_slac_reset;
    sigslot::signal<> signal_slac_start;

    sigslot::signal<> signal_hlc_stop_charging;
    sigslot::signal<> signal_hlc_pause_charging;
    sigslot::signal<types::iso15118::EvseError> signal_hlc_error;
    sigslot::signal<> signal_hlc_plug_in_timeout;

    sigslot::signal<> signal_hlc_no_energy_available;

    void run();

    void request_error_sequence();

    void set_matching_started(bool m);

    void notify_currentdemand_started();
    void reset_dc_enforce_target_limits_timer();

    std::string evse_state_to_string(EvseState s);

    EvseState get_current_state();
    sigslot::signal<EvseState> signal_state;

    void inform_new_evse_max_hlc_limits(const types::iso15118::DcEvseMaximumLimits& l);
    types::iso15118::DcEvseMaximumLimits get_evse_max_hlc_limits();

    void inform_new_evse_min_hlc_limits(const types::iso15118::DcEvseMinimumLimits& limits);
    types::iso15118::DcEvseMinimumLimits get_evse_min_hlc_limits();

    void dlink_pause();
    void dlink_error();
    void dlink_terminate();

    void set_hlc_charging_active();
    void set_hlc_allow_close_contactor(bool on);

    void set_hlc_d20_active();

    bool stop_charging_on_fatal_error();
    bool entered_fatal_error_state();
    int time_in_fatal_error_state_ms();

    /// @brief Returns the OCMF start data.
    ///
    /// The data is generated when starting the transaction. The call resets the
    /// internal variable and is thus not idempotent.
    std::optional<types::units_signed::SignedMeterValue> get_start_signed_meter_value();

    /// @brief Returns the OCMF stop data.
    ///
    /// The data is generated when stopping the transaction. The call resets the
    /// internal variable and is thus not idempotent.
    std::optional<types::units_signed::SignedMeterValue> get_stop_signed_meter_value();

    types::evse_manager::EnableDisableSource get_last_enable_disable_source();

    utils::Stopwatch& get_stopwatch() {
        return stopwatch;
    }

    void set_connector_type(types::evse_board_support::Connector_type t) {
        connector_type = t;
    }

    std::optional<types::evse_manager::StopTransactionReason> get_last_stop_transaction_reason();

    void cleanup_transactions_on_startup();
    EventQueue<CPEvent> bsp_event_queue;

private:
    utils::Stopwatch stopwatch;

    std::optional<types::units_signed::SignedMeterValue>
    take_signed_meter_data(std::optional<types::units_signed::SignedMeterValue>& data);

    bool stop_charging_on_fatal_error_internal();
    float get_max_current_internal();
    float get_max_current_signalled_to_ev_internal();
    bool deauthorize_internal();
    bool pause_charging_wait_for_power_internal();

    void bcb_toggle_reset();
    void bcb_toggle_detect_start_pulse();
    void bcb_toggle_detect_stop_pulse();
    bool bcb_toggle_detected();

    void clear_errors_on_unplug();

    void update_pwm_now(float duty_cycle);
    void update_pwm_now_if_changed(float duty_cycle);
    void update_pwm_now_if_changed_ampere(float duty_cycle);
    void update_pwm_max_every_5seconds_ampere(float duty_cycle);
    void cp_state_X1();
    void cp_state_F();

    void process_cp_events_independent(CPEvent cp_event);
    void process_cp_events_state(CPEvent cp_event);

    void main_thread();
    void error_thread();

    void emergency_shutdown();
    void error_shutdown();

    float ampere_to_duty_cycle(float ampere);

    void check_soft_over_current();

    bool power_available();

    void start_session(bool authfirst);
    void stop_session();

    bool start_transaction();
    void stop_transaction();

    void process_event(CPEvent event);

    void set_state(EvseState s);

    // This mutex locks all variables related to the state machine
    Everest::timed_mutex_traceable state_machine_mutex;

    // used by different threads, complete main loop must be locked for write access
    struct SharedContext {
        // As per IEC61851-1 A.5.3
        bool legacy_wakeup_done{false};
        bool hlc_allow_close_contactor{false};
        bool iec_allow_close_contactor{false};
        bool contactor_open{true};
        bool hlc_charging_active{false};
        HlcTerminatePause hlc_charging_terminate_pause;
        types::iso15118::DcEvseMaximumLimits current_evse_max_limits;
        types::iso15118::DcEvseMinimumLimits current_evse_min_limits;
        bool pwm_running{false};
        std::optional<types::authorization::ProvidedIdToken>
            stop_transaction_id_token; // only set in case transaction was stopped locally
        types::authorization::ProvidedIdToken id_token;
        types::authorization::ValidationResult validation_result;
        std::atomic_bool flag_authorized{false};
        std::atomic_bool flag_externally_cancelled{false};
        std::atomic_bool flag_paused_by_evse{false};
        std::atomic_bool flag_ev_plugged_in{false};
        // set to true if auth is from PnC, otherwise to false (EIM)
        bool authorized_pnc;
        bool matching_started;
        float max_current;
        std::chrono::time_point<std::chrono::steady_clock> max_current_valid_until;
        std::optional<double> max_current_cable;
        std::atomic_bool flag_transaction_active;
        bool session_active;
        std::string session_uuid;
        bool connector_enabled;
        // Set when disable is requested while a session/transaction is active.
        // Tells the state machine to transition to Disabled once the session is
        // properly terminated instead of returning to Idle.
        bool flag_disable_requested{false};
        EvseState current_state;
        std::optional<types::evse_manager::StopTransactionReason> last_stop_transaction_reason;
        types::evse_manager::StartSessionReason last_start_session_reason;
        float current_drawn_by_vehicle[3];
        ShutdownType shutdown_type{ShutdownType::None};
        ShutdownType last_shutdown_type{ShutdownType::None};
        int ac_with_soc_timer;
        // non standard compliant option: time out after a while and switch back to DC to get SoC update
        bool ac_with_soc_timeout;
        bool contactor_welded{false};
        bool switch_3ph1ph_threephase{false};
        bool switch_3ph1ph_threephase_ongoing{false};

        std::optional<types::units_signed::SignedMeterValue> stop_signed_meter_value;
        std::optional<types::units_signed::SignedMeterValue> start_signed_meter_value;

        std::atomic_bool hlc_d20_active{false};
    } shared_context;

    struct ConfigContext {
        // non standard compliant option to enforce HLC in AC mode
        bool ac_enforce_hlc;
        // Config option to use 5 percent PWM in HLC AC mode
        bool ac_hlc_use_5percent;
        // Config option to enable HLC in AC mode
        bool ac_hlc_enabled;
        // AC or DC
        ChargeMode charge_mode{0};
        // Delay when switching from 1ph to 3ph or 3ph to 1ph
        int switch_3ph1ph_delay_s{10};
        // Use state F if true, otherwise use X1
        bool switch_3ph1ph_cp_state_F{false};
        // Tolerate soft over current for given time
        int soft_over_current_timeout_ms{7000};
        // Switch to F for configured ms after a fatal error
        int state_F_after_fault_ms{300};
        // Fail on powermeter errors
        bool fail_on_powermeter_errors;
        // Raise MREC9 authorization timeout error
        bool raise_mrec9;
        // sleep before enabling pwm in hlc mode
        int sleep_before_enabling_pwm_hlc_mode_ms{1000};
        // type used to generate session ids
        utils::SessionIdType session_id_type{utils::SessionIdType::UUID};
        // Timeout in seconds that defines for how long the EVSE allows the ISO charge loop (AC: ChargingStatus, DC:
        // CurrentDemand)
        int hlc_charge_loop_without_energy_timeout_s{300};
    } config_context;

    // Used by different threads, but requires no complete state machine locking
    std::atomic<float> soft_over_current_tolerance_percent{10.};
    std::atomic<float> soft_over_current_measurement_noise_A{0.5};
    // HLC uses 5 percent signalling. Used both for AC and DC modes.
    std::atomic_bool hlc_use_5percent_current_session;
    // HLC enabled in current AC session. This can change during the session if e.g. HLC fails.
    std::atomic_bool ac_hlc_enabled_current_session;

    // This struct is only used from main loop thread
    struct InternalContext {
        bool hlc_bcb_sequence_started{false};
        int hlc_ev_pause_bcb_count{0};
        std::chrono::time_point<std::chrono::steady_clock> hlc_ev_pause_start_of_bcb;
        std::chrono::time_point<std::chrono::steady_clock> hlc_ev_pause_start_of_bcb_sequence;
        float update_pwm_last_duty_cycle;
        std::chrono::time_point<std::chrono::steady_clock> last_pwm_update;

        EvseState t_step_EF_return_state;
        float t_step_EF_return_pwm;
        float t_step_EF_return_ampere;

        EvseState switching_phases_return_state;

        EvseState t_step_X1_return_state;
        float t_step_X1_return_pwm;
        std::chrono::time_point<std::chrono::steady_clock> last_over_current_event;
        bool over_current{false};

        ShutdownType last_shutdown_type{ShutdownType::None};
        std::chrono::system_clock::time_point current_state_started;
        EvseState last_state_detect_state_change;
        EvseState last_state;

        bool pp_warning_printed{false};
        bool no_energy_warning_printed{false};
        float pwm_set_last_ampere{0};
        bool t_step_ef_x1_pause{false};
        bool cp_state_F_active{false};

        bool ac_x1_fallback_nominal_timeout_running{false};
        std::chrono::time_point<std::chrono::steady_clock> ac_x1_fallback_nominal_timeout_started;
        bool auth_received_printed{false};

        bool hlc_charge_loop_no_energy_timeout_running{false};
        std::chrono::time_point<std::chrono::steady_clock> iso_charge_loop_no_energy_timeout_started;

        std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
        bool fatal_error_timer_running{false};
        bool dc_statistics_printed{false};

        types::evse_manager::ChargingPausedEVSEReasons last_charging_paused_evse_reasons;
    } internal_context;

    // main Charger thread
    Everest::Thread main_thread_handle;
    Everest::Thread error_thread_handle;

    std::atomic<std::chrono::steady_clock::time_point> last_dc_enforce_target_limits{};

    const std::unique_ptr<IECStateMachine>& bsp;
    const std::unique_ptr<ErrorHandling>& error_handling;
    const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
    const std::unique_ptr<PersistentStore>& store;
    std::atomic<types::evse_board_support::Connector_type> connector_type{
        types::evse_board_support::Connector_type::IEC62196Type2Cable};
    const std::string evse_id;

    EventQueue<ErrorHandlingEvents> error_handling_event_queue;

    // constants
    constexpr static int LEGACY_WAKEUP_TIMEOUT{30000};
    constexpr static int PREPARING_TIMEOUT_PAUSED_BY_EV{10000};
    // valid Length of BCB toggles
    static constexpr auto TP_EV_VALD_STATE_DURATION_MIN =
        std::chrono::milliseconds(200 - 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3)
    static constexpr auto TP_EV_VALD_STATE_DURATION_MAX =
        std::chrono::milliseconds(400 + 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3)
    // Maximum duration of a BCB toggle sequence of 1-3 BCB toggles
    static constexpr auto TT_EVSE_VALD_TOGGLE =
        std::chrono::milliseconds(3500 + 200); // We give 200 msecs tolerance to the norm values (table 3 ISO15118-3)
    static constexpr auto MAINLOOP_UPDATE_RATE = std::chrono::milliseconds(100);
    static constexpr float PWM_5_PERCENT = 0.05;
    static constexpr int T_REPLUG_MS = 4000;
    // 3 seconds according to IEC61851-1
    static constexpr int T_STEP_X1 = 3000;
    // 4 seconds according to table 3 of ISO15118-3
    static constexpr int T_STEP_EF = 4000;
    static constexpr int IEC_PWM_MAX_UPDATE_INTERVAL = 5000;
    // EV READY certification requires a small pause of 500-1000 ms in X1 after a t_step_EF sequence before going to X2-
    // This is not required by IEC61851-1, but it is allowed by the IEC. It helps some older EVs to start charging
    // after the wake-up sequence.
    static constexpr int STAY_IN_X1_AFTER_TSTEP_EF_MS = 750;
    static constexpr int WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS = 5000;
    static constexpr int AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS = 10000;
    static constexpr int STOPPING_CHARGING_TIMEOUT_MS = 20000;
    // Ensures apply_new_target_voltage_current() is called at least every DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS
    // during DC charging. This re-applies EVSE limits to the power supply even when the EV does not send
    // new target values or ignores updated limits from energy management.
    // The timer is reset whenever apply_new_target_voltage_current() is called from any source
    // (subscribe callback, energy manager, or this periodic signal).
    static constexpr int DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS = 5000;

    types::evse_manager::EnableDisableSource active_enable_disable_source{
        types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000};
    std::vector<types::evse_manager::EnableDisableSource> enable_disable_source_table;
    bool parse_enable_disable_source_table();
    void enable_disable_source_table_update(const types::evse_manager::EnableDisableSource& source);

protected:
    // provide access for unit tests
    void run_state_machine();
    constexpr auto& get_shared_context() {
        return shared_context;
    }
    constexpr const auto& get_enable_disable_source_table() const {
        return enable_disable_source_table;
    }
};

} // namespace module

#endif // SRC_EVDRIVERS_CHARGER_H_
