import sys
import time
from datetime import datetime

from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QTextEdit, QFrame,
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal

from pymelsec import Type3E
from pymelsec.constants import DT

PLC_IP   = "192.168.3.39"
PLC_PORT = 5010

# ── 스타일 상수 ─────────────────────────────────────────────────────────── #
_PANEL = (
    "QFrame {"
    "  background:#222222; border:1px solid #333333; border-radius:6px;"
    "}"
)
_BTN_GREEN = (
    "QPushButton {"
    "  background:#1D9E75; color:#ffffff; border:none; border-radius:4px;"
    "  min-height:38px; font-size:13px;"
    "}"
    "QPushButton:hover    { background:#20b585; }"
    "QPushButton:disabled { background:#145f48; color:#5a9e7e; }"
)
_BTN_RED = (
    "QPushButton {"
    "  background:#8B2020; color:#ffffff; border:none; border-radius:4px;"
    "  min-height:38px; font-size:13px;"
    "}"
    "QPushButton:hover    { background:#a02828; }"
    "QPushButton:disabled { background:#4a1515; color:#9e5a5a; }"
)
_BTN_GRAY = (
    "QPushButton {"
    "  background:#333333; color:#aaaaaa; border:none; border-radius:4px;"
    "  min-height:38px; font-size:13px;"
    "}"
    "QPushButton:hover    { background:#444444; color:#ffffff; }"
    "QPushButton:disabled { background:#2a2a2a; color:#555555; }"
)
_BTN_RED_OUTLINE = (
    "QPushButton {"
    "  background:#3D1515; color:#F09595; border:none; border-radius:4px;"
    "  min-height:34px; font-size:13px;"
    "}"
    "QPushButton:hover    { background:#4a1818; }"
    "QPushButton:disabled { background:#222222; color:#555555; }"
)


# ══════════════════════════════════════════════════════════════════════════ #
# PLCMonitor — D500 폴링 스레드
# ══════════════════════════════════════════════════════════════════════════ #

class PLCMonitor(QThread):
    signal_received = pyqtSignal(int)
    error_occurred  = pyqtSignal(str)

    def __init__(self, plc: Type3E):
        super().__init__()
        self.plc     = plc
        self.running = False

    def run(self):
        self.running = True
        while self.running:
            try:
                result = self.plc.batch_read(
                    ref_device="D500",
                    read_size=1,
                    data_type=DT.SWORD,
                )
                raw   = result[0]
                value = int(raw.value if hasattr(raw, "value") else raw)
                self.signal_received.emit(value)
            except Exception as e:
                self.error_occurred.emit(str(e))
            time.sleep(0.1)

    def stop(self):
        self.running = False
        self.wait(2000)
        if self.isRunning():
            self.terminate()


# ══════════════════════════════════════════════════════════════════════════ #
# PLCTestGUI
# ══════════════════════════════════════════════════════════════════════════ #

