#!/usr/bin/env python3
"""
MechBase PLC — Web Interface

Flask web server for ladder logic editor with real-time simulation.
Serves the UI, handles API calls, and runs the ladder interpreter.
"""

import json
import sys
import threading
import time
from typing import Dict, Any
from flask import Flask, render_template, jsonify, request
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent / 'tests'))
from ladder_test import (
    LadderInterpreter, Network, Rung, Branch, Element, Contact,
    Output, Timer, Counter, Compare, Math, Data, Control
)

sys.path.insert(0, str(Path(__file__).parent / 'src'))
from l5x_parser import parse_l5x_routines

app = Flask(__name__)

# ── Global PLC instance ──────────────────────────────────────────────
plc = LadderInterpreter()
plc.scan_cycle = 0.05  # 50ms scan cycle
_plc_thread = None
_lock = threading.Lock()


def _run_plc():
    """Background thread that runs the PLC scan loop."""
    while plc.running:
        with _lock:
            plc.scan()
        time.sleep(plc.scan_cycle)


@app.route('/')
def index():
    return render_template('index.html')


# ── API: PLC State ───────────────────────────────────────────────────

@app.route('/api/state')
def get_state():
    with _lock:
        state = {
            "running": plc.running,
            "inputs": dict(plc.digital_inputs),
            "outputs": dict(plc.digital_outputs),
            "memory": dict(plc.memory_bool),
            "memory_int": dict(plc.memory_int),
            "memory_real": {k: round(v, 4) for k, v in plc.memory_real.items()},
            "timers": {
                tag: {
                    "type": t.type,
                    "en": t.en,
                    "tt": t.tt,
                    "dn": t.dn,
                    "elapsed": round(t.elapsed),
                    "preset": t.preset,
                }
                for tag, t in plc.timers.items()
            },
            "counters": {
                tag: {
                    "type": c.type,
                    "en": c.en,
                    "dn": c.dn,
                    "acc": c.acc,
                    "preset": c.preset,
                }
                for tag, c in plc.counters.items()
            },
            "networks": [net.to_dict() for net in plc.networks],
        }
    return jsonify(state)


# ── API: Program Management ──────────────────────────────────────────

@app.route('/api/program', methods=['GET'])
def get_program():
    """Get current program data."""
    with _lock:
        return jsonify({
            "program": "MechBase PLC",
            "networks": [net.to_dict() for net in plc.networks],
        })

@app.route('/api/program', methods=['POST'])
def set_program():
    """Save a new program."""
    global _plc_thread
    data = request.json

    with _lock:
        plc.running = False
        plc.networks = []
        plc.timers = {}
        plc.counters = {}
        plc.digital_outputs = {}
        plc.memory_bool = {}
        plc.memory_int = {}
        plc.memory_real = {}

        if data and 'networks' in data:
            for net_data in data['networks']:
                net = Network.from_dict(net_data)
                plc.networks.append(net)

    return jsonify({"status": "ok", "networks": len(plc.networks)})

@app.route('/api/scan', methods=['POST'])
def scan_plc():
    """Execute a single scan cycle."""
    with _lock:
        plc.scan()
    return jsonify({
        "status": "ok",
        "outputs": dict(plc.digital_outputs),
        "timers": {tag: {"en": t.en, "tt": t.tt, "dn": t.dn, "elapsed": round(t.elapsed), "preset": t.preset}
                    for tag, t in plc.timers.items()},
        "counters": {tag: {"en": c.en, "dn": c.dn, "acc": c.acc, "preset": c.preset}
                      for tag, c in plc.counters.items()},
        "memory_int": dict(plc.memory_int),
        "memory_real": {k: round(v, 4) for k, v in plc.memory_real.items()},
    })

@app.route('/api/templates')
def get_templates_alias():
    """Alias for /api/program/templates."""
    return get_templates()


