#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Witold Filipczyk
# License: Public domain code

import array
import gzip
import sys
import wx

#Font::PSF::zap(int off, unsigned int v)
#{
#	header2[off] = v & 255;
#	header2[off + 1] = (v >> 8) & 255;
#	header2[off + 2] = (v >> 16) & 255;
#	header2[off + 3] = (v >> 24) & 255;
#}
#
#void
#Font::PSF::createHeader2()
#{
#	zap(0, version);
#	zap(4, headersize);
#	zap(8, flags);
#	zap(12, length);
#	zap(16, charsize);
#	zap(20, height);
#	zap(24, width);
#}

WILDCARD = "PSF files (*.psf;*.psf.gz;*.psfu;*.psfu.gz)|*.psf;*.psf.gz;*.psfu;" \
"*.psfu.gz|All files|*"

editor = None
font_counter = 0
font_dict = {}

class ResizeDialog(wx.Dialog):
    def __init__(self, parent, y, x):
        wx.Dialog.__init__(self, parent, -1, title = "Resize",
            style = wx.DEFAULT_DIALOG_STYLE)
        labelWidth = wx.StaticText(self, -1, "Width")
        labelHeight = wx.StaticText(self, -1, "Height")
        self.spinWidth = wx.SpinCtrl(self, -1, value="width", min = 4, max = 32, 
            initial = x)
        self.spinHeight = wx.SpinCtrl(self, -1, value="height", min = 6,
            max = 32, initial = y)
        okButton = wx.Button(self, wx.ID_OK)
        cancelButton = wx.Button(self, wx.ID_CANCEL)
        grid = wx.GridSizer(3, 2)
        grid.Add(labelWidth)
        grid.Add(self.spinWidth)
        grid.Add(labelHeight)
        grid.Add(self.spinHeight)
        grid.Add(okButton)
        grid.Add(cancelButton)
        self.SetSizer(grid)
        grid.Fit(self)
        self.Fit()