class PLCTestGUI(QWidget):
    def __init__(self):
        super().__init__()
        self.plc        = None
        self._connected = False
        self._monitor   = None
        self._last_m100 = -1

        self.setWindowTitle("PLC 신호 테스트")
        self.setFixedSize(600, 400)
        self.setStyleSheet("background:#1a1a1a; color:#ffffff; font-size:13px;")

        self._build_ui()
        self._connect_plc()

    # ── UI 구성 ────────────────────────────────────────────────────────── #

    def _build_ui(self):
        root = QVBoxLayout(self)
        root.setContentsMargins(16, 12, 16, 12)
        root.setSpacing(8)

        root.addWidget(self._build_header())
        root.addWidget(self._separator())

        center = QHBoxLayout()
        center.setSpacing(10)
        center.addWidget(self._build_send_panel(), stretch=1)
        center.addWidget(self._build_recv_panel(), stretch=1)
        root.addLayout(center, stretch=1)

        root.addWidget(self._separator())
        root.addWidget(self._build_log())

    def _build_header(self) -> QWidget:
        w = QWidget()
        w.setStyleSheet("background:transparent;")
        row = QHBoxLayout(w)
        row.setContentsMargins(0, 0, 0, 0)

        lbl_ip = QLabel(f"PLC IP:  {PLC_IP} : {PLC_PORT}")
        lbl_ip.setStyleSheet("color:#888888; font-size:13px;")

        self._dot = QLabel("●")
        self._dot.setStyleSheet("color:#cc2222; font-size:16px;")

        self._lbl_status = QLabel("연결 안됨")
        self._lbl_status.setStyleSheet("color:#cc2222; font-size:13px;")

        row.addWidget(lbl_ip)
        row.addStretch()
        row.addWidget(self._dot)
        row.addSpacing(4)
        row.addWidget(self._lbl_status)
        return w

    def _build_send_panel(self) -> QFrame:
        frame = QFrame()
        frame.setStyleSheet(_PANEL)
        v = QVBoxLayout(frame)
        v.setContentsMargins(12, 10, 12, 10)
        v.setSpacing(6)

        title = QLabel("PC → PLC 신호 전송")
        title.setStyleSheet(
            "color:#aaaaaa; font-size:12px; background:transparent; border:none;"
        )
        v.addWidget(title)

        self._btn_pass = QPushButton("PASS 신호 전송   (M200 = 1)")
        self._btn_pass.setStyleSheet(_BTN_GREEN)
        self._btn_pass.clicked.connect(self._send_pass)

        self._btn_fail = QPushButton("FAIL 신호 전송   (M201 = 1)")
        self._btn_fail.setStyleSheet(_BTN_RED)
        self._btn_fail.clicked.connect(self._send_fail)

        self._btn_reset = QPushButton("신호 초기화   (M200 = M201 = D100 = 0)")
        self._btn_reset.setStyleSheet(_BTN_GRAY)
        self._btn_reset.clicked.connect(self._send_reset)

        self._send_btns = [self._btn_pass, self._btn_fail, self._btn_reset]
        v.addWidget(self._btn_pass)
        v.addWidget(self._btn_fail)
        v.addWidget(self._btn_reset)
        v.addStretch()
        return frame

    def _build_recv_panel(self) -> QFrame:
        frame = QFrame()
        frame.setStyleSheet(_PANEL)
        v = QVBoxLayout(frame)
        v.setContentsMargins(12, 10, 12, 10)
        v.setSpacing(6)

        title = QLabel("PLC → PC 신호 수신")
        title.setStyleSheet(
            "color:#aaaaaa; font-size:12px; background:transparent; border:none;"
        )
        v.addWidget(title)

        self._lbl_d500 = QLabel("● 대기 중")
        self._lbl_d500.setAlignment(Qt.AlignCenter)
        self._lbl_d500.setStyleSheet(
            "color:#555555; font-size:15px; font-weight:normal;"
            "background:transparent; border:none;"
        )
        v.addWidget(self._lbl_d500)

        self._btn_mon_start = QPushButton("신호 감지 시작")
        self._btn_mon_start.setStyleSheet(_BTN_GREEN.replace("min-height:38px", "min-height:34px"))
        self._btn_mon_start.clicked.connect(self._start_monitor)

        self._btn_mon_stop = QPushButton("신호 감지 중지")
        self._btn_mon_stop.setEnabled(False)
        self._btn_mon_stop.setStyleSheet(_BTN_RED_OUTLINE)
        self._btn_mon_stop.clicked.connect(self._stop_monitor)

        v.addWidget(self._btn_mon_start)
        v.addWidget(self._btn_mon_stop)
        v.addStretch()
        return frame

    @staticmethod
    def _separator() -> QFrame:
        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setStyleSheet("border:none; background:#2a2a2a; max-height:1px;")
        return line

    def _build_log(self) -> QTextEdit:
        self._log = QTextEdit()
        self._log.setReadOnly(True)
        self._log.setFixedHeight(108)
        self._log.setStyleSheet(
            "background:#111111; color:#888888;"
            "border:1px solid #2a2a2a; border-radius:4px;"
            "font-family: Consolas, monospace; font-size:12px;"
        )
        return self._log

    # ── PLC 연결 ───────────────────────────────────────────────────────── #

    def _connect_plc(self):
        try:
            self.plc = Type3E(host=PLC_IP, port=PLC_PORT, plc_type="Q")
            self.plc.connect(PLC_IP, PLC_PORT)
            self._connected = True
            self._dot.setStyleSheet("color:#1D9E75; font-size:16px;")
            self._lbl_status.setStyleSheet("color:#1D9E75; font-size:13px;")
            self._lbl_status.setText("연결됨")
            self._log_msg(f"PLC 연결 성공: {PLC_IP}:{PLC_PORT}")
        except Exception as e:
            self._connected = False
            self._log_msg(f"PLC 연결 실패: {e}")
            for btn in self._send_btns:
                btn.setEnabled(False)
            self._btn_mon_start.setEnabled(False)

    # ── 신호 전송 ──────────────────────────────────────────────────────── #

    def _send_pass(self):
        if not self._connected:
            return
        try:
            self.plc.batch_write(ref_device="M200", values=[1], data_type=DT.BIT)
            self._log_msg("PC → PLC: M200 = 1  (PASS)")
        except Exception as e:
            self._log_msg(f"전송 오류: {e}")

    def _send_fail(self):
        if not self._connected:
            return
        try:
            self.plc.batch_write(ref_device="M201", values=[1], data_type=DT.BIT)
            self._log_msg("PC → PLC: M201 = 1  (FAIL)")
        except Exception as e:
            self._log_msg(f"전송 오류: {e}")

    def _send_reset(self):
        if not self._connected:
            return
        try:
            self.plc.batch_write(ref_device="M200", values=[0], data_type=DT.BIT)
            self.plc.batch_write(ref_device="M201", values=[0], data_type=DT.BIT)
            self.plc.batch_write(ref_device="D100", values=[0], data_type=DT.SWORD)
            self._log_msg("PC → PLC: M200=0, M201=0, D100=0  (초기화)")
        except Exception as e:
            self._log_msg(f"초기화 오류: {e}")

    # ── 신호 수신 폴링 ─────────────────────────────────────────────────── #

    def _start_monitor(self):
        if not self._connected or self._monitor:
            return
        self._last_m100 = -1
        self._monitor = PLCMonitor(self.plc)
        self._monitor.signal_received.connect(self._on_signal)
        self._monitor.error_occurred.connect(self._on_poll_error)
        self._monitor.start()
        self._btn_mon_start.setEnabled(False)
        self._btn_mon_stop.setEnabled(True)
        self._log_msg("D500 폴링 시작  (100ms 간격)")

    def _stop_monitor(self):
        if self._monitor:
            self._monitor.stop()
            self._monitor = None
        self._btn_mon_start.setEnabled(True)
        self._btn_mon_stop.setEnabled(False)
        self._lbl_d500.setText("● 대기 중")
        self._lbl_d500.setStyleSheet(
            "color:#555555; font-size:15px; font-weight:normal;"
            "background:transparent; border:none;"
        )
        self._log_msg("D500 폴링 중지")

    def _on_signal(self, value: int):
        prev            = self._last_m100
        self._last_m100 = value

        if value >= 1:
            self._lbl_d500.setText(f"● 신호 수신!  ({value})")
            self._lbl_d500.setStyleSheet(
                "color:#1D9E75; font-size:15px; font-weight:bold;"
                "background:transparent; border:none;"
            )
            if prev < 1:
                self._log_msg(f"PLC → PC: D500 = {value}  수신!")
        else:
            self._lbl_d500.setText("● 대기 중")
            self._lbl_d500.setStyleSheet(
                "color:#555555; font-size:15px; font-weight:normal;"
                "background:transparent; border:none;"
            )
            if prev >= 1:
                self._log_msg(f"PLC → PC: D500 = {value}  (신호 해제)")

    def _on_poll_error(self, msg: str):
        self._log_msg(f"폴링 오류: {msg}")

    # ── 로그 ───────────────────────────────────────────────────────────── #

    def _log_msg(self, text: str):
        ts = datetime.now().strftime("%H:%M:%S")
        self._log.append(f"[{ts}] {text}")

    # ── 종료 ───────────────────────────────────────────────────────────── #

    def closeEvent(self, event):
        if self._monitor:
            self._monitor.stop()
        if self.plc and self._connected:
            try:
                self.plc.close()
            except Exception:
                pass
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyleSheet("""
        QWidget { background:#1a1a1a; color:#ffffff; font-size:13px; }
        QScrollBar:vertical {
            background:#2a2a2a; width:8px; border-radius:4px;
        }
        QScrollBar::handle:vertical {
            background:#444444; border-radius:4px; min-height:20px;
        }
        QScrollBar::add-line:vertical,
        QScrollBar::sub-line:vertical { height:0; }
    """)
    window = PLCTestGUI()
    window.show()
    sys.exit(app.exec_())
