/**
 * MechBase PLC — Ladder Logic Interpreter v2 Implementation
 *
 * IEC 61131-3 ladder logic interpreter for ESP32.
 *
 * Contact instructions: NO, NC, Rising Edge, Falling Edge, Timer bits (EN/TT/DN)
 * Output instructions:  OTE, OTL, OTU, TON, TOF, TP
 * Network structure:    Parallel branching, series/parallel evaluation
 */

#include "ladder_interpreter.h"

// ─────────────────────────────────────────────────────────────────────────────
// Branch evaluation
// ─────────────────────────────────────────────────────────────────────────────
bool Branch::evaluate(LadderInterpreter* interp, bool is_output) {
    power_flow = true;

    for (auto& elem : elements) {
        bool result = false;

        if (is_output) {
            // ── Output instructions ──────────────────────────────────────
            switch (static_cast<OutputType>(elem.type)) {
                case OT_OTE: {
                    // Output Energize — follows input power
                    power_flow = false; // OTE is terminal, no more flow
                    interp->writeBool(elem.tag, true);
                    return true;
                }
                case OT_OTL: {
                    // Output Latch (SET) — stays ON once set
                    power_flow = false;
                    if (this->power_flow) {
                        interp->writeBool(elem.tag, true);
                    }
                    return true;
                }
                case OT_OTU: {
                    // Output Unlatch (RESET) — clears latched output
                    power_flow = false;
                    if (this->power_flow) {
                        interp->writeBool(elem.tag, false);
                    }
                    return true;
                }
                default:
                    break;
            }
        }
        else {
            // ── Contact instructions ─────────────────────────────────────
            switch (static_cast<ContactType>(elem.type)) {
                case CT_NO: {
                    // Normally Open — passes when TRUE
                    result = interp->readBool(elem.tag);
                    break;
                }
                case CT_NC: {
                    // Normally Closed — passes when FALSE
                    result = !interp->readBool(elem.tag);
                    break;
                }
                case CT_RISING: {
                    // Rising Edge — single scan TRUE→FALSE
                    bool current = interp->readBool(elem.tag);
                    result = (current && !elem.last_state);
                    elem.last_state = current;
                    break;
                }
                case CT_FALLING: {
                    // Falling Edge — single scan FALSE→TRUE
                    bool current = interp->readBool(elem.tag);
                    result = (!current && elem.last_state);
                    elem.last_state = current;
                    break;
                }
                case CT_TON_DN:
                case CT_TON_TT:
                case CT_TON_EN: {
                    // TON status bits
                    uint8_t tidx = interp->parseTimerIndex(elem.tag);
                    LadderTimer& t = interp->getTimer(tidx);
                    switch (elem.type) {
                        case CT_TON_DN: result = t.dn; break;
                        case CT_TON_TT: result = t.tt; break;
                        case CT_TON_EN: result = t.en; break;
                        default: result = false;
                    }
                    break;
                }
                case CT_TOF_DN:
                case CT_TOF_TT:
                case CT_TOF_EN: {
                    // TOF status bits
                    uint8_t tidx = interp->parseTimerIndex(elem.tag);
                    LadderTimer& t = interp->getTimer(tidx);
                    switch (elem.type) {
                        case CT_TOF_DN: result = t.dn; break;
                        case CT_TOF_TT: result = t.tt; break;
                        case CT_TOF_EN: result = t.en; break;
                        default: result = false;
                    }
                    break;
                }
                case CT_TP_DN:
                case CT_TP_TT:
                case CT_TP_EN: {
                    // TP status bits
                    uint8_t tidx = interp->parseTimerIndex(elem.tag);
                    LadderTimer& t = interp->getTimer(tidx);
                    switch (elem.type) {
                        case CT_TP_DN: result = t.dn; break;
                        case CT_TP_TT: result = t.tt; break;
                        case CT_TP_EN: result = t.en; break;
                        default: result = false;
                    }
                    break;
                }
                default:
                    result = false;
                    break;
            }
        }

        // For contacts, if any element fails, branch fails
        if (!is_output && !result) {
            power_flow = false;
            break;
        }

        // For outputs, we've already handled the instruction
        if (is_output) {
            break;
        }
    }

    return power_flow;
}