class Editor(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, title="AFE", 
            style= wx.MINIMIZE_BOX | wx.CAPTION | wx.CLOSE_BOX)
        self.directory = "/lib/kbd/consolefonts"
        self.button = []
        self.width = 8
        self.height = 16
        self.grid = wx.GridSizer(self.height, self.width, 2, 2)
        self.font_id = -1
        self.char_id = -1
        tb = wx.ToolBar(self)
        save_char = wx.Button(tb, 32 * 32, label = "Save char")
        self.Bind(wx.EVT_BUTTON, self.saveChar, save_char)
        tb.AddControl(save_char)
        self.SetToolBar(tb)
        for i in xrange(32 * 32):
            b = wx.ToggleButton(self, i, size = (20, 20))
            self.button.append(b)
        self.grid.AddMany(self.button[:self.height * self.width])
        for i in xrange(self.height * self.width, 32 * 32):
            self.button[i].Show(False)
        self.SetSizer(self.grid)
        self.grid.Fit(self)

        mainMenu = wx.MenuBar()
        menu = wx.Menu()
        openItem = wx.MenuItem(menu, -1, 'Open\tCTRL-O', 'Open')
        newItem = wx.MenuItem(menu, -1, 'New\tCTRL-N', 'New')
        resizeItem = wx.MenuItem(menu, -1, 'Resize\tCTRL-R', 'Resize')
        exitItem = wx.MenuItem(menu, -1, 'Quit\tCTRL-Q', 'Quit')
        menu.AppendItem(openItem)
        menu.AppendItem(newItem)
        menu.AppendItem(resizeItem)
        menu.AppendItem(exitItem)
        self.Bind(wx.EVT_MENU, self.load, openItem)
        self.Bind(wx.EVT_MENU, self.new, newItem)
        self.Bind(wx.EVT_MENU, self.dlgResize, resizeItem)
        self.Bind(wx.EVT_MENU, self.exit, exitItem)
        mainMenu.Append(menu, "&File")
        self.SetMenuBar(mainMenu)

        self.Fit()

    def load(self, event):
        f = Font(self, load = True)
        f.Show()

    def new(self, event):
        f = Font(self, load = False)
        f.Show()

    def exit(self, event):
        self.Destroy()

    def resize(self, y, x):
        old_x, old_y = self.width, self.height
        old_char = self.getData(self.width, self.height)
        self.height = y
        self.width = x
        for i in xrange(32 * 32):
            self.button[i].SetValue(False)
        self.setChar(old_x, old_y, old_char)
        m = y * x
        self.grid.Clear()
        self.grid.SetRows(self.height)
        self.grid.SetCols(self.width)
        self.grid.AddMany(self.button[:m])
        for i in xrange(m):
            self.button[i].Show(True)
        for i in xrange(m, 32 * 32):
            self.button[i].Show(False)
        self.Fit()

    def saveChar(self, event):
        global font_dict

        if font_dict.has_key(self.font_id):
            f = font_dict[self.font_id]
            f.setChar(self.char_id)

    def dlgResize(self, event):
        dlg = ResizeDialog(self, self.height, self.width)
        if dlg.ShowModal() == wx.ID_OK:
            height = dlg.spinHeight.GetValue()
            width = dlg.spinWidth.GetValue()
            self.resize(height, width)
        dlg.Destroy()

    def setChar(self, width, height, data, font_id = -1, char_id = -1):
        self.font_id = font_id
        self.char_id = char_id
        charsize = ((width + 7) // 8)
#        print("setChar: %d %d %d" % (width, height, charsize))
#        print("len = %d" % len(data))
        if height > self.height:
            height = self.height
        for i in xrange(height):
            for j in xrange(charsize):
                ind = i * charsize + j
                for bit in xrange(7, -1, -1):
                    indeks = 7 - bit + j * 8
                    if indeks >= self.width:
                        continue
                    self.button[i * self.width + indeks].SetValue(data[ind] &\
                        (1 << bit))

    def getData(self, width, height):
        linesize = (width + 7) // 8
        charsize = linesize * height
        res = array.array('B', [0] * charsize)
#        print("getData: width = %d, height = %d, linesize = %d, "
#        "charsize = %d, len = %d" % (width, height, linesize, charsize,
#            len(res)))
        if height > self.height:
            height = self.height
        for i in xrange(height):
            for j in xrange(linesize):
                ind = i * linesize + j
                for bit in xrange(7, -1, -1):
                    indeks = 7 - bit + j * 8
                    if indeks >= self.width:
                        continue
                    if self.button[i * self.width + indeks].GetValue():
                        res[ind] |= (1 << bit)
        return res

def zamien(a):
    bit = 128
    d = 1
    w = 0
    while bit:
        if not (a & bit):
            w |= d
        bit >>= 1
        d <<= 1
    return w

class Font(wx.Frame):
    def __init__(self, parent, filename = "", load = 0):
        global font_counter, font_dict
        wx.Frame.__init__(self, parent, -1, title="", 
            pos= wx.DefaultPosition,
            style= wx.MINIMIZE_BOX | wx.CAPTION | wx.CLOSE_BOX)
        font_counter += 1
        font_dict[font_counter] = self
        self.id = font_counter
        self.directory = editor.directory
        self.filename = filename
        self.psf = PSF(filename)
        self.grid = wx.GridSizer(self.psf.length / 32, 32, 2, 2)
        self.button = []
        for i in xrange(1024):
            b = wx.BitmapButton(self, i, bitmap = wx.NullBitmap)
                #size = (self.psf.width + 4, self.psf.height + 4))
            self.Bind(wx.EVT_BUTTON, self.OnButton, b)
            b.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddle)
            b.Bind(wx.EVT_RIGHT_DOWN, self.OnRight)
            self.button.append(b)
#        self.grid.AddMany(self.button[:self.psf.length])
        self.SetSizer(self.grid)
