#!/opt/local/bin/python3.11

############################################################################
#
# MODULE:       wxpyimgview
# AUTHOR(S):    Glynn Clements <glynn@gclements.plus.com>
# COPYRIGHT:    (C) 2010 Glynn Clements
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
# /

#%module
#% description: Views BMP images from the PNG driver.
#% keyword: display
#% keyword: raster
#%end
#%option G_OPT_F_INPUT
#% key: image
#% description: Name of input image file
#%end
#%option
#% key: percent
#% type: integer
#% required: no
#% multiple: no
#% description: Percentage of CPU time to use
#% answer: 10
#%end

import os
import signal
import struct
import sys
import time

import grass.script as grass
from grass.script.setup import set_gui_path

set_gui_path()
from gui_core.wrap import BitmapFromImage

import numpy

import wx


class Frame(wx.Frame):
    title = "Image Viewer"

    def __init__(self, app, size):
        self.app = app
        wx.Frame.__init__(self, None, title=Frame.title, size=size)
        self.Create()

    def Create(self):
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.erase)
        self.Bind(wx.EVT_PAINT, self.redraw)

        self.Bind(wx.EVT_TIMER, self.tick, id=1)
        self.timer = wx.Timer(self, 1)
        self.timer.Start(100, True)

        # Python doesn't receive signals while wx is waiting for an event
        self.Bind(wx.EVT_TIMER, self.dummy, id=2)
        self.ticker = wx.Timer(self, 2)
        self.ticker.Start(100, False)

    def erase(self, ev):
        ev.GetDC()

    def draw(self):
        app = self.app
        size = self.GetSize()
        x0 = (size.GetWidth() - app.i_width) / 2
        y0 = (size.GetHeight() - app.i_height) / 2
        dc = wx.PaintDC(self)
        data = app.imgbuf.reshape((app.i_height, app.i_width, 4))
        data = data[::, ::, 2::-1]
        fn = getattr(data, "tobytes", getattr(data, "tostring"))
        image = wx.Image(app.i_width, app.i_height, fn())
        dc.DrawBitmap(BitmapFromImage(image), x0, y0, False)

    def redraw(self, ev):
        if self.app.fraction > 0.001:
            t0 = time.time()
            self.draw()
            t1 = time.time()

            last = t1 - t0
            delay = last / self.app.fraction
            self.timer.Start(int(delay * 1000), True)
        else:
            self.draw()

    def tick(self, ev):
        self.Refresh()

    def dummy(self, ev):
        pass


class Application(wx.App):

    def __init__(self):
        self.image = sys.argv[1]
        self.fraction = int(sys.argv[2]) / 100.0
        self.HEADER_SIZE = 64
        wx.App.__init__(self)

    def read_bmp_header(self, header):
        magic, bmfh, bmih = struct.unpack("2s12s40s10x", header)

        if grass.decode(magic) != 'BM':
            raise SyntaxError("Invalid magic number")

        size, res1, res2, hsize = struct.unpack("<IHHI", bmfh)

        if hsize != self.HEADER_SIZE:
            raise SyntaxError("Invalid file header size")

        hsize, width, height, planes, bpp, compression, imsize, xppm, yppm, cused, cimp = \
            struct.unpack("<IiiHHIIiiII", bmih)

        if hsize != 40:
            raise SyntaxError("Invalid info header size")

        self.i_width = width
        self.i_height = -height

        if planes != 1:
            raise SyntaxError("Planar data not supported")
        if bpp != 32:
            raise SyntaxError("Only 32-BPP images supported")
        if compression != 0:
            raise SyntaxError("Compression not supported")
        if imsize != self.i_width * self.i_height * 4:
            raise SyntaxError("Invalid image data size")
        if size != self.HEADER_SIZE + self.i_width * self.i_height * 4:
            raise SyntaxError("Invalid image size")

    def map_file(self):
        f = open(self.image, 'rb')

        header = f.read(self.HEADER_SIZE)
        self.read_bmp_header(header)

        self.imgbuf = numpy.memmap(f, mode='r', offset=self.HEADER_SIZE)

    def signal_handler(self, sig, frame):
        wx.CallAfter(self.mainwin.Refresh)

    def set_handler(self):
        if 'SIGUSR1' in dir(signal):
            signal.signal(signal.SIGUSR1, self.signal_handler)

    def OnInit(self):
        self.map_file()

        size = wx.Size(self.i_width, self.i_height)
        self.mainwin = Frame(self, size)
        self.mainwin.Show()
        self.SetTopWindow(self.mainwin)

        self.set_handler()

        return True

if __name__ == "__main__":
    app = Application()
    app.MainLoop()