// ─────────────────────────────────────────────────────────────────────────────
// Network evaluation
// ─────────────────────────────────────────────────────────────────────────────
bool Network::evaluate(LadderInterpreter* interp) {
    power_flow = false;

    // ── Phase 1: Evaluate input branches (OR'd together) ─────────────────
    // Any branch with power flow means the network has power
    for (auto& branch : input_branches) {
        if (branch.evaluate(interp, false)) {
            power_flow = true;
            break;  // One branch is enough for OR logic
        }
    }

    // ── Phase 2: If power flows, execute output branches ─────────────────
    if (power_flow) {
        for (auto& branch : output_branches) {
            branch.power_flow = true;  // Pass power to output branch
            branch.evaluate(interp, true);
        }
    }

    return power_flow;
}

// ─────────────────────────────────────────────────────────────────────────────
// LadderInterpreter constructor / destructor
// ─────────────────────────────────────────────────────────────────────────────
LadderInterpreter::LadderInterpreter()
    : running(false), simulating(false), scan_start(0) {
    // Zero out all arrays
    for (int i = 0; i < MAX_DIGITAL_INPUTS; i++) {
        di[i] = false;
    }
    for (int i = 0; i < MAX_DIGITAL_OUTPUTS; i++) {
        do_[i] = false;
        do_prev[i] = false;
    }
    for (int i = 0; i < MAX_BOOL_REGISTERS; i++) {
        m_bool[i] = false;
    }
    for (int i = 0; i < MAX_INT_REGISTERS; i++) {
        m_int[i] = 0;
    }
    for (int i = 0; i < MAX_TIMER_COUNT; i++) {
        timers[i] = LadderTimer{
            (uint8_t)i, 0, 0, false, false, false
        };
    }
    for (int i = 0; i < MAX_COUNTER_COUNT; i++) {
        counters[i] = LadderCounter{
            (uint8_t)i, 0, 0, false, false, false, false, false, false
        };
    }
}

LadderInterpreter::~LadderInterpreter() {
}

// ─────────────────────────────────────────────────────────────────────────────
// Lifecycle
// ─────────────────────────────────────────────────────────────────────────────
void LadderInterpreter::begin() {
    scan_start = millis();
    Serial.println(F("MechBase PLC v2 — Ladder Interpreter Initialized"));
    Serial.println(F("Instructions: NO NC Rising Falling | OTE OTL OTU | TON TOF TP"));
}

void LadderInterpreter::scan() {
    if (!running) return;

    scan_start = millis();

    // Phase 1: Evaluate all networks (rung-by-rung)
    for (auto& net : networks) {
        net.evaluate(this);
    }

    // Phase 2: Update timers
    // Timers are updated in the output branch evaluation,
    // but we also need to track elapsed time per scan
    for (auto& timer : timers) {
        if (timer.tt) {
            timer.elapsed = min(timer.elapsed + (millis() - scan_start),
                                (uint32_t)(timer.preset + 1));
        }
    }

    // Phase 3: Update counters
    for (auto& counter : counters) {
        counter.update();
    }

    // Phase 4: Write physical outputs (skip in simulation)
    if (!simulating) {
        for (int i = 0; i < MAX_DIGITAL_OUTPUTS; i++) {
            if (do_[i] != do_prev[i]) {
                digitalWrite(i, do_[i] ? HIGH : LOW);
                do_prev[i] = do_[i];
            }
        }
    }
}

void LadderInterpreter::run() {
    running = true;
    Serial.println(F("PLC Running"));
}

void LadderInterpreter::stop() {
    running = false;
    Serial.println(F("PLC Stopped"));
}

// ─────────────────────────────────────────────────────────────────────────────
// I/O access
// ─────────────────────────────────────────────────────────────────────────────
void LadderInterpreter::setDI(uint8_t idx, bool val) {
    if (idx < MAX_DIGITAL_INPUTS) di[idx] = val;
}

bool LadderInterpreter::getDI(uint8_t idx) {
    if (idx < MAX_DIGITAL_INPUTS) return di[idx];
    return false;
}