#        self.grid.Fit(self)
        mainMenu = wx.MenuBar()
        menu = wx.Menu()
        openItem = wx.MenuItem(menu, -1, 'Open\tCTRL-O', 'Open')
        saveItem = wx.MenuItem(menu, -1, 'Save\tCTRL-S', 'Save')
        saveAsItem = wx.MenuItem(menu, -1, 'Save As', 'Save As')
        closeItem = wx.MenuItem(menu, -1, 'Close\tCTRL-W', 'Close')
        menu.AppendItem(openItem)
        menu.AppendItem(saveItem)
        menu.AppendItem(saveAsItem)
        menu.AppendItem(closeItem)
        self.Bind(wx.EVT_MENU, self.load, openItem)
        self.Bind(wx.EVT_MENU, self.save, saveItem)
        self.Bind(wx.EVT_MENU, self.saveAs, saveAsItem)
        self.Bind(wx.EVT_MENU, self.close, closeItem)
        mainMenu.Append(menu, "&File")
        self.SetMenuBar(mainMenu)
        self.Bind(wx.EVT_CLOSE, self.close)

        if load:
            self.load(None)
        self.display()

    def getBitmap(self, i):
        bits = self.psf.getData(i)
        for i in xrange(len(bits)):
            bits[i] = zamien(bits[i])
        return wx.BitmapFromBits(bits.tostring(), self.psf.width,
            self.psf.height)

    def display(self):
        #print "display"
        self.grid.Clear()
        #self.grid.SetCols(32)
        for i in xrange(self.psf.length):
            bitmap = self.getBitmap(i)
            self.button[i].SetBitmapLabel(bitmap)
            self.button[i].Show(True)
            #self.button[i].SetSize((self.psf.width + 4, self.psf.height + 4))
        for i in xrange(self.psf.length, 1024):
            self.button[i].Show(False)
        self.grid.SetRows(self.psf.length / 32)
        self.grid.AddMany(self.button[:self.psf.length])
        #self.SetSizer(self.grid)
        self.grid.Fit(self)
        self.Fit()

    def close(self, event):
        global font_dict
        del font_dict[self.id]
        self.Destroy()

    def load(self, event):
        dlg = wx.FileDialog(
            self, message="Choose file",
            defaultDir=self.directory,
            defaultFile=self.filename,
            wildcard=WILDCARD,
            style=wx.OPEN | wx.CHANGE_DIR
            )
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            editor.directory = self.directory = dlg.GetDirectory()
            self.psf.load(self.directory + '/' + self.filename)
            #self.psf.dump_info()
            self.display()
            self.SetTitle("AFE " + self.filename)
        dlg.Destroy()

    def save(self, event):
        self.psf.save(self.directory + '/' + self.filename)

    def saveAs(self, event):
        dlg = wx.FileDialog(
            self, message="Name the file",
            defaultDir=self.directory,
            defaultFile=self.filename,
            wildcard=WILDCARD,
            style=wx.SAVE | wx.CHANGE_DIR
            )
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            editor.directory = self.directory = dlg.GetDirectory()
            self.psf.save(self.directory + '/' + self.filename)
            self.SetTitle("AFE " + self.filename)
            #self.psf.dump_info()
        dlg.Destroy()

    def OnButton(self, event):
        global editor
        i = event.GetId()
        editor.setChar(self.psf.width, self.psf.height, self.psf.getData(i),
                       self.id, i)


    def setChar(self, i):
        data = editor.getData(self.psf.width, self.psf.height)
        self.psf.setData(i, data)
        bitmap = self.getBitmap(i)
        self.button[i].SetBitmapLabel(bitmap)

    def OnMiddle(self, event):
        global editor
        b = event.GetEventObject()
        i = b.GetId()
        #data = editor.getData(self.psf.width, self.psf.height)
        #print "OnMiddle: len(data) = %d" % len(data)
        #self.psf.setData(i, data)
        #bitmap = self.getBitmap(i)
        #b.SetBitmapLabel(bitmap)
        self.setChar(i)
        #print "OnMiddle:", b.GetId()

    def OnRight(self, event):
        b = event.GetEventObject()
        i = b.GetId()
        old = self.psf.getData(i)
        #data = editor.getData(self.psf.width, self.psf.height)
        #self.psf.setData(i, data)
        #bitmap = self.getBitmap(i)
        #b.SetBitmapLabel(bitmap)
        self.setChar(i)
        editor.setChar(self.psf.width, self.psf.height, old, self.id, i)
        #print "OnRight:", b.GetId()

