"""
Compass rose widget.
The card rotates; the lubber line (red) is fixed at top.
Current heading always appears at the top of the rose.
"""
import math
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore    import Qt, QPointF, QRectF
from PyQt5.QtGui     import (QPainter, QPen, QBrush, QPainterPath,
                              QRadialGradient, QConicalGradient, QColor, QFont)
import ui.styles as S


class CompassWidget(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMinimumSize(300, 300)
        self._hdg      = 0.0
        self._roll     = 0.0
        self._pitch    = 0.0
        self._night    = False
        self._hdg_mode = 'M'   # 'M' = magnetic, 'T' = true

    # ── Public setters ──────────────────────────────────────────────────────

    def set_heading(self, h: float):
        self._hdg = h % 360
        self.update()

    def set_attitude(self, pitch: float, roll: float):
        self._pitch = pitch
        self._roll  = roll
        self.update()

    def set_hdg_mode(self, mode: str):
        self._hdg_mode = mode
        self.update()

    def set_night(self, on: bool):
        self._night = on
        self.update()

    # ── Paint ───────────────────────────────────────────────────────────────

    def paintEvent(self, event):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.TextAntialiasing)

        w, h  = self.width(), self.height()
        cx, cy = w / 2.0, h / 2.0
        R = min(cx, cy) * 0.94       # outer bezel radius

        C = S.is_night(self._night)
        p.fillRect(self.rect(), C['bg'])

        self._bezel(p, cx, cy, R, C)

        # ── Rotating card ──────────────────────────────────────────────────
        p.save()
        p.translate(cx, cy)
        p.rotate(-self._hdg)          # card rotates opposite to heading

        self._card_bg(p, R * 0.87, C)
        self._tick_ring(p, R * 0.71, R * 0.87, C)
        self._star(p, R * 0.50, C)
        self._cardinal_labels(p, R * 0.62, C)

        p.restore()

        # ── Fixed elements ─────────────────────────────────────────────────
        p.save()
        p.translate(cx, cy)
        self._lubber_line(p, R, C)
        self._center_ball(p, R * 0.10, C)
        p.restore()

        self._heading_readout(p, cx, cy, R, C)

    # ── Drawing primitives ──────────────────────────────────────────────────

    def _bezel(self, p, cx, cy, R, C):
        p.save()
        p.translate(cx, cy)

        g = QRadialGradient(QPointF(0, -R * 0.3), R * 1.2)
        g.setColorAt(0.0,  S.BEZEL_MID)
        g.setColorAt(0.82, S.BEZEL_DARK)
        g.setColorAt(0.88, C['gold'])
        g.setColorAt(0.93, C['bright'])
        g.setColorAt(1.0,  C['gold'].darker(160))

        p.setBrush(QBrush(g))
        p.setPen(Qt.NoPen)
        p.drawEllipse(QPointF(0, 0), R * 1.01, R * 1.01)

        p.restore()

    def _card_bg(self, p, r, C):
        g = QRadialGradient(QPointF(0, -r * 0.2), r)
        g.setColorAt(0.0, QColor('#121A2E'))
        g.setColorAt(0.7, QColor('#090D1A'))
        g.setColorAt(1.0, QColor('#060810'))
        p.setBrush(QBrush(g))
        p.setPen(Qt.NoPen)
        p.drawEllipse(QPointF(0, 0), r, r)

    def _tick_ring(self, p, r_in, r_out, C):
        span = r_out - r_in

        for deg in range(0, 360, 5):
            p.save()
            p.rotate(deg)

            if deg % 90 == 0:
                length = span * 0.88
                pen = QPen(C['bright'], max(1.8, r_out * 0.006))
            elif deg % 45 == 0:
                length = span * 0.72
                pen = QPen(C['gold'],   max(1.4, r_out * 0.005))
            elif deg % 10 == 0:
                length = span * 0.55
                pen = QPen(C['gold'].darker(130), max(1.1, r_out * 0.004))
            else:
                length = span * 0.32
                pen = QPen(S.GOLD_DIM,  max(0.8, r_out * 0.003))

            p.setPen(pen)
            p.drawLine(QPointF(0, -r_out), QPointF(0, -r_out + length))

            # Degree numbers every 10°, skip cardinal positions
            if deg % 10 == 0 and deg % 45 != 0:
                fs = max(7, int(r_out * 0.052))
                f = QFont('Arial', fs)
                p.setFont(f)
                p.setPen(QPen(C['dim']))
                text_y = -(r_out - span * 0.98) - fs * 1.6
                rect = QRectF(-20, text_y - 12, 40, 24)
                p.drawText(rect, Qt.AlignCenter, str(deg // 10))

            p.restore()

    def _star(self, p, r, C):
        """16-point compass star. N=red, cardinals=white, intercardinals=gold."""
        for i in range(16):
            p.save()
            p.rotate(i * 22.5)

            cardinal      = (i % 4 == 0)
            intercardinal = (i % 2 == 0 and not cardinal)
            is_north      = (i == 0)

            if cardinal:
                tip    = -r
                shld_y = -r * 0.28
                width  = r * 0.17
                color  = C['red'] if is_north else C['white']
            elif intercardinal:
                tip    = -r * 0.68
                shld_y = -r * 0.20
                width  = r * 0.11
                color  = C['gold']
            else:
                tip    = -r * 0.42
                shld_y = -r * 0.12
                width  = r * 0.07
                color  = S.GOLD_DIM

            # Main point (upper half — bright)
            path = QPainterPath()
            path.moveTo(0, tip)
            path.lineTo(-width / 2, shld_y)
            path.lineTo(0, 0)
            path.lineTo( width / 2, shld_y)
            path.closeSubpath()
            p.setBrush(QBrush(color))
            p.setPen(QPen(color.darker(200), 0.5))
            p.drawPath(path)

            # Lower shadow half
            shadow = QPainterPath()
            shadow.moveTo(0, 0)
            shadow.lineTo(-width / 2, shld_y)
            shadow.lineTo(0, abs(tip) * 0.22)
            shadow.lineTo( width / 2, shld_y)
            shadow.closeSubpath()
            p.setBrush(QBrush(color.darker(220)))
            p.setPen(Qt.NoPen)
            p.drawPath(shadow)

            p.restore()

        # Center ring
        cr = r * 0.09
        p.setPen(QPen(C['gold'], max(1.5, r * 0.025)))
        p.setBrush(QBrush(S.BEZEL_DARK))
        p.drawEllipse(QPointF(0, 0), cr, cr)

    def _cardinal_labels(self, p, r, C):
        entries = [
            (  0, 'N',  C['bright'], True,  True),
            ( 90, 'E',  C['white'],  True,  False),
            (180, 'S',  C['white'],  True,  False),
            (270, 'W',  C['white'],  True,  False),
            ( 45, 'NE', C['gold'],   False, False),
            (135, 'SE', C['gold'],   False, False),
            (225, 'SW', C['gold'],   False, False),
            (315, 'NW', C['gold'],   False, False),
        ]
        for angle, label, color, big, bold in entries:
            p.save()
            p.rotate(angle)
            fs = max(11, int(r * 0.17)) if big else max(7, int(r * 0.10))
            f  = QFont('Arial', fs)
            f.setBold(bold)
            p.setFont(f)
            p.setPen(QPen(color))
            rect = QRectF(-r * 0.20, -r * 1.12, r * 0.40, r * 0.24)
            p.drawText(rect, Qt.AlignCenter, label)
            p.restore()

    def _lubber_line(self, p, R, C):
        """Fixed red lubber line — always at top = ship's heading."""
        pen = QPen(C['red'], max(2.5, R * 0.013))
        pen.setCapStyle(Qt.RoundCap)
        p.setPen(pen)
        p.drawLine(QPointF(0, -R * 0.97), QPointF(0, -R * 0.70))

        # Arrow tip
        path = QPainterPath()
        path.moveTo(0,                  -R * 0.70)
        path.lineTo(-R * 0.028,         -R * 0.62)
        path.lineTo( R * 0.028,         -R * 0.62)
        path.closeSubpath()
        p.setBrush(QBrush(C['red']))
        p.setPen(Qt.NoPen)
        p.drawPath(path)

    def _center_ball(self, p, r, C):
        """Ball in center that shows roll/pitch deviation."""
        p.setPen(QPen(C['gold'], max(1.5, r * 0.10)))
        p.setBrush(QBrush(QColor('#080C18')))
        p.drawEllipse(QPointF(0, 0), r, r)

        max_d  = r * 0.58
        ball_x = math.sin(math.radians(self._roll))  * max_d
        ball_y = -math.sin(math.radians(self._pitch)) * max_d
        dist   = math.hypot(ball_x, ball_y)
        if dist > max_d:
            ball_x = ball_x / dist * max_d
            ball_y = ball_y / dist * max_d

        br = r * 0.38
        p.setPen(Qt.NoPen)
        p.setBrush(QBrush(C['gold']))
        p.drawEllipse(QPointF(ball_x, ball_y), br, br)

    def _heading_readout(self, p, cx, cy, R, C):
        """Digital heading displayed at the top of the compass, just below the lubber arrow."""
        fs = max(13, int(R * 0.115))
        p.setFont(QFont('Courier New', fs, QFont.Bold))

        # Semi-transparent backing so text reads cleanly over the rotating card
        box_w = R * 0.68
        box_h = R * 0.21
        box_x = cx - box_w / 2
        box_y = cy - R * 0.59
        p.setBrush(QBrush(QColor(6, 9, 20, 200)))
        p.setPen(Qt.NoPen)
        p.drawRoundedRect(QRectF(box_x, box_y, box_w, box_h),
                          max(3, box_h * 0.20), max(3, box_h * 0.20))

        p.setPen(QPen(C['bright']))
        p.drawText(QRectF(box_x, box_y, box_w, box_h),
                   Qt.AlignCenter, f"{self._hdg:05.1f}°{self._hdg_mode}")