@app.route('/api/run', methods=['POST'])
def run_plc():
    global _plc_thread
    with _lock:
        if not plc.running:
            plc.running = True
            _plc_thread = threading.Thread(target=_run_plc, daemon=True)
            _plc_thread.start()
    return jsonify({"status": "running"})


@app.route('/api/stop', methods=['POST'])
def stop_plc():
    with _lock:
        plc.running = False
    return jsonify({"status": "stopped"})


@app.route('/api/reset', methods=['POST'])
def reset_plc():
    global _plc_thread
    with _lock:
        plc.running = False
        plc.digital_outputs = {}
        plc.memory_bool = {}
        plc.memory_int = {}
        plc.memory_real = {}
        plc.timers = {}
        plc.counters = {}
    return jsonify({"status": "reset"})


# ── API: L5X File Loading ───────────────────────────────────────────

L5X_DIR = Path(__file__).parent


def _l5x_parsed_to_networks(parsed_rung, rung_num, comment) -> Dict[str, Any]:
    """Convert parsed L5X rung data to the Network/Rung format the interpreter expects."""
    lead_in = parsed_rung.get('lead_in', [])
    branches = parsed_rung.get('branches', [])
    trailing = parsed_rung.get('trailing', [])

    # Build inline elements (lead-in contacts)
    inline_elements = [{'type': e.get('type', 'XIC'), 'tag': e.get('tag', ''), 'value': e.get('value', 0)} for e in lead_in]

    # Build parallel branches
    parallel_branches = []
    for branch in branches:
        branch_elements = [{'type': e.get('type', 'XIC'), 'tag': e.get('tag', ''), 'value': e.get('value', 0)} for e in branch]
        parallel_branches.append(branch_elements)

    # Build outputs (trailing coils/blocks)
    outputs = [{'type': e.get('type', 'OTE'), 'tag': e.get('tag', ''), 'value': e.get('value', 0)} for e in trailing]

    rung_dict = {
        'number': rung_num,
        'comment': comment,
        'inline': {'elements': inline_elements},
        'outputs': outputs,
    }
    if parallel_branches:
        rung_dict['parallel'] = [{'elements': b} for b in parallel_branches]

    return {
        'number': rung_num,
        'comment': comment,
        'rungs': [rung_dict]
    }


@app.route('/api/l5x/list')
def list_l5x_files():
    """List available L5X files."""
    files = []
    for ext in ['*.L5X', '*.l5x']:
        files.extend(L5X_DIR.glob(ext))
    result = [f.name for f in sorted(files)]
    return jsonify({'files': result})


@app.route('/api/l5x/load/<filename>')
def load_l5x(filename):
    """Load and parse an L5X file, return as program data compatible with the ladder UI."""
    l5x_path = L5X_DIR / filename
    if not l5x_path.exists():
        return jsonify({'error': f'File not found: {filename}'}), 404

    try:
        routines = parse_l5x_routines(str(l5x_path))
        if not routines:
            return jsonify({'error': 'No LAD (RLL) routines found in file'}), 400

        # Return all routines
        result = {
            'routines': [],
            'default': 0
        }

        for routine in routines:
            networks = []
            for rung in routine['rungs']:
                net = _l5x_parsed_to_networks(
                    rung['parsed'],
                    rung['number'],
                    rung['comment']
                )
                networks.append(net)

            result['routines'].append({
                'name': routine['name'],
                'language': routine['language'],
                'rung_count': routine['rung_count'],
                'networks': networks
            })

        return jsonify(result)

    except Exception as e:
        return jsonify({'error': str(e)}), 500


# ── API: I/O Control ─────────────────────────────────────────────────

@app.route('/api/io/input/<tag>', methods=['POST'])
def toggle_input(tag):
    with _lock:
        current = plc.digital_inputs.get(tag, False)
        plc.digital_inputs[tag] = not current
    return jsonify({"tag": tag, "value": not current})