class PSF:
    def __init__(self, filename):
        if not filename:
            self.type = 2
            self.header = array.array('B', (0x72, 0xb5, 0x4a, 0x86))
            self.height = editor.height
            self.width = editor.width
            self.charsize = self.height * ((self.width + 7) // 8)

            self.unicodeData = ""
            self.length = 512
            self.data = array.array('B', [0] * self.length * self.charsize)
            self.rest = ""
            self.header2 = array.array('B', [0] * 28)
            self.createHeader2()
        else:
            self.load(filename)

    def getData(self, i):
        return self.data[i * self.charsize: (i+1) * self.charsize]

    def setData(self, i, data):
        assert len(data) == self.charsize
        self.data[i * self.charsize : (i + 1) * self.charsize] = data

    def calc(self, offset):
        return self.header2[offset] + (self.header2[offset + 1] << 8) + \
            (self.header2[offset + 2] << 16) + (self.header2[offset + 3] << 24)

    def zap(self, off, v):
        self.header2[off] = v & 255
        self.header2[off + 1] = (v >> 8) & 255
        self.header2[off + 2] = (v >> 16) & 255
        self.header2[off + 3] = (v >> 24) & 255

    def createHeader2(self):
        self.zap(0, 0) # version
        self.zap(4, 32) # headersize
        self.zap(8, 0) # flags
        self.zap(12, self.length) # length
        self.zap(16, self.charsize)
        self.zap(20, self.height)
        self.zap(24, self.width)

    def dump_info(self):
        print "type = %d" % self.type
        print "charsize = %d" % self.charsize
        print "width = %d" % self.width
        print "height = %d" % self.height
        print "length = %d" % self.length
        print "has_unicode = %s" % self.has_unicode

    def load(self, filename):
        if filename.endswith(".gz"):
            f = gzip.open(filename, "rb")
        else:
            f = open(filename, "rb")
        self.type = 0
        d = f.read(4)
        self.header = array.array('B', d)
        if (self.header[0] == 0x36 and self.header[1] == 0x04):
            self.type = 1
            if self.header[2] & 1 == 1:
                self.length = 512
            else:
                self.length = 256
            if self.header[2] & 2 == 2:
                self.has_unicode = True
            else:
                self.has_unicode = False
            self.height = self.charsize = self.header[3]
            self.width = 8
            d = f.read(self.length * self.charsize)
            self.data = array.array('B', d)
            d = f.read()
            self.unicode_data = array.array('B', d)
        elif (self.header[0] == 0x72 and self.header[1] == 0xb5 and \
            self.header[2] == 0x4a and self.header[3] == 0x86):
            self.type = 2
            d = f.read(28)
            self.header2 = array.array('B', d)
            self.version = self.calc(0)
            self.headersize = self.calc(4)
            self.flags = self.calc(8)
            self.length = self.calc(12)
            self.charsize = self.calc(16)
            self.height = self.calc(20)
            self.width = self.calc(24)
            self.has_unicode = self.flags & 1 == 1
            self.rest_length = self.headersize - 32
            d = f.read(self.rest_length)
            self.rest = array.array('B', d)
            d = f.read(self.length * self.charsize)
            self.data = array.array('B', d)
            d = f.read()
            self.unicoda_data = array.array('B', d)
        f.close()

    def save(self, filename):
        if filename.endswith(".gz"):
            f = gzip.open(filename, "wb")
        else:
            f = open(filename, "wb")
        if self.type == 1:
            f.write(self.header)
            f.write(self.data)
            f.write(self.unicodeData)
        elif self.type == 2:
            f.write(self.header)
            f.write(self.header2)
            f.write(self.rest)
            f.write(self.data)
            f.write(self.unicodeData)
        f.close()

class MyApp(wx.App):

    def OnInit(self):
        global editor

        editor = Editor(None)
        editor.Show(True)
        self.SetTopWindow(editor)
        return True

if __name__ == "__main__":
    app = MyApp(0)     # Create an instance of the application class
    app.MainLoop()     # Tell it to start processing events
