from cpuC64 import *
import unittest


class TestOutput(unittest.TestCase):

   def setUp(self):
       self.cpu = CPUC64()
       self.cpu.load('output.prg')
       self.cpu.pc = 0x7000
       self.cpu.start = self.cpu.pc
       self.debug = False
       self.chars = [] # fuer den Vergleich des Inhalts einer Bildschirmzeile
       print("")
       print("---------------------------")
       print("test method: %s" % self._testMethodName)

   def check_wparams(self, wtlp, wlnp, wcols, wrows, wx, wy):
       if self.debug:
           self.cpu.mempage(0xc3)
       self.assertEqual(self.cpu.mem[0xc3e0], wtlp & 0xff)
       self.assertEqual(self.cpu.mem[0xc3e1], wtlp >> 8)
       self.assertEqual(self.cpu.mem[0xc3e2], wlnp & 0xff)
       self.assertEqual(self.cpu.mem[0xc3e3], wlnp >> 8)
       self.assertEqual(self.cpu.mem[0xc3e4], wcols)
       self.assertEqual(self.cpu.mem[0xc3e5], wrows)
       self.assertEqual(self.cpu.mem[0xc3e6], wx)
       self.assertEqual(self.cpu.mem[0xc3e7], wy)

   def check_wsaves(self, stlp, slnp, scols, srows, sx, sy):
       if self.debug:
           self.cpu.mempage(0xc3)
       self.assertEqual(self.cpu.mem[0xc3e8], stlp & 0xff)
       self.assertEqual(self.cpu.mem[0xc3e9], stlp >> 8)
       self.assertEqual(self.cpu.mem[0xc3ea], slnp & 0xff)
       self.assertEqual(self.cpu.mem[0xc3eb], slnp >> 8)
       self.assertEqual(self.cpu.mem[0xc3ec], scols)
       self.assertEqual(self.cpu.mem[0xc3ed], srows)
       self.assertEqual(self.cpu.mem[0xc3ee], sx)
       self.assertEqual(self.cpu.mem[0xc3ef], sy)

   def listing(self):
       self.cpu.end = self.cpu.pc
       self.cpu.disass()

   def run_test(self, debug=False):
       self.cpu.resetcyc()
       self.cpu.run()
       if self.debug:
           self.listing()
           self.cpu.registers()
           print("")
           print("Cycles: %d" % (self.cpu.cycles))
           print("")
           self.cpu.print_screen()

   def test_wselall(self):
       """ Testet die Auswahl eines grossen Fensters """
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wparams(0x0400, 0x0400, 39, 24, 0, 0)

   def test_wselleft(self):
       """ Testet die Auswahl eines halben Fensters links """
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wparams(0x0400, 0x0400, 19, 24, 0, 0)

   def test_wselright(self):
       """ Testet die Auswahl eines halben Fensters rechts """
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wparams(0x0414, 0x0414, 19, 24, 0, 0)

   def test_wseltop(self):
       """ Testet die Auswahl eines halben Fensters oben """
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wparams(0x0400, 0x0400, 39, 12, 0, 0)

   def test_wselbottom(self):
       """ Testet die Auswahl eines halben Fensters unten """
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wparams(0x0608, 0x0608, 39, 11, 0, 0)

   def test_wsave(self):
       """ Waehlt das linke Fenster aus und setzt wx auf 10
           sowie die aktuelle Zeile und wy auf die zweite Zeile.
           Die Parameter des linken Fensters muessen im
           Zwischenspeicher stehen.
       """
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.cpu.addcmd('lda #', 10)
       self.cpu.addcmd('sta aaaa', 0xe6, 0xc3) # wx
       self.cpu.addcmd('lda #', 40)
       self.cpu.addcmd('sta aaaa', 0xe2, 0xc3) # wlnp (low)
       self.cpu.addcmd('lda #', 1)
       self.cpu.addcmd('sta aaaa', 0xe7, 0xc3) # wy
       self.cpu.addcmd('jsr aaaa', 0x30, 0xc0) # wsave
       self.cpu.addcmd('brk')
       self.run_test()
       self.check_wsaves(0x0400, 0x0428, 19, 24, 10, 1)

   def test_delline_all(self):
       """ Waehlt ein grosses Fenster aus und loescht die erste Zeile.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
           (bytearrays werden mit null initialisert)
       """
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.cpu.addcmd('jsr aaaa', 0x12, 0xc0) # delline
       self.cpu.addcmd('brk')
       self.run_test()
       # Die ersten 40 Zeichen muessen Leerzeichen sein,
       self.assertEqual(sum(self.cpu.mem[0x0400:0x0428]), 40*32)
       # die restlichen Zeichen null
       self.assertEqual(sum(self.cpu.mem[0x0428:0x07e8]), 0)

   def test_delline_left(self):
       """ Waehlt ein halbes Fenster links aus und loescht die
           erste Zeile.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.cpu.addcmd('jsr aaaa', 0x12, 0xc0) # delline
       self.cpu.addcmd('brk')
       self.run_test()
       # Die ersten 20 Zeichen muessen Leerzeichen sein,
       self.assertEqual(sum(self.cpu.mem[0x0400:0x0414]), 20*32)
       # die restlichen Zeichen null
       self.assertEqual(sum(self.cpu.mem[0x0414:0x07e8]), 0)

   def test_delline_right(self):
       """ Waehlt ein halbes Fenster rechts aus und loescht die
           erste Zeile.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.cpu.addcmd('jsr aaaa', 0x12, 0xc0) # delline
       self.cpu.addcmd('brk')
       self.run_test()
       # Die ersten 20 Zeichen muessen null sein,
       self.assertEqual(sum(self.cpu.mem[0x0400:0x0414]), 0)
       # dann muessen 20 Leerzeichen kommen,
       self.assertEqual(sum(self.cpu.mem[0x0414:0x0428]), 20*32)
       # die restlichen Zeichen muessen wieder null sein
       self.assertEqual(sum(self.cpu.mem[0x0428:0x07e8]), 0)

   def test_delline_top(self):
       """ Waehlt ein halbes Fenster oben aus und loescht die
           erste Zeile.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.cpu.addcmd('jsr aaaa', 0x12, 0xc0) # delline
       self.cpu.addcmd('brk')
       self.run_test()
       # Die ersten 40 Zeichen muessen Leerzeichen sein,
       self.assertEqual(sum(self.cpu.mem[0x0400:0x0428]), 40*32)
       # die restlichen Zeichen null
       self.assertEqual(sum(self.cpu.mem[0x0428:0x07e8]), 0)

   def test_delline_bottom(self):
       """ Waehlt ein halbes Fenster unten aus und loescht die
           erste Zeile.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0) # wselbottom
       self.cpu.addcmd('jsr aaaa', 0x12, 0xc0) # delline
       self.cpu.addcmd('brk')
       self.run_test()
       # Die ersten 13 Zeilen muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x0400:0x0608]), 0)
       # dann muessen 40 Leerzeichen kommen,
       self.assertEqual(sum(self.cpu.mem[0x0608:0x0630]), 40*32)
       # die restlichen Zeichen muessen wieder null sein
       self.assertEqual(sum(self.cpu.mem[0x0630:0x07e8]), 0)

   def test_delwin_all(self):
       """ Waehlt ein grosses Fenster aus und loescht das ganze
           Fenster.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des siebten Speicherseite muessen Nullen stehen
           (bytearrays werden mit null initialisert)
       """
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('brk')
       self.run_test()
       # Insgesamt muessen 25 * 40 Leerzeichen im Bildschirmspeicher
       # stehen
       self.assertEqual(sum(self.cpu.mem[0x0400:0x07e8]), 25*40*32)
       # Die restlichen Zeichen in der siebten Speicherseite muessen
       # null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_delwin_left(self):
       """ Waehlt ein halbes Fenster links aus und loescht das ganze
           Fenster.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('brk')
       self.run_test()
       # In allen 25 Zeilen muessen die ersten 20 Zeichen
       # Leerzeichen sein und die restlichen Zeichen null
       adr = 0x400
       for i in range(25):
           start = adr + i*40
           end1 = start + 20
           end2 = end1 + 20
           self.assertEqual(sum(self.cpu.mem[start:end1]), 20*32)
           self.assertEqual(sum(self.cpu.mem[end1:end2]), 0)
       # Die restlichen Zeichen in der siebten Speicherseite
       # muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_delwin_right(self):
       """ Waehlt ein halbes Fenster rechts aus und loescht das ganze
           Fenster.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest des Bildschirmspeichers muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('brk')
       self.run_test()
       # In allen 25 Zeilen muessen die ersten 20 Zeichen
       # null sein und die restlichen Zeichen ein Leerzeichen
       adr = 0x400
       for i in range(25):
           start = adr + i*40
           end1 = start + 20
           end2 = end1 + 20
           self.assertEqual(sum(self.cpu.mem[start:end1]), 0)
           self.assertEqual(sum(self.cpu.mem[end1:end2]), 20*32)
       # Die restlichen Zeichen in der siebten Speicherseite
       # muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_delwin_top(self):
       """ Waehlt ein halbes Fenster oben und loescht das ganze
           Fenster.
           Als Ergebnis muessen dort Leerzeichen stehen und im
           Rest bis zum Ende des siebten Speicherseite muessen
           Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('brk')
       self.run_test()
       # In den ersten 13 Zeilen muessen Leerzeichen stehen
       self.assertEqual(sum(self.cpu.mem[0x0400:0x608]), 13*40*32)
       # Die restlichen Zeichen bis zum Ende der siebten
       # Speicherseite muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x0608:0x07ff]), 0)

   def test_delwin_bottom(self):
       """ Waehlt ein halbes Fenster unten und loescht das ganze
           Fenster.
           Als Ergebnis muessen dort Leerzeichen stehen und davor
           sowie danach bis zum Ende des siebten Speicherseite
           muessen Nullen stehen
       """
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0) # wselbottom
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('brk')
       self.run_test()
       # In den ersten 13 Zeilen muessen Nullen stehen
       self.assertEqual(sum(self.cpu.mem[0x0400:0x608]), 0)
       # In den darauf folgenden 12 Zeilen muessen Leerzeichen stehen
       self.assertEqual(sum(self.cpu.mem[0x0608:0x07e8]), 12*40*32)
       # Die restlichen Zeichen in der siebten Speicherseite
       # muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def hscroll_fill_line(self, adr, cols, cnt):
       """ Schreibt Ziffern in die erste Zeile eines Fensters
           (Startadresse wird mit adr uebergeben). Es wird nicht
           chrout verwendet, da chrout selbst hscroll benutzt.
           In der Vergleichsliste wird das Scrolling vorab
           durchgefuehrt (erstes Zeichen entfernen und am Schluss
           ein Leerzeichen hinzufuegen; das Ganze so oft, wie in
           cnt angegeben).
       """
       for i in range(cols):
           c = 48 + (i % 10)
           self.chars.append(c)
           self.cpu.mem[adr + i] = c
       for i in range(cnt):
           self.chars.pop(0)     # erstes Zeichen entfernen
           self.chars.append(32) # und Leerzeichen hinzufuegen

   def hscroll_program(self, cnt):
       """ Schreibt das Testprogramm; cnt legt die Anzahl der
           Schleifendurchgaenge fest
       """
       self.cpu.addcmd('ldx #', cnt)
       self.cpu.addcmd('jsr aaaa', 0x18, 0xc0) # hscroll
       self.cpu.addcmd('dex')
       self.cpu.addcmd('bne aa', 0xfa)
       self.cpu.addcmd('brk')

   def test_hscroll_all(self):
       """ Horizontales Scrolling fuer ein grosses Fenster """
       self.hscroll_fill_line(0x0400, 40, 1)
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.hscroll_program(1)
       self.run_test()
       self.assertEqual(sum(self.chars), sum(self.cpu.mem[0x0400:0x0428]))

   def test_hscroll_left(self):
       """ Horizontales Scrolling fuer ein halbes Fenster links """
       self.hscroll_fill_line(0x0400, 20, 5)
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.hscroll_program(5)
       self.run_test()
       self.assertEqual(sum(self.chars), sum(self.cpu.mem[0x0400:0x0414]))

   def test_hscroll_right(self):
       """ Horizontales Scrolling fuer ein halbes Fenster rechts """
       self.hscroll_fill_line(0x0414, 20, 10)
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.hscroll_program(10)
       self.run_test()
       self.assertEqual(sum(self.chars), sum(self.cpu.mem[0x0414:0x0428]))

   def test_hscroll_top(self):
       """ Horizontales Scrolling fuer ein halbes Fenster oben """
       self.hscroll_fill_line(0x0400, 40, 20)
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.hscroll_program(20)
       self.run_test()
       self.assertEqual(sum(self.chars), sum(self.cpu.mem[0x0400:0x0428]))

   def test_hscroll_bottom(self):
       """ Horizontales Scrolling fuer ein halbes Fenster unten """
       self.hscroll_fill_line(0x0608, 40, 40)
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0) # wselbottom
       self.hscroll_program(40)
       self.run_test()
       self.assertEqual(sum(self.chars), sum(self.cpu.mem[0x0608:0x0630]))

   def chrout_fill_mem(self, cnt):
       """ Schreibt 40 Ziffern an die Speicherstelle $6000
           In der Vergleichsliste wird ein horizontalles Scrolling
           vorab durchgefuehrt (erstes Zeichen entfernen und am
           Schluss ein Leerzeichen hinzufuegen).
       """
       for i in range(cnt):
           c = 48 + (i % 10)
           self.chars.append(c)
           self.cpu.mem[0x6000 + i] = c
       self.chars.pop(0)     # erstes Zeichen entfernen
       self.chars.append(32) # und Leerzeichen hinzufuegen

   def chrout_program(self, cnt):
       """ Schreibt das Testprogramm; cnt legt die Anzahl der
           Schleifendurchgaenge fest
       """
       self.cpu.addcmd('ldx #', 0)
       self.cpu.addcmd('lda aaaa,x', 0x00, 0x60)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('inx')
       self.cpu.addcmd('cpx #', cnt)
       self.cpu.addcmd('bne aa', 0xf5)
       self.cpu.addcmd('brk')

   def test_chrout_all(self):
       """ Gibt fuer ein grosses Fenster in der ersten Zeile
           so viele Ziffern aus, dass es einmal zum horizontalen
           Scrolling kommt.
           Ergebnis muss die Ziffernfolge ohne der ersten '0' sein
           und am Ende ein Leerzeichen enthalten.
       """
       self.chrout_fill_mem(40)
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.chrout_program(40)
       self.run_test()
       for i in range(40):
           self.assertEqual(self.cpu.mem[0x400 + i], self.chars[i])

   def test_chrout_left(self):
       """ Gibt fuer ein halbes Fenster links in der ersten Zeile
           so viele Ziffern aus, dass es einmal zum horizontalen
           Scrolling kommt.
           Ergebnis muss die Ziffernfolge ohne der ersten '0' sein
           und am Ende ein Leerzeichen enthalten.
       """
       self.chrout_fill_mem(20)
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.chrout_program(20)
       self.run_test()
       for i in range(20):
           self.assertEqual(self.cpu.mem[0x400 + i], self.chars[i])

   def test_chrout_right(self):
       """ Gibt fuer ein halbes Fenster rechts in der ersten Zeile
           so viele Ziffern aus, dass es einmal zum horizontalen
           Scrolling kommt.
           Ergebnis muss die Ziffernfolge ohne der ersten '0' sein
           und am Ende ein Leerzeichen enthalten.
       """
       self.chrout_fill_mem(20)
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.chrout_program(20)
       self.run_test()
       for i in range(20):
           self.assertEqual(self.cpu.mem[0x414 + i], self.chars[i])

   def test_chrout_top(self):
       """ Gibt fuer ein halbes Fenster oben in der ersten Zeile
           so viele Ziffern aus, dass es einmal zum horizontalen
           Scrolling kommt.
           Ergebnis muss die Ziffernfolge ohne der ersten '0' sein
           und am Ende ein Leerzeichen enthalten.
       """
       self.chrout_fill_mem(40)
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.chrout_program(40)
       self.run_test()
       for i in range(40):
           self.assertEqual(self.cpu.mem[0x400 + i], self.chars[i])

   def test_chrout_bottom(self):
       """ Gibt fuer ein halbes Fenster unten in der ersten Zeile
           so viele Ziffern aus, dass es einmal zum horizontalen
           Scrolling kommt.
           Ergebnis muss die Ziffernfolge ohne der ersten '0' sein
           und am Ende ein Leerzeichen enthalten.
       """
       self.chrout_fill_mem(40)
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0) # wselbottom
       self.chrout_program(40)
       self.run_test()
       for i in range(40):
           self.assertEqual(self.cpu.mem[0x0608 + i], self.chars[i])

   def vscroll_fill_screen(self, adr, lines, cols):
       """ Fuellt alle Zeilen mit fortlaufenden Ziffern im ersten und
           letzten Zeichen der Zeile, und dazwischen mit Leerzeichen,
           z.B. '1                  1' bei 20 Spalten
       """
       chars = []
       for i in range(cols):
           chars.append(32)
       for i in range(lines):
           chars[0] = 48 + (i % 10)
           chars[-1] = 48 + (i % 10)
           j = 0
           for c in chars:
               self.cpu.mem[adr + i*40 + j] = c
               j += 1

   def test_vscroll_all(self):
       """ Ein grosses Fenster wird mit Zeilen gefuellt, deren
           erstes und letztes Zeichen eine fortlaufende Ziffer
           ist, dazwischen sind immer Leerzeichen.
           Nach einem Scroll-Vorgang muessen alle Zeilen um
           eins nach oben gerueckt sein.
       """
       start = 0x0400
       self.vscroll_fill_screen(start, 25, 40)
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.cpu.addcmd('jsr aaaa', 0x1e, 0xc0) # vscroll
       self.cpu.addcmd('brk')
       self.run_test()
       # Test, ob in den ersten 23 Zeilen die richtigen
       # Zahlen beginnend ab 1 stehen
       for i in range(23):
           val = ((i+1) % 10) + 48
           lstart = start + i*40
           lend = lstart + 39
           self.assertEqual(self.cpu.mem[lstart], val)
           self.assertEqual(self.cpu.mem[lend], val)
       # In der letzten Zeile duerfen nur Leerzeichen stehen
       self.assertEqual(sum(self.cpu.mem[0x07c0:0x07e8]), 40*32)
       # Die restlichen Zeichen in der siebten Speicherseite muessen
       # null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_vscroll_left(self):
       """ Ein halbes Fenster links wird mit Zeilen gefuellt, deren
           erstes und letztes Zeichen eine fortlaufende Ziffer
           ist, dazwischen sind immer Leerzeichen.
           Nach einem Scroll-Vorgang muessen alle Zeilen um
           eins nach oben gerueckt sein.
       """
       start = 0x0400
       self.vscroll_fill_screen(start, 25, 20)
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.cpu.addcmd('jsr aaaa', 0x1e, 0xc0) # vscroll
       self.cpu.addcmd('brk')
       self.run_test()
       # Test, ob in den ersten 23 Zeilen die richtigen
       # Zahlen beginnend ab 1 stehen und in der rechten
       # Haelfte Nullen
       for i in range(23):
           val = ((i+1) % 10) + 48
           lstart = start + i*40
           lend = lstart + 19
           lstart2 = lend + 1
           lend2 = lstart2 + 20 # +1 wg. range
           self.assertEqual(self.cpu.mem[lstart], val)
           self.assertEqual(self.cpu.mem[lend], val)
           self.assertEqual(sum(self.cpu.mem[lstart2:lend2]), 0)
       # In der letzten Zeile duerfen in der linken Haelfte nur
       # Leerzeichen stehen, in der rechten Haelfte Nullen
       self.assertEqual(sum(self.cpu.mem[0x07c0:0x07d4]), 20*32)
       self.assertEqual(sum(self.cpu.mem[0x07d4:0x07e8]), 0)
       # Die restlichen Zeichen in der siebten Speicherseite muessen
       # null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_vscroll_right(self):
       """ Ein halbes Fenster rechts wird mit Zeilen gefuellt, deren
           erstes und letztes Zeichen eine fortlaufende Ziffer
           ist, dazwischen sind immer Leerzeichen.
           Nach einem Scroll-Vorgang muessen alle Zeilen um
           eins nach oben gerueckt sein.
       """
       start = 0x0414
       self.vscroll_fill_screen(start, 25, 20)
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.cpu.addcmd('jsr aaaa', 0x1e, 0xc0) # vscroll
       self.cpu.addcmd('brk')
       self.run_test()
       # Test, ob in den ersten 23 Zeilen die richtigen
       # Zahlen beginnend ab 1 stehen und in der linken
       # Haelfte Nullen
       # Da der ganze Bildschirmspeicher ueberprueft wird,
       # muss start wiederauf den Anfang des Bildschirm-
       # speichers zeigen (und nicht auf den Start des
       # Fensters)
       start = 0x0400
       for i in range(23):
           val = ((i+1) % 10) + 48
           lstart = start + i*40
           lend = lstart + 20 # +1 wg. range
           lstart2 = lend
           lend2 = lstart2 + 19
           self.assertEqual(sum(self.cpu.mem[lstart:lend]), 0)
           self.assertEqual(self.cpu.mem[lstart2], val)
           self.assertEqual(self.cpu.mem[lend2], val)
       # In der letzten Zeile duerfen in der linken Haelfte nur
       # Nullen stehen, in der rechten Haelfte Leerzeichen
       self.assertEqual(sum(self.cpu.mem[0x07c0:0x07d4]), 0)
       self.assertEqual(sum(self.cpu.mem[0x07d4:0x07e8]), 20*32)
       # Die restlichen Zeichen in der siebten Speicherseite muessen
       # null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_vscroll_top(self):
       """ Ein halbes Fenster oben wird mit Zeilen gefuellt, deren
           erstes und letztes Zeichen eine fortlaufende Ziffer
           ist, dazwischen sind immer Leerzeichen.
           Nach einem Scroll-Vorgang muessen alle Zeilen um
           eins nach oben gerueckt sein.
       """
       start = 0x0400
       self.vscroll_fill_screen(start, 13, 40)
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.cpu.addcmd('jsr aaaa', 0x1e, 0xc0) # vscroll
       self.cpu.addcmd('brk')
       self.run_test()
       # Test, ob in den ersten 12 Zeilen die richtigen
       # Zahlen beginnend ab 1 stehen
       for i in range(12):
           val = ((i+1) % 10) + 48
           lstart = start + i*40
           lend = lstart + 39
           self.assertEqual(self.cpu.mem[lstart], val)
           self.assertEqual(self.cpu.mem[lend], val)
       # In der letzten Zeile duerfen nur Leerzeichen stehen
       self.assertEqual(sum(self.cpu.mem[0x05e0:0x0608]), 40*32)
       # Die restlichen Zeichen bis zum Ende der siebten
       # Speicherseite muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x0608:0x07ff]), 0)

   def test_vscroll_bottom(self):
       """ Ein halbes Fenster oben wird mit Zeilen gefuellt, deren
           erstes und letztes Zeichen eine fortlaufende Ziffer
           ist, dazwischen sind immer Leerzeichen.
           Nach einem Scroll-Vorgang muessen alle Zeilen um
           eins nach oben gerueckt sein.
       """
       start = 0x0608
       self.vscroll_fill_screen(start, 12, 40)
       self.cpu.addcmd('jsr aaaa', 0x0f, 0xc0) # wselbottom
       self.cpu.addcmd('jsr aaaa', 0x1e, 0xc0) # vscroll
       self.cpu.addcmd('brk')
       self.run_test()
       # In der oberen Haelfte des Bildschirmspeichers muessen
       # Nullen stehen
       self.assertEqual(sum(self.cpu.mem[0x0400:0x608]), 0)
       # Test, ob in den ersten 11 Zeilen die richtigen
       # Zahlen beginnend ab 1 stehen
       for i in range(11):
           val = ((i+1) % 10) + 48
           lstart = start + i*40
           lend = lstart + 39
           self.assertEqual(self.cpu.mem[lstart], val)
           self.assertEqual(self.cpu.mem[lend], val)
       # In der letzten Zeile duerfen nur Leerzeichen stehen
       self.assertEqual(sum(self.cpu.mem[0x07c0:0x07e8]), 40*32)
       # Die restlichen Zeichen in der siebten Speicherseite
       # muessen null sein
       self.assertEqual(sum(self.cpu.mem[0x07e8:0x07ff]), 0)

   def test_newline(self):
       """ Gibt eine Zahl mit Zeilenumbruch im halben Fenster
           oben aus.
           wlnp und wy muessen auf die zweite Zeile zeigen
           und wx muss null sein
       """
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('lda #', 49)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('brk')
       self.run_test()
       self.assertEqual(self.cpu.mem[0xc3e2], 40) # wlnp
       self.assertEqual(self.cpu.mem[0xc3e3], 4)  # wlnp
       self.assertEqual(self.cpu.mem[0xc3e6], 0)  # wx
       self.assertEqual(self.cpu.mem[0xc3e7], 1)  # wy

   def test_newline_scroll(self):
       """ Gibt 13 Zeilen mit den Buchstaben "M" bis "A"
           im halben Fenster oben aus, so dass es zu einem
           Scrollvorgang kommt.
           Danach muss in der ersten Zeile ein "L" und in
           der letzten Zeile ein Leerzeichen stehen.
       """
       self.cpu.addcmd('jsr aaaa', 0x0c, 0xc0) # wseltop
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('ldx #', 13)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       self.cpu.addcmd('txa')
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('dex')
       self.cpu.addcmd('bne aa', 0xf0)
       self.cpu.addcmd('brk')
       self.run_test()
       self.assertEqual(self.cpu.mem[0x0400], 12) # "L"
       self.assertEqual(self.cpu.mem[0x05e0], 32) # Leerzeichen

   def test_hexout(self):
       """ Gibt die Werte 0-255 hexadezimal aus (immer
           16 Werte pro Zeile).
       """
       self.cpu.addcmd('jsr aaaa', 0x00, 0xc0) # winit
       self.cpu.addcmd('ldx #', 0)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # loop
       self.cpu.addcmd('txa')
       self.cpu.addcmd('jsr aaaa', 0x24, 0xc0) # hexout
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('inx')
       # Abbruchbedingung: X ist wieder null
       self.cpu.addcmd('beq aa', 0x11)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # Test, ob die unteren 4 Bits vom Index null sind
       # (dann wurde ein Vielfaches von 16 erreicht)
       self.cpu.addcmd('txa')
       self.cpu.addcmd('and #', 0x0f)
       self.cpu.addcmd('bne aa', 0x06)
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03) # X wieder laden
       self.cpu.addcmd('jmp aaaa', 0x08, 0x70) # -> loop
       self.cpu.addcmd('brk')
       self.run_test()
       for i in range(16):
           for j in range(16):
               adr = 0x400 + i*40 + j*2
               h = self.cpu.mem[adr]
               l = self.cpu.mem[adr+1]
               # Buchstabencodes A-F in ASCII umwandeln
               if l < 0x30:
                   l = l | 0x40
               if h < 0x30:
                   h = h | 0x40
               val = i * 16 + j
               self.assertEqual("%s%s" % (chr(h),chr(l)),
                   hexfmt8(val))

   def binout_program(self, start):
       """ Gibt 64 Werte binaer aus (immer 4 Werte pro Zeile). """
       self.cpu.addcmd('jsr aaaa', 0x00, 0xc0) # winit
       self.cpu.addcmd('ldx #', start)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # loop
       self.cpu.addcmd('txa')
       self.cpu.addcmd('jsr aaaa', 0x27, 0xc0) # binout
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('inx')
       # Abbruchbedingung (beim Startwert 192 wird X wieder null)
       if start < 192:
           self.cpu.addcmd('cpx #', start + 64)
       self.cpu.addcmd('beq aa', 0x19)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # Test, ob die unteren 2 Bits vom Index null sind
       # (dann wurde ein Vielfaches von 4 erreicht)
       self.cpu.addcmd('txa')
       self.cpu.addcmd('and #', 0x03)
       self.cpu.addcmd('bne aa', 0x09) # -> Leerzeichen
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03) # X wieder laden
       self.cpu.addcmd('jmp aaaa', 0x08, 0x70) # -> loop
       # Leerzeichen (wird nur nach den ersten drei Werten
       # als Trenner ausgegeben; beim vierten Wert gaebe es
       # ein horiz. Scrolling)
       self.cpu.addcmd('lda #', 32)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jmp aaaa', 0x08, 0x70) # -> loop
       self.cpu.addcmd('brk')
       self.run_test()
       for i in range(16):
           for j in range(4):
               # 10 Zeichen wg. Leerzeichen zw. den beiden
               # 4er-Gruppen und nach dem Wert
               adr = 0x400 + i*40 + j*10
               s = []
               # 9 Zeichen wg. Leerzeichen zw. den beiden
               # 4er-Gruppen
               for k in range(9):
                   s.append(chr(self.cpu.mem[adr+k]))
               val = start + i * 4 + j
               self.assertEqual("".join(s), binfmt8(val))

   def test_binout_00(self):
       """ Gibt die Werte 0-63 binaer aus. """
       self.binout_program(0)

   def test_binout_40(self):
       """ Gibt die Werte 64-127 binaer aus. """
       self.binout_program(64)

   def test_binout_80(self):
       """ Gibt die Werte 128-191 binaer aus. """
       self.binout_program(128)

   def test_binout_c0(self):
       """ Gibt die Werte 192-255 binaer aus. """
       self.binout_program(192)

   def decout_program(self, start, neg=False):
       """ Gibt 128 Werte dezimal aus (immer 8 Werte pro Zeile)
           Die Werte sind durch Plus- oder Minuszeichen getrennt.
       """
       self.cpu.addcmd('jsr aaaa', 0x00, 0xc0) # winit
       self.cpu.addcmd('ldx #', start)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # loop
       # Pluszeichen ausgeben (Minuszeichen wird von decout ausgegeben)
       if not neg:
           self.cpu.addcmd('lda #', ord('+'))
           self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       # Zahl ausgeben
       self.cpu.addcmd('txa')
       if neg:
           self.cpu.addcmd('sec')
       self.cpu.addcmd('jsr aaaa', 0x2a, 0xc0) # decout
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('inx')
       if start < 128:
           self.cpu.addcmd('cpx #', start + 128)
       # Abbruchbedingung (bei Startwert 128 wird X ist wieder null)
       self.cpu.addcmd('beq aa', 0x11)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # Test, ob die unteren 3 Bits vom Index null sind
       # (dann wurde ein Vielfaches von 8 erreicht)
       self.cpu.addcmd('txa')
       self.cpu.addcmd('and #', 0x07)
       self.cpu.addcmd('bne aa', 0x06)
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03) # X wieder laden
       self.cpu.addcmd('jmp aaaa', 0x08, 0x70) # -> loop
       self.cpu.addcmd('brk')
       self.run_test()
       for i in range(16):
           adr = 0x400 + i*40
           for j in range(8):
               val = start + i * 8 + j
               if neg:
                   # Zweier-Komplement in positiven Wert umwandeln
                   val = val ^ 255
                   val += 1
                   val = abs(val)
                   sval = "-%d" % val
               else:
                   sval = "+%d" % val
               s = []
               for k in range(len(sval)):
                   s.append(chr(self.cpu.mem[adr]))
                   adr += 1
               self.assertEqual("".join(s), sval)

   def test_decout_00(self):
       """ Gibt die Werte 0-127 dezimal aus. """
       self.decout_program(0)

   def test_decout_80(self):
       """ Gibt die Werte 128-255 dezimal aus. """
       self.decout_program(128)

   def test_decout_neg(self):
       """ Gibt die Werte -128 bis -1 dezimal aus. """
       self.decout_program(128, True)

   def test_asciiout(self):
       """ Die ASCII-Zeichen 32 bis 127 werden ausgegeben
           (immer 32 Zeichen pro Zeile, dadurch auch Test
           von chr(13)/LF)
           Im Bildschirmspeicher muessen in den Zeilen die
           entsprechenden 32 Bildschirmcodes stehen und danach
           Leerzeichen (dadurch auch Test von chr(147)/CLR)
           Die ASCII-Zeichen 160 bis 191 werden (noch) nicht
           getestet.
       """
       self.cpu.addcmd('jsr aaaa', 0x03, 0xc0) # wselall
       self.cpu.addcmd('lda #', 147) # clr
       self.cpu.addcmd('jsr aaaa', 0x2d, 0xc0) # asciiout
       self.cpu.addcmd('ldx #', 32)
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       # loop
       self.cpu.addcmd('txa')
       self.cpu.addcmd('jsr aaaa', 0x2d, 0xc0) # asciiout
       # nach 32 Zeichen einen Zeilenumbruch einfuegen (die
       # unteren 5 Bits sind bei einem Vielfachen von 16 null)
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('inx')
       self.cpu.addcmd('stx aaaa', 0x35, 0x03)
       self.cpu.addcmd('txa')
       self.cpu.addcmd('and #', 0x1f)
       self.cpu.addcmd('bne aa', 0x05)
       self.cpu.addcmd('lda #', 13) # LF
       self.cpu.addcmd('jsr aaaa', 0x2d, 0xc0) # asciiout
       self.cpu.addcmd('ldx aaaa', 0x35, 0x03)
       self.cpu.addcmd('cpx #', 128)
       self.cpu.addcmd('bne aa', 0xe4) # -> loop
       self.cpu.addcmd('brk')
       self.run_test()
       for i in range(3):
           start = 0x400 + i*40
           # zuerste die 32 ASCII-Werte
           for j in range(32):
               adr = start + j
               val = 32 + i * 32 + j
               self.assertEqual(
                   self.cpu._scrcode2ascii(self.cpu.mem[adr]), chr(val)
               )
           # dann noch 8 Leerzeichen
           for j in range(8):
               adr = start + 32 + j
               self.assertEqual(self.cpu.mem[adr], 32)

   def test_wswitch(self):
       """ Gibt im Wechel zwischen dem linken und rechten Fenster
           die Zeichen A-F und nach einem Zeilenumbruch die Zahlen
           1-6 aus.
           Links muss in den ersten beiden Zeilen "ACE" und "135"
           stehen und rechts "BDF" und "246".
       """
       self.cpu.addcmd('jsr aaaa', 0x06, 0xc0) # wselleft
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('lda #', 1)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jsr aaaa', 0x30, 0xc0) # wsave
       self.cpu.addcmd('jsr aaaa', 0x09, 0xc0) # wselright
       self.cpu.addcmd('jsr aaaa', 0x15, 0xc0) # delwin
       self.cpu.addcmd('lda #', 2)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       # Hier wird der Assembler-Code wiederholt durch eine
       # Python-Schleife erzeugt, anstatt in Assembler eine
       # Schleife zu implementieren.
       for i in range(3, 7):
           self.cpu.addcmd('jsr aaaa', 0x33, 0xc0) # wswitch
           self.cpu.addcmd('lda #', i)
           self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jsr aaaa', 0x33, 0xc0) # wswitch
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('lda #', 49)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.cpu.addcmd('jsr aaaa', 0x33, 0xc0) # wswitch
       self.cpu.addcmd('jsr aaaa', 0x21, 0xc0) # newline
       self.cpu.addcmd('lda #', 50)
       self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       # Gleiche Anmerkung wie oben
       for i in range(51, 55):
           self.cpu.addcmd('jsr aaaa', 0x33, 0xc0) # wswitch
           self.cpu.addcmd('lda #', i)
           self.cpu.addcmd('jsr aaaa', 0x1b, 0xc0) # chrout
       self.run_test()
       # links, erste Zeile
       adr = 0x0400
       s = []
       for i in range(3):
           s.append(self.cpu._scrcode2ascii(self.cpu.mem[adr+i]))
       self.assertEqual("".join(s), "ACE")
       # rechts, erste Zeile
       adr = 0x0414
       s = []
       for i in range(3):
           s.append(self.cpu._scrcode2ascii(self.cpu.mem[adr+i]))
       self.assertEqual("".join(s), "BDF")
       # links, zweite Zeile
       adr = 0x0428
       s = []
       for i in range(3):
           s.append(self.cpu._scrcode2ascii(self.cpu.mem[adr+i]))
       self.assertEqual("".join(s), "135")
       # rechts, zweite Zeile
       adr = 0x043c
       s = []
       for i in range(3):
           s.append(self.cpu._scrcode2ascii(self.cpu.mem[adr+i]))
       self.assertEqual("".join(s), "246")


if __name__ == '__main__':
   unittest.main()