bool LadderInterpreter::getDO(uint8_t idx) {
    if (idx < MAX_DIGITAL_OUTPUTS) return do_[idx];
    return false;
}

void LadderInterpreter::setDO(uint8_t idx, bool val) {
    if (idx < MAX_DIGITAL_OUTPUTS) do_[idx] = val;
}

// ─────────────────────────────────────────────────────────────────────────────
// Memory access
// ─────────────────────────────────────────────────────────────────────────────
void LadderInterpreter::setM(bool idx, bool val) {
    // idx is a bool, cast to int for indexing
    uint8_t i = (uint8_t)idx;
    if (i < MAX_BOOL_REGISTERS) m_bool[i] = val;
}

bool LadderInterpreter::getM(bool idx) {
    uint8_t i = (uint8_t)idx;
    if (i < MAX_BOOL_REGISTERS) return m_bool[i];
    return false;
}

void LadderInterpreter::setINT(uint8_t idx, int16_t val) {
    if (idx < MAX_INT_REGISTERS) m_int[idx] = val;
}

int16_t LadderInterpreter::getINT(uint8_t idx) {
    if (idx < MAX_INT_REGISTERS) return m_int[idx];
    return 0;
}

// ─────────────────────────────────────────────────────────────────────────────
// Tag helpers — parse "I0", "Q0", "M0", "T0", "INT0", "T0.DN", "T0.TT", etc.
// ─────────────────────────────────────────────────────────────────────────────
bool LadderInterpreter::readBool(const char* tag) {
    // Format: PREFIX[INDEX] or PREFIX[INDEX].BIT
    // e.g. "I0", "Q3", "M10", "T0.DN", "T0.TT", "T0.EN"

    if (tag[0] == 'I' || tag[0] == 'i') {
        int idx = atoi(&tag[1]);
        return getDI((uint8_t)idx);
    }
    if (tag[0] == 'Q' || tag[0] == 'q') {
        int idx = atoi(&tag[1]);
        return getDO((uint8_t)idx);
    }
    if (tag[0] == 'M' || tag[0] == 'm') {
        int idx = atoi(&tag[1]);
        return (bool)idx ? (idx < MAX_BOOL_REGISTERS && m_bool[idx]) : m_bool[0];
    }
    if (tag[0] == 'T' || tag[0] == 't') {
        int idx = atoi(&tag[1]);
        LadderTimer& t = getTimer((uint8_t)idx);

        // Check for bit suffix
        if (strlen(tag) > 2) {
            const char* dot = strchr(tag, '.');
            if (dot) {
                if (strcmp(dot + 1, "DN") == 0) return t.dn;
                if (strcmp(dot + 1, "TT") == 0) return t.tt;
                if (strcmp(dot + 1, "EN") == 0) return t.en;
            }
        }
        // Default: return DN bit
        return t.dn;
    }
    if (tag[0] == 'C' || tag[0] == 'c') {
        int idx = atoi(&tag[1]);
        LadderCounter& c = getCounter((uint8_t)idx);
        return c.dn;
    }

    return false;
}

void LadderInterpreter::writeBool(const char* tag, bool val) {
    if (tag[0] == 'Q' || tag[0] == 'q') {
        int idx = atoi(&tag[1]);
        setDO((uint8_t)idx, val);
    }
    else if (tag[0] == 'M' || tag[0] == 'm') {
        int idx = atoi(&tag[1]);
        if (idx < MAX_BOOL_REGISTERS) m_bool[idx] = val;
    }
    // Timers/counters written by their own update logic
}

int16_t LadderInterpreter::readInt(const char* tag) {
    if (tag[0] == 'I' && tag[1] == 'N') {
        // INT0..INT31
        int idx = atoi(&tag[3]);
        return getINT((uint8_t)idx);
    }
    return 0;
}

void LadderInterpreter::writeInt(const char* tag, int16_t val) {
    if (tag[0] == 'I' && tag[1] == 'N') {
        int idx = atoi(&tag[3]);
        setINT((uint8_t)idx, val);
    }
}

