#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from typing import Literal
import wx


class MyCanvas(wx.ScrolledWindow):
    def __init__(self, parent, *args, **kwds):
        super().__init__(parent, *args, **kwds)
        self.font = wx.Font(
            11,
            wx.FONTFAMILY_TELETYPE,
            wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_NORMAL
            )

        self.SetFont(self.font)
        self.SetDoubleBuffered(True)
        self.SetScrollRate(0, 17)
        self.lines = []
        self.line_height = 17
        self.bytes_per_line = 16
        self.total_width = self.calculate_widths(self.font)
        self.SetMinSize(wx.Size(self.total_width, 500))

        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        self.Bind(wx.EVT_PAINT, self.on_paint)

    def on_paint(self, event):
        dc = wx.AutoBufferedPaintDC(self)
        self.PrepareDC(dc)

        dc.SetBackground(wx.WHITE_BRUSH)
        dc.Clear()

        client_h = self.GetClientSize().height

        _, y_v = dc.GetDeviceOrigin()
        view_y = y_v

        first_row = max(0, view_y // self.line_height)
        last_row = min(len(self.lines), first_row + client_h // self.line_height + 1)

        for row in range(first_row, last_row):
            y = row * self.line_height
            line_text = self.lines[row]
            dc.SetTextForeground(wx.BLACK)
            dc.DrawText(line_text, 4, y)
    
    def set_data(self, data):
        self.file_bytes = data
        self.lines = []
        bpl = self.bytes_per_line
        groups = []
        for index in range(0, len(data), bpl):
            chunk = data[index:index+bpl]
            for i in range(0, len(chunk), 4):
                g = chunk[i:i+4]
                groups.append(' ' ''.join(f'{b:02X}' for b in g))
            line = '  ' + '  '.join(groups) + '  '
            self.lines.append(line)
        total_h = len(self.lines) * self.line_height
        self.SetVirtualSize(wx.Size(self.total_width, total_h))
        self.Scroll(0, 0)
        self.Refresh()

    def calculate_widths(self, font):
        dc = wx.MemoryDC()
        bmp = wx.Bitmap(1, 1)
        dc.SelectObject(bmp)
        dc.SetFont(font)
        line = 'X' * len(' 00 01 02 03  04 05 06 07  08 09 0a 0b  0c 0d 0e 0f ')
        tw, _ = dc.GetTextExtent(line)
        width = tw
        dc.SelectObject(wx.NullBitmap)
        return width


class HexPanel(wx.Panel):
    def __init__(self, parent, *args, **kwds):
        super().__init__(parent, *args, **kwds)
        sizer = wx.BoxSizer(wx.VERTICAL)
        bsizer = wx.BoxSizer(wx.HORIZONTAL)
        button = wx.Button(self, wx.ID_ANY, 'Load')
        self.canvas = MyCanvas(self, style=wx.VSCROLL)
        sizer.Add(self.canvas, 1, wx.EXPAND)
        self.SetSizer(sizer)
        button.Bind(wx.EVT_BUTTON, self.on_button)

    def on_button(self, event):
        with open(__file__, 'rb') as infile:
            file_data = infile.read()
        self.canvas.set_data(file_data)



class MainWindow(wx.Frame):
    def __init__(self, parent, *args, **kwds):
        super().__init__(parent, *args, **kwds)
        sizer = wx.BoxSizer(wx.VERTICAL)
        panel = HexPanel(self, wx.ID_ANY)
        sizer.Add(panel, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.Layout()


class MyApp(wx.App):
    def OnInit(self) -> Literal[True]:
        self.main_window = MainWindow(None, wx.ID_ANY)
        self.SetTopWindow(self.main_window)
        self.main_window.Show()
        return True


if __name__ == "__main__":
    application = MyApp(False)
    application.MainLoop()


