// 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 - Located Variables Support
 *
 * This header defines the types and structures needed for located variables
 * (AT %IX0.0, %QX0.0, %MW100, etc.) that bind to runtime I/O image tables.
 *
 * Located variables provide the interface between IEC programs and physical
 * I/O in PLC systems. The compiler generates a descriptor array that the
 * runtime uses to bind variables to I/O addresses.
 */

#pragma once

#include <cstddef>
#include <cstdint>
#include "iec_fault.hpp"
#if STRUCPP_HAS_EXCEPTIONS
#include <stdexcept>
#endif

namespace strucpp {

// =============================================================================
// Located Variable Area Types
// =============================================================================

/**
 * Memory area for located variables.
 * Corresponds to the first letter after % in IEC addresses:
 * - I = Input (read from physical inputs)
 * - Q = Output (write to physical outputs)
 * - M = Memory (internal markers/flags)
 */
enum class LocatedArea : uint8_t {
    Input  = 0,  // %I - Input area
    Output = 1,  // %Q - Output area
    Memory = 2   // %M - Memory/Marker area
};

// =============================================================================
// Located Variable Size Types
// =============================================================================

/**
 * Data size for located variables.
 * Corresponds to the size specifier in IEC addresses:
 * - X = Bit (1 bit)
 * - B = Byte (8 bits)
 * - W = Word (16 bits)
 * - D = Double Word (32 bits)
 * - L = Long Word (64 bits)
 */
enum class LocatedSize : uint8_t {
    Bit   = 0,  // %?X - Single bit
    Byte  = 1,  // %?B - 8 bits
    Word  = 2,  // %?W - 16 bits
    DWord = 3,  // %?D - 32 bits
    LWord = 4   // %?L - 64 bits
};

// =============================================================================
// Located Variable Descriptor
// =============================================================================

/**
 * Descriptor for a located variable.
 *
 * This structure is generated by the compiler for each located variable
 * in the program. The runtime uses this descriptor array to:
 * 1. Bind variables to I/O image tables at startup
 * 2. Copy values between I/O images and variables during scan cycle
 * 3. Support variable forcing for debugging
 *
 * Memory Layout (16 bytes, aligned):
 * - area: 1 byte
 * - size: 1 byte
 * - byte_index: 2 bytes (major address component)
 * - bit_index: 1 byte (minor address component, 0-7 for bits)
 * - reserved: 3 bytes (padding for alignment)
 * - pointer: 8 bytes (pointer to variable storage)
 *
 * Address Format:
 *   %<area><size><byte_index>.<bit_index>
 *
 * Examples:
 *   %IX0.5   -> area=Input, size=Bit, byte_index=0, bit_index=5
 *   %QW10    -> area=Output, size=Word, byte_index=10, bit_index=0
 *   %MD100   -> area=Memory, size=DWord, byte_index=100, bit_index=0
 */
struct LocatedVar {
    LocatedArea area;       ///< Memory area (I, Q, or M)
    LocatedSize size;       ///< Data size (X, B, W, D, or L)
    uint16_t byte_index;    ///< Byte offset in the I/O image
    uint8_t bit_index;      ///< Bit offset within byte (0-7, only for X size)
    uint8_t _reserved[3];   ///< Padding for alignment
    void* pointer;          ///< Pointer to the variable's raw storage

    /**
     * Check if this descriptor represents a bit-addressed variable.
     */
    constexpr bool is_bit() const noexcept {
        return size == LocatedSize::Bit;
    }

    /**
     * Get the size in bytes for this variable.
     * Returns 0 for bit-addressed variables (handled specially).
     */
    constexpr size_t byte_size() const noexcept {
        switch (size) {
            case LocatedSize::Bit:   return 0;  // Special handling
            case LocatedSize::Byte:  return 1;
            case LocatedSize::Word:  return 2;
            case LocatedSize::DWord: return 4;
            case LocatedSize::LWord: return 8;
            default: return 0;
        }
    }
};

// Verify expected layout (size varies by platform: 16 bytes on 64-bit, 8 on 32-bit, 6 on AVR)
#if INTPTR_MAX == INT64_MAX
static_assert(sizeof(LocatedVar) == 16, "LocatedVar should be 16 bytes on 64-bit");
static_assert(alignof(LocatedVar) == 8, "LocatedVar should be 8-byte aligned on 64-bit");
#endif

// =============================================================================
// Helper Functions for Address Parsing
// =============================================================================

/**
 * Parse an area character to LocatedArea enum.
 * @param c The area character ('I', 'Q', or 'M')
 * @return The corresponding LocatedArea value
 * @throws std::invalid_argument if character is invalid
 */
inline LocatedArea parse_area(char c) {
    switch (c) {
        case 'I': case 'i': return LocatedArea::Input;
        case 'Q': case 'q': return LocatedArea::Output;
        case 'M': case 'm': return LocatedArea::Memory;
#if STRUCPP_HAS_EXCEPTIONS
        default: throw std::invalid_argument("Invalid area character");
#else
        default: iec_runtime_fault(IecFault::BadLocation, "Invalid area character");
#endif
    }
}

/**
 * Parse a size character to LocatedSize enum.
 * @param c The size character ('X', 'B', 'W', 'D', or 'L')
 * @return The corresponding LocatedSize value
 * @throws std::invalid_argument if character is invalid
 */
inline LocatedSize parse_size(char c) {
    switch (c) {
        case 'X': case 'x': return LocatedSize::Bit;
        case 'B': case 'b': return LocatedSize::Byte;
        case 'W': case 'w': return LocatedSize::Word;
        case 'D': case 'd': return LocatedSize::DWord;
        case 'L': case 'l': return LocatedSize::LWord;
#if STRUCPP_HAS_EXCEPTIONS
        default: throw std::invalid_argument("Invalid size character");
#else
        default: iec_runtime_fault(IecFault::BadLocation, "Invalid size character");
#endif
    }
}

/**
 * Get the area character for a LocatedArea enum.
 * @param area The LocatedArea value
 * @return The corresponding character ('I', 'Q', or 'M')
 */
constexpr char area_to_char(LocatedArea area) noexcept {
    switch (area) {
        case LocatedArea::Input:  return 'I';
        case LocatedArea::Output: return 'Q';
        case LocatedArea::Memory: return 'M';
        default: return '?';
    }
}

/**
 * Get the size character for a LocatedSize enum.
 * @param size The LocatedSize value
 * @return The corresponding character ('X', 'B', 'W', 'D', or 'L')
 */
constexpr char size_to_char(LocatedSize size) noexcept {
    switch (size) {
        case LocatedSize::Bit:   return 'X';
        case LocatedSize::Byte:  return 'B';
        case LocatedSize::Word:  return 'W';
        case LocatedSize::DWord: return 'D';
        case LocatedSize::LWord: return 'L';
        default: return '?';
    }
}

// =============================================================================
// Located Variable Table Marker
// =============================================================================

/**
 * End marker for the located variable descriptor array.
 * The compiler generates this as the last entry in the array.
 * Runtime scans the array until it finds this marker (pointer == nullptr).
 */
constexpr LocatedVar LOCATED_VAR_END = {
    LocatedArea::Input,  // Doesn't matter
    LocatedSize::Bit,    // Doesn't matter
    0,                   // byte_index
    0,                   // bit_index
    {0, 0, 0},           // reserved
    nullptr              // End marker
};

}  // namespace strucpp
