// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception
// Copyright (C) 2025 Autonomy / OpenPLC Project
// This file is part of the STruC++ Runtime Library and is covered by the
// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details.
/**
 * STruC++ Runtime - IEC Enumeration Support
 *
 * This header provides infrastructure for IEC 61131-3 enumeration types.
 * Enumerations in IEC 61131-3 are strongly typed, similar to C++ enum class.
 * Actual enum definitions are generated by the compiler based on user TYPE declarations.
 */

#pragma once

#include <cstdint>
#ifndef __AVR__
#include <ostream>
#endif
#include <type_traits>
#include "iec_var.hpp"

namespace strucpp {

/**
 * Value wrapper for IEC enumerations.
 * Provides a type-safe wrapper around C++ enum class types.
 *
 * @tparam EnumType The underlying C++ enum class type
 */
template<typename EnumType>
class IEC_ENUM_Value {
private:
    EnumType value_;
    
public:
    using enum_type = EnumType;
    
    // Default constructor - initializes to first enum value (0)
    constexpr IEC_ENUM_Value() noexcept : value_{} {}
    
    // Constructor from enum value
    constexpr IEC_ENUM_Value(EnumType val) noexcept : value_(val) {}
    
    // Implicit conversion to underlying enum
    constexpr operator EnumType() const noexcept { return value_; }
    
    // Get the underlying enum value
    constexpr EnumType get() const noexcept { return value_; }
    
    // Assignment from enum value
    IEC_ENUM_Value& operator=(EnumType val) noexcept {
        value_ = val;
        return *this;
    }
    
    // Comparison operators
    constexpr bool operator==(const IEC_ENUM_Value& other) const noexcept {
        return value_ == other.value_;
    }
    
    constexpr bool operator!=(const IEC_ENUM_Value& other) const noexcept {
        return value_ != other.value_;
    }
    
    constexpr bool operator==(EnumType val) const noexcept {
        return value_ == val;
    }
    
    constexpr bool operator!=(EnumType val) const noexcept {
        return value_ != val;
    }
    
    // Ordering operators (for enums with numeric values)
    constexpr bool operator<(const IEC_ENUM_Value& other) const noexcept {
        return static_cast<int>(value_) < static_cast<int>(other.value_);
    }
    
    constexpr bool operator<=(const IEC_ENUM_Value& other) const noexcept {
        return static_cast<int>(value_) <= static_cast<int>(other.value_);
    }
    
    constexpr bool operator>(const IEC_ENUM_Value& other) const noexcept {
        return static_cast<int>(value_) > static_cast<int>(other.value_);
    }
    
    constexpr bool operator>=(const IEC_ENUM_Value& other) const noexcept {
        return static_cast<int>(value_) >= static_cast<int>(other.value_);
    }
    
    // Convert to integer (for serialization, debugging)
    constexpr int to_int() const noexcept {
        return static_cast<int>(value_);
    }
};

/**
 * IEC enumeration variable with forcing support.
 * Wraps IEC_ENUM_Value in IECVar for debugging capabilities.
 *
 * @tparam EnumType The underlying C++ enum class type
 */
template<typename EnumType>
class IEC_ENUM_Var {
public:
    using value_type = IEC_ENUM_Value<EnumType>;
    using enum_type = EnumType;
    
private:
    value_type value_;
    bool forced_;
    value_type forced_value_;
    
public:
    IEC_ENUM_Var() noexcept : value_{}, forced_{false}, forced_value_{} {}

    // Non-explicit so raw `EnumType` values can implicitly convert at call
    // sites — matches the implicit `IECVar(T v)` constructor for elementary
    // types. Without this, codegen would have to wrap every raw enum
    // literal at the call site, since enum-typed variables are now
    // declared as `IEC_<name>` (= IEC_ENUM_Var<EnumType>).
    IEC_ENUM_Var(EnumType val) noexcept
        : value_{val}, forced_{false}, forced_value_{} {}

    IEC_ENUM_Var(value_type val) noexcept
        : value_{val}, forced_{false}, forced_value_{} {}
    
