"""

Basisklasse für alle Darstellungsseiten

Hinweise zu Cairo:
Das Koordinatensystem geht von (0, 0) links oben bis (400, 300) rechts unten.
Um exakte Pixel zu treffen müssen Koordinaten mit Offset 0.5 verwendet werden.

"""
import os
import cairo
import math
from datetime import datetime

class Page():

    pageno = 1                # Nummer der aktuell sichtbaren Seite
    backlight = False
    color_normal = "dcdcdc"   # Standardhintergrund
    color_lighted = "d89090"  # Hintergrund im Nachtmodus

    bgcolor = (0.86, 0.86, 0.86)
    fgcolor = (0, 0, 0)

    @staticmethod
    def hexcolor(hexstr):
        if (len(hexstr) != 6) or (not all(c.lower in '0123456789abcdef' for c in hexstr)):
            raise ValueError('Not a valid RGB Hexstring')
        else:
            return(int(hexstr[0:2], 16) / 255.0,
                   int(hexstr[2:4], 16) / 255.0,
                   int(hexstr[4:6], 16) / 255.0)

    @staticmethod
    def rotate (origin, points, angle):
        # operates on tuples, angle in degrees
        ox, oy = origin
        phi = math.radians(angle)
        fs = math.sin(phi)
        fc = math.cos(phi)
        rotated = []
        for x, y in points:
            dx = x - ox
            dy = y - oy
            rotated.append((ox + fc * dx - fs * dy, oy + fs * dx + fc * dy))
        return rotated

    @staticmethod
    def rhumb(lat1, lon1, lat2, lon2):
        # Distance in m
        lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        mlat = (lat1 + lat2) / 2
        return 6371000 * sqrt(dlat**2 + (cos(mlat) * dlon)**2)

    @staticmethod
    def haversine(lat1, lon1, lat2, lon2):
        # Great circle distance in nm
        lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
        c = 2 * asin(sqrt(a))
        return c * 3440

    def __init__(self, pageno, cfg, appdata, boatdata):
        self.pageno = pageno
        self.cfg = cfg
        self.fullscreen = cfg['guistyle'] == 'fullscreen'
        self.app = appdata
        self.bd = boatdata
        self.header = True
        self.footer = True
        self.hbled = False # Heartbeat LED 
        self.hbfreq = 1000 # Heartbeat Frequenz in ms
        self.keylock = False
        self.icon = {}
        self.icon['PREV'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_l1.png"))
        self.icon['NEXT'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_r1.png"))
        self.icon['UP'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_up.png"))
        self.icon['DOWN'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_dn.png"))
        self.icon['ILUM'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "lighton.png"))
        self.sym_lock = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "lock.png"))
        self.sym_swipe = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "swipe3.png"))
        self.sym_exclamation = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "exclamation.png"))
        self.sym_question = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "question.png"))
        self.buttonlabel = {
            1: '',
            2: '',
            3: '#PREV',
            4: '#NEXT',
            5: '',
            6: '#ILUM'
        }

    def handle_key(self, buttonid):
        """
        Diese Methode sollte in der Detailseite überladen werden
        """
        print(f"Button no. {buttonid} ignored")
        return False

    def heartbeat(self, ctx):
        """
        Wie ausschalten bei Seitenwechsel?
        """
        ctx.save()
        ctx.set_line_width(1)
        ctx.set_source_rgb(0, 0, 0)
        ctx.rectangle(201.5, 0.5, 23, 19)
        if self.hbled:
            ctx.fill_preserve()
        ctx.stroke()

        if self.hbled:
            ctx.set_source_rgb(0.86, 0.86, 0.86) # 0xdcdcdc
        else:
            ctx.set_source_rgb(0, 0, 0)
        pno = str(self.pageno)
        ext = ctx.text_extents(pno)
        ctx.move_to(211 - ext.width / 2.0, 9 + ext.height / 2.0)
        ctx.show_text(pno)

        ctx.stroke()
        ctx.restore()
        self.hbled = not self.hbled

    def display_new(self):
        """
        Aufruf jedenfalls bei Seitenwechsel.
        Überladen in spezialisierten Seiten bei Bedarf
        """
        pass

    def draw_header(self, ctx):
        """
        Mögliche Zeichen für aktivierte Funktionen (max. 8)
          AP - Accesspoint ist aktiv
          WIFI - WIFI-Client
          TCP
          N2K - NMEA2000
          183 
          USB
          GPS - GPS Fix vorhanden
          TRK - Tracking aktiv
          # TODO Umstellung auf Symbole je 16 Pixel zum Platz sparen
          Neu: Nummer der aktiven Seite (1 - 10)
        """
        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
        ctx.set_font_size(16)
        ctx.move_to(0.5, 14.5)
        ctx.show_text(' '.join([s for s in self.app.status if self.app.status[s]]))
        ctx.stroke()

        # Tastenstatus
        ctx.save()
        if self.keylock:
            ctx.set_source_surface(self.sym_lock, 170, 1)
        else:
            ctx.set_source_surface(self.sym_swipe, 166, 1)
        ctx.paint()
        ctx.restore()

        # Heartbeat
        self.heartbeat(ctx)

        # Datum und Uhrzeit
        ctx.move_to(230, 14.5)
        ctx.show_text(datetime.today().strftime('%H:%M %Y-%m-%d LOT'))
        ctx.stroke()

    def draw_sidebuttons(self, ctx):
        """
        Seitliche Buttons zeichnen im Fullscreen-Modus
        """
        ctx.save()
        ctx.select_font_face("AtariST8x16SystemFont")
        ctx.set_font_size(16)
        ctx.scale(1.6, 1.6)
        ctx.set_source_rgb(0, 0, 0)

        bx = 400
        by = (128, 192, 256, 320, 384, 448)
        bw = 160
        bh = 64
        for i in range(6):
            if len(self.buttonlabel[i+1]) > 0 :
                if self.buttonlabel[i+1][0] == "#":
                    # Symbol verwenden 16x16 Pixel
                    ctx.save()
                    key = self.buttonlabel[i+1][1:]
                    ctx.set_source_surface(self.icon[key], bx + bw / 3.2 - 8, by[i] / 1.6 - 6)
                    ctx.paint()
                    ctx.restore()
                else:
                    w = ctx.text_extents(self.buttonlabel[i+1]).width
                    ctx.move_to(bx + bw/3.2 - w/2, by[i] / 1.6 + 8)
                    ctx.show_text(self.buttonlabel[i+1])
        ctx.stroke()
        ctx.restore()

    def draw_footer(self, ctx):
        """
        Nur Belegung der Buttons (label[1] bis label[6])
        """
        ctx.select_font_face("AtariST8x16SystemFont")
        ctx.set_font_size(16)
        x = (35, 101, 167, 233, 299, 365) 
        y = 294

        ctx.set_line_width(1)
        ctx.move_to(0, 280.5)
        ctx.line_to(10, 280.5)
        ctx.move_to(380, 280.5)
        ctx.line_to(400, 280.5)
        for i in range(5):
            ctx.move_to(x[i]+32.5-10, 280.5)
            ctx.line_to(x[i]+32.5+10, 280.5)
            ctx.move_to(x[i]+32.5, 280)
            ctx.line_to(x[i]+32.5, 300)
        ctx.stroke()

        for i in range(6):
            if len(self.buttonlabel[i+1]) > 0 :
                if self.buttonlabel[i+1][0] == "#":
                    # Symbol verwenden 16x16 Pixel
                    ctx.save()
                    key = self.buttonlabel[i+1][1:]
                    ctx.set_source_surface(self.icon[key], x[i]-8, y-13)
                    ctx.paint()
                    ctx.restore()
                else:
                    text = self.buttonlabel[i+1]
                    w = ctx.text_extents(text).width
                    ctx.move_to(x[i] - w/2, y+2)               
                    ctx.show_text(text)
        ctx.stroke()

    def draw_query(self, ctx, title, message):
        x, y, w, h = (50, 80, 300, 150)
        ctx.save()
        ctx.select_font_face("AtariST8x16SystemFont")
        ctx.set_font_size(16)
        # Dialogbox mit Rahmen
        ctx.set_line_width(1)
        ctx.set_source_rgb(*self.fgcolor)
        ctx.rectangle(x + 0.5, y + 0.5, w, h)
        ctx.stroke()
        ctx.set_source_rgb(*self.bgcolor)
        ctx.rectangle(x + 1, y + 1, w - 1, h - 1)
        ctx.fill()
        ctx.set_source_rgb(*self.fgcolor)
        ctx.set_line_width(2)
        ctx.rectangle(x + 3, y + 3, w - 5, h - 5)
        ctx.clip_preserve()
        ctx.stroke()
        # Symbol
        ctx.save()
        ctx.set_source_surface(self.sym_question, x + 16, y + 16)
        ctx.paint()
        ctx.restore()
        # Titel
        ctx.move_to(x + 64, y + 30)
        ctx.show_text(title)
        ctx.move_to(x + 64, y + 48)
        # Meldung, umgebrochen
        n = 0
        for l in wordwrap(message,  (w - 16 - 8) / 8):
            ctx.move_to(x + 16, y + 70 + n)
            ctx.show_text(l)
            n += 16
            if n > 64:
                break
        # Auswahlmöglichkeiten
        self.draw_button(ctx, x + 16, y + h - 32, "YES")
        self.draw_button(ctx, x + w - 96, y + h - 32, "NO")
        ctx.restore()

    def draw_alarm(self, ctx, source, alarmid, message):
        x, y, w, h = (50, 100, 300, 150)
        ctx.select_font_face("AtariST8x16SystemFont")
        ctx.set_font_size(16)
        # Dialogbox mit Rahmen
        ctx.set_line_width(1)
        ctx.set_source_rgb(*self.fgcolor)
        ctx.rectangle(x + 0.5, y + 0.5, w, h)
        ctx.stroke()
        ctx.set_source_rgb(*self.bgcolor)
        ctx.rectangle(x + 1, y + 1, w - 1, h - 1)
        ctx.fill()
        ctx.set_source_rgb(*self.fgcolor)
        ctx.set_line_width(2)
        ctx.rectangle(x + 3, y + 3, w - 5, h - 5)
        ctx.clip_preserve()
        ctx.stroke()
        # Symbol
        ctx.save()
        ctx.set_source_surface(self.sym_exclamation, x + 16, y + 16)
        ctx.paint()
        ctx.restore()
        # Titel
        ctx.move_to(x + 64, y + 30)
        ctx.show_text("A L A R M")
        ctx.move_to(x + 64, y + 48)
        ctx.show_text(f"#{alarmid} from {source}")
        # Alarmmeldung, umgebrochen
        n = 0
        for l in wordwrap(message,  (w - 16 - 8) / 8):
            ctx.move_to(x + 16, y + 80 + n)
            ctx.show_text(l)
            n += 16
            if n > 64:
                break
        # Button-Hinweis
        self.draw_text_center(ctx, x + w / 2, y + h - 16, "Press button 1 to dismiss alarm")

    def clear(self):
        ctx.set_source_rgb(1, 1, 1)
        ctx.rectangle(0, 0, 399, 299)
        ctx.fill()
        ctx.set_source_rgb(0, 0, 0)

    def draw_text_center(self, ctx, x, y, content, rotate=False, baseline=False, fill=False, fix1=False):
        """
        Korrektur für DSEG7: Die Breite der 1 ist gleich 0.289 * Breite der anderen Ziffern
        Da der Leerraum bei der Ausgabe mit berücksichtigt wird, muß die tatsächliche
        Ausgabeposition links von der Mitte sein um (1 - 0.289) * Breite (=0.711)
        Zusätzlich muß der Abstand zwischen der 1 und dem nachfolgenden Zeichen berücksichtigt
        werden
        """
        if fix1 and content[0] == '1':
            print("Fix1")
            ext1 = ctx.text_extents('1')
            w1 = 0.289 * ext1.width
            dx = ext1.width - w1
            ext = ctx.text_extents(content[1:])
        else:
            ext = ctx.text_extents(content)
        if fill:
            ctx.set_source_rgb(*self.bgcolor)
            xf = x + ext.x_bearing - 2
            yf = y + ext.height / 2 + ext.y_bearing - 2
            wf = ext.width + 4
            if fix1:
                wf += w1
            hf = ext.height + 4
            ctx.rectangle(xf, yf, wf, hf)
            ctx.fill()
        ctx.set_source_rgb(*self.fgcolor)
        if rotate:
            w = ext[2]
            if fix1 and content[0] == '1':
                w += w1
                x = x - dx
            if baseline:
                ctx.move_to(x - w / 2.0, y)
            else:
                ctx.move_to(x - w / 2.0, y + ext[2] / 2.0)
            ctx.save()
            ctx.rotate(1.5 * math.pi)
            ctx.show_text(content)
            ctx.restore()
        else:
            w = ext.width
            if fix1 and content[0] == '1':
                w += w1
                x = x - dx
            if baseline:
                ctx.move_to(x - w / 2.0, y)
            else:
                ctx.move_to(x - w / 2.0, y + ext[3] / 2.0)
            ctx.show_text(content)
        ctx.stroke()

    def draw_text_ralign(self, ctx, x, y, content, fix1=False):
        if fix1 and content[0] == '1':
            w1 = ctx.text_extents('1')[2] * 0.289
            ext = ctx.text_extents(content[1:])
            w = ext[2] + w1
            x = x - dx
        else:
            ext = ctx.text_extents(content)
            w = ext[2]
        ctx.move_to(x - w, y)
        ctx.show_text(content)
        ctx.stroke()

    def draw_text_boxed(self, ctx, x, y, w, h, content, inverted=False, border=False):
        ctx.save()
        ctx.set_line_width(1)
        # Background fill
        ctx.set_source_rgb(*self.fgcolor)
        if inverted:
            ctx.rectangle(x, y + 0.5, w, h)
            ctx.fill()
        else:
            if border:
                ctx.rectangle(x + 0.5, y + 0.5, w, h)
                ctx.stroke()
        # Text
        if inverted:
            ctx.set_source_rgb(*self.bgcolor)
        else:
            ctx.set_source_rgb(*self.fgcolor)
        ctx.move_to(x + 4, y + h - 5 + 0.5)
        ctx.show_text(content)
        ctx.stroke()
        ctx.restore()

    def draw_button(self, ctx, x, y, label):
        ctx.save()
        ctx.select_font_face("AtariST8x16SystemFont")
        ctx.set_font_size(16)
        ctx.set_line_width(3)
        ctx.set_source_rgb(*self.fgcolor)
        ext = ctx.text_extents(label)
        w = max(ext.width, 80)
        h = 24
        ctx.rectangle(x + 0.5, y + 0.5, w, h)
        ctx.stroke()
        ctx.move_to(x + (w - ext.width) / 2.0, y + h - 5)
        ctx.show_text(label)
        ctx.restore()

def wordwrap(text, wrap):
    # Wrap long line to multiple lines, monospaced character set
    # e.g. used for alarm dialog boxes
    llen = 0
    l = 0
    lines = [""]
    for w in text.split():
        wordlength = len(w)
        if llen + 1 + wordlength <= wrap:
            if llen > 0:
                lines[l] += ' '
            lines[l] += w
            llen += wordlength + 1
        else:
            lines.append(w)
            l += 1
            llen = wordlength
    return lines