@app.route('/api/io/memory/<tag>', methods=['POST'])
def set_memory(tag):
    data = request.json
    value = data.get('value', True) if data else True
    with _lock:
        if tag.startswith('M'):
            plc.memory_bool[tag] = bool(value)
        elif tag.startswith('N'):
            plc.memory_int[tag] = int(value)
        elif tag.startswith('R'):
            plc.memory_real[tag] = float(value)
        else:
            plc.digital_inputs[tag] = bool(value)
    return jsonify({"tag": tag, "value": value})


# ── API: Templates ───────────────────────────────────────────────────

TEMPLATES = [
    {
        "name": "motor-start-stop",
        "title": "Motor Start/Stop",
        "description": "Classic start/stop circuit with seal-in latch",
        "data": {
            "program": "Motor Start/Stop",
            "networks": [
                {
                    "number": 0,
                    "comment": "Motor start with seal-in",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "Start button latches motor",
                            "inline": {"elements": []},
                            "parallel": [
                                {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                                {"elements": [{"type": "XIC", "tag": "Q0", "value": 0}]},
                            ],
                            "outputs": [
                                {"type": "XIO", "tag": "I1", "value": 0},
                                {"type": "OTE", "tag": "Q0", "value": 0},
                            ]
                        },
                        {
                            "number": 1,
                            "comment": "Stop button unlatches motor",
                            "inline": {"elements": [{"type": "XIC", "tag": "I1", "value": 0}]},
                            "outputs": [{"type": "OTU", "tag": "Q0", "value": 0}]
                        }
                    ]
                }
            ]
        }
    },
    {
        "name": "timed-light",
        "title": "Timed Light",
        "description": "Press button to turn on light with timed delay",
        "data": {
            "program": "Timed Light",
            "networks": [
                {
                    "number": 0,
                    "comment": "Timer on delay",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "Button enables TON timer",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "TON", "tag": "T0", "value": 2000}]
                        },
                        {
                            "number": 1,
                            "comment": "Timer done energizes light",
                            "inline": {"elements": [{"type": "TON_DN", "tag": "T0", "value": 0}]},
                            "outputs": [{"type": "OTE", "tag": "Q0", "value": 0}]
                        }
                    ]
                }
            ]
        }
    },
    {
        "name": "counter-batch",
        "title": "Batch Counter",
        "description": "Count items and signal when batch is complete",
        "data": {
            "program": "Batch Counter",
            "networks": [
                {
                    "number": 0,
                    "comment": "Count items",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "Photo eye rising edge increments counter",
                            "inline": {"elements": [{"type": "Rising", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "CTU", "tag": "C0", "value": 10}]
                        },
                        {
                            "number": 1,
                            "comment": "Counter done signals batch complete",
                            "inline": {"elements": [{"type": "CTU_DN", "tag": "C0", "value": 0}]},
                            "outputs": [{"type": "OTL", "tag": "Q0", "value": 0}]
                        },
                        {
                            "number": 2,
                            "comment": "Reset button clears counter",
                            "inline": {"elements": [{"type": "XIC", "tag": "I1", "value": 0}]},
                            "outputs": [{"type": "RES", "tag": "C0", "value": 0}]
                        }
                    ]
                }
            ]
        }
    },
    {
        "name": "comparison-monitor",
        "title": "Comparison Monitor",
        "description": "Monitor values with comparison instructions",
        "data": {
            "program": "Comparison Monitor",
            "networks": [
                {
                    "number": 0,
                    "comment": "Value monitoring with comparisons",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "N0 >= 80 triggers high alarm",
                            "inline": {"elements": [{"type": "GEQ", "tag": "N0", "value": 80}]},
                            "outputs": [{"type": "OTE", "tag": "Q0", "value": 0}]
                        },
                        {
                            "number": 1,
                            "comment": "N0 < 20 triggers low alarm",
                            "inline": {"elements": [{"type": "LES", "tag": "N0", "value": 20}]},
                            "outputs": [{"type": "OTE", "tag": "Q1", "value": 0}]
                        },
                        {
                            "number": 2,
                            "comment": "N0 in range [20, 80] → normal",
                            "inline": {"elements": [{"type": "LIM", "tag": "N0", "value": 20, "value2": 80}]},
                            "outputs": [{"type": "OTE", "tag": "Q2", "value": 0}]
                        }
                    ]
                }
            ]
        }
    },
    {
        "name": "interlock",
        "title": "Forward/Reverse Interlock",
        "description": "Motor forward/reverse with electrical interlock",
        "data": {
            "program": "F/R Interlock",
            "networks": [
                {
                    "number": 0,
                    "comment": "Forward with reverse interlock",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "Forward start with seal-in and interlock",
                            "inline": {"elements": []},
                            "parallel": [
                                {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                                {"elements": [{"type": "XIC", "tag": "Q0", "value": 0}]},
                            ],
                            "outputs": [
                                {"type": "XIO", "tag": "I1", "value": 0},
                                {"type": "XIO", "tag": "Q1", "value": 0},
                                {"type": "OTE", "tag": "Q0", "value": 0},
                            ]
                        }
                    ]
                },
                {
                    "number": 1,
                    "comment": "Reverse with forward interlock",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "Reverse start with seal-in and interlock",
                            "inline": {"elements": []},
                            "parallel": [
                                {"elements": [{"type": "XIC", "tag": "I2", "value": 0}]},
                                {"elements": [{"type": "XIC", "tag": "Q1", "value": 0}]},
                            ],
                            "outputs": [
                                {"type": "XIO", "tag": "I1", "value": 0},
                                {"type": "XIO", "tag": "Q0", "value": 0},
                                {"type": "OTE", "tag": "Q1", "value": 0},
                            ]
                        }
                    ]
                }
            ]
        }
    },
    {
        "name": "math-calculate",
        "title": "Math Calculation",
        "description": "Basic math operations with memory storage",
        "data": {
            "program": "Math Calculation",
            "networks": [
                {
                    "number": 0,
                    "comment": "Math operations",
                    "rungs": [
                        {
                            "number": 0,
                            "comment": "ADD: N0 + N1 → N10",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "ADD", "tag": "N0", "value": "N1", "value2": "N10"}]
                        },
                        {
                            "number": 1,
                            "comment": "SUB: N0 - N1 → N11",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "SUB", "tag": "N0", "value": "N1", "value2": "N11"}]
                        },
                        {
                            "number": 2,
                            "comment": "MUL: N0 * N1 → N12",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "MUL", "tag": "N0", "value": "N1", "value2": "N12"}]
                        },
                        {
                            "number": 3,
                            "comment": "DIV: N0 / N1 → N13",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "DIV", "tag": "N0", "value": "N1", "value2": "N13"}]
                        },
                        {
                            "number": 4,
                            "comment": "MOV: Copy N0 to N20",
                            "inline": {"elements": [{"type": "XIC", "tag": "I0", "value": 0}]},
                            "outputs": [{"type": "MOV", "tag": "N0", "value": "N20"}]
                        }
                    ]
                }
            ]
        }
    },
]


@app.route('/api/program/templates')
def get_templates():
    return jsonify([
        {"name": t["name"], "title": t["title"], "description": t["description"]}
        for t in TEMPLATES
    ])


@app.route('/api/program/template/<name>')
def get_template(name):
    for t in TEMPLATES:
        if t["name"] == name:
            return jsonify(t["data"])
    return jsonify({"error": "Template not found"}), 404


# ── CLI helpers ──────────────────────────────────────────────────────

def set_input(tag, value):
    plc.digital_inputs[tag] = value

def get_output(tag):
    return plc.digital_outputs.get(tag, False)


if __name__ == '__main__':
    print("MechBase PLC — Web Interface")
    print("Open http://localhost:5003")
    print("Press Ctrl+C to stop\n")
    app.run(host='0.0.0.0', port=5003, debug=False)
