/*
 * reinette, a french Apple II emulator, using SDL2
 * and powered by puce65c02 - a WDS 65c02 cpu emulator by the same author
 * Last modified 1st of July 2021
 * Copyright (c) 2021 Arthur Ferreira (arthur.ferreira2@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdio.h>
#include "reinette.h"


Video::Video() {
  memset(ramHeatmap, 0xFF000000, sizeof(ramHeatmap));
  memset(auxHeatmap, 0xFF000000, sizeof(auxHeatmap));

  // array from https://github.com/Michaelangel007/apple2_hgr_font_tutorial/
  const char FONT[] = {
      0x10, 0x08, 0x36, 0x7F, 0x3F, 0x3F, 0x7E, 0x36, // 0x00 ^@
      0x10, 0x08, 0x36, 0x41, 0x21, 0x21, 0x4A, 0x36, // 0x01 ^A
      0x00, 0x00, 0x02, 0x06, 0x0E, 0x1E, 0x36, 0x42, // 0x02 ^B
      0x7F, 0x22, 0x14, 0x08, 0x08, 0x14, 0x2A, 0x7F, // 0x03 ^C
      0x00, 0x40, 0x20, 0x11, 0x0A, 0x04, 0x04, 0x00, // 0x04 ^D
      0x7F, 0x3F, 0x5F, 0x6C, 0x75, 0x7B, 0x7B, 0x7F, // 0x05 ^E
      0x70, 0x60, 0x7E, 0x31, 0x79, 0x30, 0x3F, 0x02, // 0x06 ^F
      0x00, 0x18, 0x07, 0x00, 0x07, 0x0C, 0x08, 0x70, // 0x07 ^G
      0x08, 0x04, 0x02, 0x7F, 0x02, 0x04, 0x08, 0x00, // 0x08 ^H
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, // 0x09 ^I
      0x08, 0x08, 0x08, 0x08, 0x49, 0x2A, 0x1C, 0x08, // 0x0A ^J
      0x08, 0x1C, 0x2A, 0x49, 0x08, 0x08, 0x08, 0x08, // 0x0B ^K
      0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0C ^L
      0x40, 0x40, 0x40, 0x44, 0x46, 0x7F, 0x06, 0x04, // 0x0D ^M
      0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, // 0x0E ^N
      0x13, 0x18, 0x1C, 0x7E, 0x1C, 0x18, 0x10, 0x6F, // 0x0F ^O
      0x64, 0x0C, 0x1C, 0x3F, 0x1C, 0x0C, 0x04, 0x7B, // 0x10 ^P
      0x40, 0x48, 0x08, 0x7F, 0x3E, 0x1C, 0x48, 0x40, // 0x11 ^Q
      0x40, 0x48, 0x1C, 0x3E, 0x7E, 0x08, 0x48, 0x40, // 0x12 ^R
      0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, // 0x13 ^S
      0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7F, // 0x14 ^T
      0x08, 0x10, 0x20, 0x7F, 0x20, 0x10, 0x08, 0x00, // 0x15 ^U
      0x2A, 0x55, 0x2A, 0x55, 0x2A, 0x55, 0x2A, 0x55, // 0x16 ^V
      0x55, 0x2A, 0x55, 0x2A, 0x55, 0x2A, 0x55, 0x2A, // 0x17 ^W
      0x00, 0x3E, 0x41, 0x01, 0x01, 0x01, 0x7F, 0x00, // 0x18 ^X
      0x00, 0x00, 0x3F, 0x40, 0x40, 0x40, 0x7F, 0x00, // 0x19 ^Y
      0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x1A ^Z
      0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x1C, 0x08, 0x00, // 0x1B ^[
      0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, // 0x1C ^
      0x14, 0x14, 0x77, 0x00, 0x77, 0x14, 0x14, 0x00, // 0x1D ^]
      0x7F, 0x40, 0x40, 0x4C, 0x4C, 0x40, 0x40, 0x7F, // 0x1E ^^
      0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x1F ^_
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 space
      0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, // 0x21 !
      0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x22 "
      0x14, 0x14, 0x3E, 0x14, 0x3E, 0x14, 0x14, 0x00, // 0x23 #
      0x08, 0x3C, 0x0A, 0x1C, 0x28, 0x1E, 0x08, 0x00, // 0x24 $
      0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30, 0x00, // 0x25 %
      0x04, 0x0A, 0x0A, 0x04, 0x2A, 0x12, 0x2C, 0x00, // 0x26 &
      0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x27 '
      0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, // 0x28 (
      0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, // 0x29 )
      0x08, 0x2A, 0x1C, 0x08, 0x1C, 0x2A, 0x08, 0x00, // 0x2A *
      0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, // 0x2B +
      0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04, 0x00, // 0x2C ,
      0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, // 0x2D -
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, // 0x2E .
      0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, // 0x2F /
      0x1C, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x1C, 0x00, // 0x30 0
      0x08, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 0x31 1
      0x1C, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3E, 0x00, // 0x32 2
      0x3E, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1C, 0x00, // 0x33 3
      0x10, 0x18, 0x14, 0x12, 0x3E, 0x10, 0x10, 0x00, // 0x34 4
      0x3E, 0x02, 0x1E, 0x20, 0x20, 0x22, 0x1C, 0x00, // 0x35 5
      0x38, 0x04, 0x02, 0x1E, 0x22, 0x22, 0x1C, 0x00, // 0x36 6
      0x3E, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04, 0x00, // 0x37 7
      0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, // 0x38 8
      0x1C, 0x22, 0x22, 0x3C, 0x20, 0x10, 0x0E, 0x00, // 0x39 9
      0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, // 0x3A :
      0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04, 0x00, // 0x3B ;
      0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, // 0x3C <
      0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, // 0x3D =
      0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, // 0x3E >
      0x1C, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08, 0x00, // 0x3F ?
      0x1C, 0x22, 0x2A, 0x3A, 0x1A, 0x02, 0x3C, 0x00, // 0x40 @
      0x08, 0x14, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x00, // 0x41 A
      0x1E, 0x22, 0x22, 0x1E, 0x22, 0x22, 0x1E, 0x00, // 0x42 B
      0x1C, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1C, 0x00, // 0x43 C
      0x1E, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x00, // 0x44 D
      0x3E, 0x02, 0x02, 0x1E, 0x02, 0x02, 0x3E, 0x00, // 0x45 E
      0x3E, 0x02, 0x02, 0x1E, 0x02, 0x02, 0x02, 0x00, // 0x46 F
      0x3C, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3C, 0x00, // 0x47 G
      0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, // 0x48 H
      0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 0x49 I
      0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, // 0x4A J
      0x22, 0x12, 0x0A, 0x06, 0x0A, 0x12, 0x22, 0x00, // 0x4B K
      0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3E, 0x00, // 0x4C L
      0x22, 0x36, 0x2A, 0x2A, 0x22, 0x22, 0x22, 0x00, // 0x4D M
      0x22, 0x22, 0x26, 0x2A, 0x32, 0x22, 0x22, 0x00, // 0x4E N
      0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, // 0x4F O
      0x1E, 0x22, 0x22, 0x1E, 0x02, 0x02, 0x02, 0x00, // 0x50 P
      0x1C, 0x22, 0x22, 0x22, 0x2A, 0x12, 0x2C, 0x00, // 0x51 Q
      0x1E, 0x22, 0x22, 0x1E, 0x0A, 0x12, 0x22, 0x00, // 0x52 R
      0x1C, 0x22, 0x02, 0x1C, 0x20, 0x22, 0x1C, 0x00, // 0x53 S
      0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // 0x54 T
      0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, // 0x55 U
      0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, // 0x56 V
      0x22, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x22, 0x00, // 0x57 W
      0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, // 0x58 X
      0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, // 0x59 Y
      0x3E, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x00, // 0x5A Z
      0x3E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3E, 0x00, // 0x5B [
      0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // 0x5C back slash
      0x3E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3E, 0x00, // 0x5D ]
      0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, // 0x5E ^
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, // 0x5F _
      0x04, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 `
      0x00, 0x00, 0x1C, 0x20, 0x3C, 0x22, 0x3C, 0x00, // 0x61 a
      0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, // 0x62 b
      0x00, 0x00, 0x3C, 0x02, 0x02, 0x02, 0x3C, 0x00, // 0x63 c
      0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, // 0x64 d
      0x00, 0x00, 0x1C, 0x22, 0x3E, 0x02, 0x3C, 0x00, // 0x65 e
      0x18, 0x24, 0x04, 0x1E, 0x04, 0x04, 0x04, 0x00, // 0x66 f
      0x00, 0x00, 0x1C, 0x22, 0x22, 0x3C, 0x20, 0x1C, // 0x67 g
      0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x22, 0x00, // 0x68 h
      0x08, 0x00, 0x0C, 0x08, 0x08, 0x08, 0x1C, 0x00, // 0x69 i
      0x10, 0x00, 0x18, 0x10, 0x10, 0x10, 0x12, 0x0C, // 0x6A j
      0x02, 0x02, 0x22, 0x12, 0x0E, 0x12, 0x22, 0x00, // 0x6B k
      0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 0x6C l
      0x00, 0x00, 0x36, 0x2A, 0x2A, 0x2A, 0x22, 0x00, // 0x6D m
      0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x22, 0x00, // 0x6E n
      0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00, // 0x6F o
      0x00, 0x00, 0x1E, 0x22, 0x22, 0x1E, 0x02, 0x02, // 0x70 p
      0x00, 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, // 0x71 q
      0x00, 0x00, 0x3A, 0x06, 0x02, 0x02, 0x02, 0x00, // 0x72 r
      0x00, 0x00, 0x3C, 0x02, 0x1C, 0x20, 0x1E, 0x00, // 0x73 s
      0x04, 0x04, 0x1E, 0x04, 0x04, 0x24, 0x18, 0x00, // 0x74 t
      0x00, 0x00, 0x22, 0x22, 0x22, 0x32, 0x2C, 0x00, // 0x75 u
      0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, // 0x76 v
      0x00, 0x00, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x00, // 0x77 w
      0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, // 0x78 x
      0x00, 0x00, 0x22, 0x22, 0x22, 0x3C, 0x20, 0x1C, // 0x79 y
      0x00, 0x00, 0x3E, 0x10, 0x08, 0x04, 0x3E, 0x00, // 0x7A z
      0x38, 0x0C, 0x0C, 0x06, 0x0C, 0x0C, 0x38, 0x00, // 0x7B {
      0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // 0x7C |
      0x0E, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0E, 0x00, // 0x7D }
      0x2C, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7E ~
      0x00, 0x2A, 0x14, 0x2A, 0x14, 0x2A, 0x00, 0x00  // 0x7F DEL
  };

  // Generate the font arrays from the above array
  int raw;
  for (int c=0; c<128; c++) {
    for (int y=0; y<8; y++) {
      raw = FONT[c*8 + y];
      for (int x=0; x<7; x++) {
        if (raw & (1<<x)) {
          fontInverse[c][y][x]= 0xFF000000;
          fontNormal[c][y][x]= 0xFFFFFFFF;
        }
        else {
          fontInverse[c][y][x]= 0xFFFFFFFF;
          fontNormal[c][y][x]= 0xFF000000;
        }
      }
    }
  }
}


Video::~Video() {
  // delete[] fontInverse;
  // delete[] fontNormal;
  // ...
}


void Video::clearCache() {
  memset(previousDots,   -2, sizeof(previousDots));
  memset(previousBlocks, -2, sizeof(previousBlocks));
  memset(previousChars,  -2, sizeof(previousChars));
  memset(previousChars80,-2, sizeof(previousChars80));
}


void Video::update() {

  // Note: Colors may vary, depending upon the controls on the monitor or TV set
  const uint32_t lcolor[16] = {                                                 // the 16 low res colors
    0xFF000000, 0xFF5639E2, 0xFFCD741C, 0xFFAD6E7E,
    0xFF80811F, 0xFF7A8289, 0xFFE4A856, 0xFFDFB290,
    0xFF225897, 0xFF156CEA, 0xFF8F979E, 0xFFF0CEFF,
    0xFF31C090, 0xFFA6FDFF, 0xFFD5D29F, 0xFFFFFFFF
  };

  const uint32_t hcolor[16] = {                                                 // the high res colors (light & dark levels)
    0xFF000000, 0xFF31C090, 0xFFAD6E7E, 0xFFFFFFFF,
    0xFF000000, 0xFF156CEA, 0xFFE4A856, 0xFFFFFFFF,
    0xFF000000, 0xFF56373f, 0xFF196048, 0xFFFFFFFF,
    0xFF000000, 0xFF72542B, 0xFF0A3675, 0xFFFFFFFF
  };

  const uint32_t dhcolor[16] = {
    0xFF000000, 0xFFCD741C, 0xFF80811F, 0xFFDFB290,
    0xFF225897, 0xFF7A8289, 0xFF31C090, 0xFFE4A856,
    0xFF5639E2, 0xFFAD6E7E, 0xFF8F979E, 0xFFD5D29F,
    0xFF156CEA, 0xFFF0CEFF, 0xFFA6FDFF, 0xFFFFFFFF
  };

  const uint16_t offsetGR[24] = {                                               // base addresses for each line in TEXT or GR
    0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380,                     // lines 0-7
    0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8,                     // lines 8-15
    0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0                      // lines 16-23
  };

  const uint16_t offsetHGR[192] = {                                             // base addresses for each line in HGR
    0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00,             // lines 0-7
    0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80,             // lines 8-15
    0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00,             // lines 16-23
    0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80,
    0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00,
    0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80,
    0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00,
    0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80,
    0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28,
    0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8,
    0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28,
    0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8,
    0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28,
    0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8,
    0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28,
    0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8,
    0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50,
    0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0,
    0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50,
    0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0,
    0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50,
    0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0,             // lines 168-183
    0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50,             // lines 176-183
    0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0              // lines 184-191
  };


  // HIGH RES GRAPHICS
  if (!mmu->TEXT && mmu->HIRES && !mmu->DHIRES) {
    gfxmode = 40;
    uint8_t colorIdx;                                                           // index to the color array
    uint16_t word;
    uint8_t bits[16], bit, pbit, colorSet, even;
    int vRamBase = 0x2000 + mmu->PAGE2 * 0x2000;
    int endRaw = mmu->MIXED ? 160 : 192;

    for (int line = 0; line < endRaw; line++) {                                 // for every line
      for (int col = 0; col < 40; col += 2) {                                   // for every 7 horizontal dots
        word = (uint16_t)(mmu->ram[vRamBase + offsetHGR[line] + col + 1]) << 8; // store the two next bytes into 'word'
        word += mmu->ram[vRamBase + offsetHGR[line] + col];                     // in reverse order

        if (previousDots[line][col] != word) {                                  // check if this group of 7 dots need a redraw

          int x = col * 7;
          even = 0;

          for (bit = 0; bit < 16; bit++)                                        // store all bits 'word' into 'bits'
            bits[bit] = (word >> bit) & 1;
          colorSet = bits[7] * 4;                                               // select the right color set
          pbit = previousBit[line][col];                                        // the bit value of the left dot
          bit = 0;                                                              // starting at 1st bit of 1st byte

          while (bit < 15) {                                                    // until we reach bit7 of 2nd byte
            if (bit == 7) {                                                     // moving into the second byte
              colorSet = bits[15] * 4;                                          // update the color set
              bit++;                                                            // skip bit 7
            }

            colorIdx = even + colorSet + (bits[bit] << 1) + (pbit);
            screenPixels[line * 280 + x] =  hcolor[colorIdx];

            x++;
            pbit = bits[bit++];                                                 // proceed to the next pixel
            even = even ? 0 : 8;                                                // one pixel every two is darker
          }

          previousDots[line][col] = word;                                       // update the video cache
          if ((col < 37) && (previousBit[line][col + 2] != pbit)) {             // check color franging effect on the dot after
            previousBit[line][col + 2] = pbit;                                  // set pbit and clear the
            previousDots[line][col + 2] = -1;                                   // video cache for next dot
          }
        }                                                                       // if (previousDots[line][col] ...
      }
    }
  }

  // DOUBLE HIGH RES GRAPHICS
  else if (!mmu->TEXT && mmu->HIRES && mmu->DHIRES) {
    gfxmode = 80;
    // int vRamBase = 0x2000 + PAGE2 * 0x2000;
    int vRamBase = mmu->STORE80 ? 0x2000 : mmu->PAGE2 * 0x2000 + 0x2000;        // TODO : CHECK THIS !
    int endRaw = mmu->MIXED ? 160 : 192;
    uint32_t color;
    uint32_t dword;

    for (int line = 0; line < endRaw; line++) {
      for (int col = 0, x = 0; col < 39; col+=2) {

        int address = vRamBase + offsetHGR[line] + col;

        // combine 4 bytes of memory, from MAIN and AUX
        dword =  (uint32_t)(mmu->aux[address] & 0x7F);
        dword |= (uint32_t)(mmu->ram[address] & 0x7F) << 7;
        address++;
        dword |= (uint32_t)(mmu->aux[address] & 0x7F) << 14;
        dword |= (uint32_t)(mmu->ram[address] & 0x7F) << 21;

        int offset1 = 560 * line * 2;
        int offset2 = 560 + offset1;

        for (int p=0; p<7; p++, dword >>= 4) {
          color = dhcolor[dword & 0x0F];
          for (int a=0; a<4; a++, x++) {   // draw 4x2 pixels
            screenPixels80[offset1 + x] = color;
            screenPixels80[offset2 + x] = color;
          }
        }
      }
    }
  }

  // lOW RES GRAPHICS
  else if (!mmu->TEXT && !mmu->HIRES && !mmu->DHIRES) {                         // and not in HIRES
    gfxmode = 40;
    int vRamBase = mmu->PAGE2 * 0x0400 + 0x400;
    int endRaw = mmu->MIXED ? 20 : 24;
    uint32_t color;
    for (int col = 0; col < 40; col++) {                                        // for each column
      int x = col * 7;
      for (int line = 0; line < endRaw; line++) {                               // for each row

        glyph = mmu->ram[vRamBase + offsetGR[line] + col];                      // read video memory

        if (previousBlocks[line][col] != glyph) {                               // check if this block need a redraw
          previousBlocks[line][col] = glyph;

          int y = line * 8;                                                     // first block
          color = lcolor[glyph & 0x0F];                                         // first nibble
          for (int a=0; a<7; a++)
            for (int b=0; b<4; b++)
              screenPixels[(y+b) * 280 + x + a] =  color;

          y += 4;                                                               // second block
          color = lcolor[(glyph & 0xF0) >> 4];                                  // second nibble
          for (int a=0; a<7; a++)
            for (int b=0; b<4; b++)
              screenPixels[(y+b) * 280 + x + a] =  color;
        }
      }
    }
  }

  // DOUBLE lOW RES GRAPHICS
  else if (!mmu->TEXT && !mmu->HIRES && mmu->DHIRES) {
    gfxmode = 80;
    int vRamBase = mmu->PAGE2 * 0x0400 + 0x400;    // TODO : CHECK THIS !
    int endRaw = mmu->MIXED ? 20 : 24;
    int x, y;
    uint32_t color;
    for (int col = 0; col < 40; col++) {                                        // for each column
      x = col * 14;
      for (int line = 0; line < endRaw; line++) {                               // for each row
        y = line * 16;                                                          // first block

        glyph = mmu->aux[vRamBase + offsetGR[line] + col];                      // read AUX video memory
        color = lcolor[glyph & 0x0F];                                           // first nibble
        for (int a=0; a<7; a++)
          for (int b=0; b<8; b++)
            screenPixels80[(y+b) * 560 + (x+a)] = color;

        color = lcolor[(glyph & 0xF0) >> 4];                                    // second nibble
        for (int a=0; a<7; a++)
          for (int b=8; b<16; b++)
            screenPixels80[(y+b) * 560 + (x+a)] = color;

        glyph = mmu->ram[vRamBase + offsetGR[line] + col];                      // read MAIN video memory
        color = lcolor[glyph & 0x0F];                                           // first nibble
        for (int a=7; a<14; a++)
          for (int b=0; b<8; b++)
            screenPixels80[(y+b) * 560 + (x+a)] = color;

        color = lcolor[(glyph & 0xF0) >> 4];                                    // second nibble
        for (int a=7; a<14; a++)
          for (int b=8; b<16; b++)
            screenPixels80[(y+b) * 560 + (x+a)] = color;
      }
    }
  }

  // TEXT 40 COLUMNS
  if (!mmu->COL80 && (mmu->TEXT || mmu->MIXED)) {
    gfxmode = 40;
    int vRamBase = mmu->PAGE2 * 0x0400 + 0x400;
    int startRaw = mmu->TEXT ? 0 : 20;
    for (int col = 0; col < 40; col++) {                                        // for each column
      for (int line = startRaw; line < 24; line++) {                            // for each row

        glyph = mmu->ram[vRamBase + offsetGR[line] + col];                      // read video memory

        if (glyph > 0x7F) glyphAttr = A_NORMAL;                                 // is NORMAL ?
        else if (glyph < 0x40) glyphAttr = A_INVERSE;                           // is INVERSE ?
        else glyphAttr = A_FLASH;                                               // it's FLASH !

        if (previousChars[line][col] != (glyph <<(8*mmu->ALTCHARSET)) || glyphAttr == A_FLASH) {  // check if this char need a redraw
          previousChars[line][col] = (glyph <<(8*mmu->ALTCHARSET));

          glyph &= 0x7F;                                                        // unset bit 7
          if (glyph < 0x20) glyph |= 0x40;                                      // shifts to match the ASCII codes

          if ((!mmu->ALTCHARSET) && (glyphAttr == A_FLASH) && (glyph > 0x5F))
            glyph &= 0x3F;

          if (mmu->ALTCHARSET && (glyphAttr == A_FLASH)) {
            if (glyph >= 0x40 && glyph < 0x60) {
              glyph &= 0x3F;
              glyphAttr = A_NORMAL;
            }
            else if (glyph >= 0x60)
              glyphAttr = A_INVERSE;
          }

          if (glyphAttr == A_NORMAL || ((glyphAttr == A_FLASH) && (gui->frameNumber % 30 < 16))) {
            for (int yy=0; yy<8; yy++)
              for (int xx=0; xx<7; xx++)
                screenPixels[(yy+line*8)*280 + (xx+(col*7))] = fontNormal[glyph][yy][xx];
          }
          else {
            for (int yy=0; yy<8; yy++)
              for (int xx=0; xx<7; xx++)
                screenPixels[(yy+line*8)*280 + (xx+(col*7))] = fontInverse[glyph][yy][xx];
          }
        }
      }
    }
  }

  // TEXT 80 COLUMNS
  else if (mmu->COL80 && (mmu->TEXT || mmu->MIXED)) {
    gfxmode = 80;
    int startRaw = mmu->TEXT ? 0 : 20;
    int vRamBase = 0x0400; // + PAGE2 * 0x400;

    for (int col = 0; col < 40; col++) {                                        // for each column
      for (int line = startRaw; line < 24; line++) {                            // for each row
        glyph = mmu->ram[vRamBase + offsetGR[line] + col];                      // read video memory in Main memory

        if (glyph > 0x7F) glyphAttr = A_NORMAL;                                 // is NORMAL ?
        else if (glyph < 0x40) glyphAttr = A_INVERSE;                           // is INVERSE ?
        else glyphAttr = A_FLASH;                                               // it's FLASH !

        if (previousChars[line][col] != (glyph <<(8*mmu->ALTCHARSET)) || glyphAttr == A_FLASH) {  // check if this char need a redraw
          previousChars[line][col] = (glyph <<(8*mmu->ALTCHARSET));

          glyph &= 0x7F;                                                        // unset bit 7
          if (glyph < 0x20) glyph |= 0x40;                                      // shifts to match the ASCII codes

          if ((!mmu->ALTCHARSET) && (glyphAttr == A_FLASH) && (glyph > 0x5F))
            glyph &= 0x3F;

          if (mmu->ALTCHARSET && (glyphAttr == A_FLASH)) {
            if (glyph >= 0x40 && glyph < 0x60) {
              glyph &= 0x3F;
              glyphAttr = A_NORMAL;
            }
            else if (glyph >= 0x60)
              glyphAttr = A_INVERSE;
          }

          if (glyphAttr == A_NORMAL || ((glyphAttr == A_FLASH) && (gui->frameNumber % 30 < 16))) {
            for (int yy=0; yy<16; yy+=2)
              for (int xx=0; xx<7; xx++){
                screenPixels80[(yy+line*16)*560 + (xx+(((col*2)+1)*7))] = fontNormal[glyph][yy/2][xx];
                screenPixels80[(1+yy+line*16)*560 + (xx+(((col*2)+1)*7))] = fontNormal[glyph][yy/2][xx];
              }
          }
          else {
            for (int yy=0; yy<16; yy+=2)
              for (int xx=0; xx<7; xx++){
                screenPixels80[(yy+line*16)*560 + (xx+(((col*2)+1)*7))] = fontInverse[glyph][yy/2][xx];
                screenPixels80[(1+yy+line*16)*560 + (xx+(((col*2)+1)*7))] = fontInverse[glyph][yy/2][xx];
              }
          }
        }

        glyph = mmu->aux[vRamBase + offsetGR[line] + col];                      // read video memory in Auxiliary Memory

        if (glyph > 0x7F) glyphAttr = A_NORMAL;                                 // is NORMAL ?
        else if (glyph < 0x40) glyphAttr = A_INVERSE;                           // is INVERSE ?
        else glyphAttr = A_FLASH;                                               // it's FLASH !

        if (previousChars80[line][col] != (glyph <<(8*mmu->ALTCHARSET)) || glyphAttr == A_FLASH) {  // check if this char need a redraw
          previousChars80[line][col] = (glyph <<(8*mmu->ALTCHARSET));

          glyph &= 0x7F;                                                        // unset bit 7
          if (glyph < 0x20) glyph |= 0x40;                                      // shifts to match the ASCII codes

          if ((!mmu->ALTCHARSET) && (glyphAttr == A_FLASH) && (glyph > 0x5F))
            glyph &= 0x3F;

          if (mmu->ALTCHARSET && (glyphAttr == A_FLASH)) {
            if (glyph >= 0x40 && glyph < 0x60) {
              glyph &= 0x3F;
              glyphAttr = A_NORMAL;
            }
            else if (glyph >= 0x60)
              glyphAttr = A_INVERSE;
          }

          if (glyphAttr == A_NORMAL || ((glyphAttr == A_FLASH) && (gui->frameNumber % 30 < 16))) {
            for (int yy=0; yy<16; yy+=2)
              for (int xx=0; xx<7; xx++) {
                screenPixels80[(yy+line*16)*560 + (xx+(col*2*7))] = fontNormal[glyph][yy/2][xx];
                screenPixels80[(1+yy+line*16)*560 + (xx+(col*2*7))] = fontNormal[glyph][yy/2][xx];
              }
          }
          else {
            for (int yy=0; yy<16; yy+=2)
              for (int xx=0; xx<7; xx++) {
                screenPixels80[(yy+line*16)*560 + (xx+(col*2*7))] = fontInverse[glyph][yy/2][xx];
                screenPixels80[(1+yy+line*16)*560 + (xx+(col*2*7))] = fontInverse[glyph][yy/2][xx];
              }
          }
        }
      }
    }
  }

  // update ram & aux HEATMAPS (fade out green and red components)
  for (int address=0; address<65536; address++){
    if (ramHeatmap[address] & 0x000000FF)
      ramHeatmap[address]--;
    if (ramHeatmap[address] & 0x0000FF00)
    ramHeatmap[address] -= 256;

    if (auxHeatmap[address] & 0x000000FF)
      auxHeatmap[address]--;
    if (auxHeatmap[address] & 0x0000FF00)
    auxHeatmap[address] -= 256;
  }
}