uint8_t LadderInterpreter::parseTimerIndex(const char* tag) {
    if (tag[0] == 'T' || tag[0] == 't') {
        return (uint8_t)atoi(&tag[1]);
    }
    return 0;
}

uint8_t LadderInterpreter::parseCounterIndex(const char* tag) {
    if (tag[0] == 'C' || tag[0] == 'c') {
        return (uint8_t)atoi(&tag[1]);
    }
    return 0;
}

// ─────────────────────────────────────────────────────────────────────────────
// Timer / Counter access
// ─────────────────────────────────────────────────────────────────────────────
LadderTimer& LadderInterpreter::getTimer(uint8_t idx) {
    if (idx < MAX_TIMER_COUNT) return timers[idx];
    static LadderTimer dummy = {};
    return dummy;
}

void LadderInterpreter::resetTimer(uint8_t idx) {
    if (idx < MAX_TIMER_COUNT) timers[idx].reset();
}

LadderCounter& LadderInterpreter::getCounter(uint8_t idx) {
    if (idx < MAX_COUNTER_COUNT) return counters[idx];
    static LadderCounter dummy = {};
    return dummy;
}

void LadderInterpreter::resetCounter(uint8_t idx) {
    if (idx < MAX_COUNTER_COUNT) counters[idx].reset();
}

// ─────────────────────────────────────────────────────────────────────────────
// Program loading
// ─────────────────────────────────────────────────────────────────────────────
Network& LadderInterpreter::addNetwork(uint16_t num) {
    Network net;
    net.number = num;
    net.power_flow = false;
    networks.push_back(net);
    return networks.back();
}

void LadderInterpreter::loadFromJSON(const char* json) {
    // TODO: JSON parsing for program loading
    Serial.println(F("loadFromJSON() called — JSON parser not yet implemented"));
}

// ─────────────────────────────────────────────────────────────────────────────
// Debug
// ─────────────────────────────────────────────────────────────────────────────
void LadderInterpreter::dumpState() {
    Serial.println(F("=== PLC State Dump ==="));
    Serial.print(F("Networks: ")); Serial.println(networks.size());
    Serial.print(F("Running: ")); Serial.println(running ? "YES" : "NO");

    Serial.print(F("Inputs (I0-I15): "));
    for (int i = 0; i < MAX_DIGITAL_INPUTS; i++) {
        Serial.print(di[i] ? '1' : '0');
        Serial.print(' ');
    }
    Serial.println();

    Serial.print(F("Outputs (Q0-Q15): "));
    for (int i = 0; i < MAX_DIGITAL_OUTPUTS; i++) {
        Serial.print(do_[i] ? '1' : '0');
        Serial.print(' ');
    }
    Serial.println();

    Serial.print(F("Memory (M0-M15): "));
    for (int i = 0; i < 16; i++) {
        Serial.print(m_bool[i] ? '1' : '0');
        Serial.print(' ');
    }
    Serial.println();

    Serial.print(F("Timers: "));
    for (int i = 0; i < 4; i++) {
        Serial.print(F("T")). print(i);
        Serial.print(F("[EN=")). print(timers[i].en ? '1' : '0');
        Serial.print(F(" TT=")). print(timers[i].tt ? '1' : '0');
        Serial.print(F(" DN=")). print(timers[i].dn ? '1' : '0');
        Serial.print(F(" el=")). print(timers[i].elapsed);
        Serial.print(F(" pr=")). print(timers[i].preset);
        Serial.print(F("] "));
    }
    Serial.println();

    Serial.print(F("Scan: "));
    Serial.print(getScanTime());
    Serial.println(F("ms"));
}

// ─────────────────────────────────────────────────────────────────────────────
// Simulation mode
// ─────────────────────────────────────────────────────────────────────────────
void LadderInterpreter::enableSimulation() {
    simulating = true;
    Serial.println(F("Simulation mode enabled — GPIO writes disabled"));
}

void LadderInterpreter::disableSimulation() {
    simulating = false;
    Serial.println(F("Simulation mode disabled — GPIO writes active"));
}

bool LadderInterpreter::getSimulation() {
    return simulating;
}