    IEC_ENUM_Var(const IEC_ENUM_Var&) = default;
    IEC_ENUM_Var(IEC_ENUM_Var&&) = default;
    IEC_ENUM_Var& operator=(const IEC_ENUM_Var&) = default;
    IEC_ENUM_Var& operator=(IEC_ENUM_Var&&) = default;
    
    // Get current value (returns forced value if forced)
    value_type get() const noexcept {
        return forced_ ? forced_value_ : value_;
    }
    
    // Set value (ignored if forced)
    void set(value_type v) noexcept {
        value_ = v;
    }
    
    void set(EnumType v) noexcept {
        value_ = v;
    }
    
    // Get underlying value (ignoring forcing)
    value_type get_underlying() const noexcept {
        return value_;
    }
    
    // Force to a specific value
    void force(value_type v) noexcept {
        forced_ = true;
        forced_value_ = v;
    }
    
    void force(EnumType v) noexcept {
        forced_ = true;
        forced_value_ = v;
    }
    
    // Remove forcing
    void unforce() noexcept {
        forced_ = false;
    }
    
    // Check if forced
    bool is_forced() const noexcept {
        return forced_;
    }
    
    // Get forced value
    value_type get_forced_value() const noexcept {
        return forced_value_;
    }
    
    // Implicit conversion to value_type
    operator value_type() const noexcept {
        return get();
    }
    
    // Implicit conversion to enum_type
    operator EnumType() const noexcept {
        return get().get();
    }
    
    // Assignment operators
    IEC_ENUM_Var& operator=(value_type v) noexcept {
        set(v);
        return *this;
    }
    
    IEC_ENUM_Var& operator=(EnumType v) noexcept {
        set(v);
        return *this;
    }
    
    // Comparison operators
    bool operator==(const IEC_ENUM_Var& other) const noexcept {
        return get() == other.get();
    }
    
    bool operator!=(const IEC_ENUM_Var& other) const noexcept {
        return get() != other.get();
    }
    
    bool operator==(EnumType val) const noexcept {
        return get() == val;
    }
    
    bool operator!=(EnumType val) const noexcept {
        return get() != val;
    }
};

/**
 * Convenience alias for IEC enumeration with forcing support.
 * Usage: IEC_ENUM<MyEnumClass> myVar;
 */
template<typename EnumType>
using IEC_ENUM = IEC_ENUM_Var<EnumType>;

#ifndef __AVR__
// Stream output for IEC_ENUM_Var — outputs underlying integer value
template<typename EnumType>
inline std::ostream& operator<<(std::ostream& os, const IEC_ENUM_Var<EnumType>& v) {
    return os << static_cast<typename std::underlying_type<EnumType>::type>(
        static_cast<EnumType>(v));
}
#endif

/*
 * Example generated enumeration:
 *
 * ST Source:
 *   TYPE TrafficLight : (RED, YELLOW, GREEN); END_TYPE
 *
 * Generated C++:
 *   enum class TrafficLight : int16_t {
 *       RED = 0,
 *       YELLOW = 1,
 *       GREEN = 2
 *   };
 *   using TrafficLight_Value = IEC_ENUM_Value<TrafficLight>;
 *   using TrafficLight_Var = IEC_ENUM_Var<TrafficLight>;
 *
 * Usage:
 *   TrafficLight_Var light;
 *   light = TrafficLight::RED;
 *   
 *   if (light == TrafficLight::RED) {
 *       // Handle red light
 *   }
 *   
 *   // Force for debugging
 *   light.force(TrafficLight::GREEN);
 *   light = TrafficLight::RED;  // Ignored
 *   assert(light == TrafficLight::GREEN);
 */

/*
 * Example typed enumeration (IEC v3):
 *
 * ST Source:
 *   TYPE Status : INT (IDLE := 0, RUNNING := 1, ERROR := -1); END_TYPE
 *
 * Generated C++:
 *   enum class Status : int16_t {
 *       IDLE = 0,
 *       RUNNING = 1,
 *       ERROR = -1
 *   };
 *   using Status_Value = IEC_ENUM_Value<Status>;
 *   using Status_Var = IEC_ENUM_Var<Status>;
 */

}  // namespace strucpp
