#include <wx/wx.h>

#include "LisaCanvas.h"

#include "keyscan.h"

extern "C"
{
    #include "vars.h"
    int ImageWriter_LisaEm_Init(ImageWriterType *IW, int pcl, int ps, int xlib);
    int32 reg68k_external_execute(void);
    void cpu68k_init(void);
    void cpu68k_reset(void);
    void cpu68k_step(void);
    void unvars(void);
}
int effective_lisa_vid_size_x, effective_lisa_vid_size_y;

int screen_origin_x, screen_origin_y;

int initialize_all_subsystems(LisaConfig *lisa_config);

/* XXX Horrible horrible, fix me please! */
LisaCanvas *me;
bool floppy_just_ejected = false;
bool contrast_changed = false;

#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))

#define LISA_X          0
#define LISA_Y          0
#define LISA_WIDTH   1484
#define LISA_HEIGHT  1030

#define FLOPPY_X     1099
#define FLOPPY_Y      481
#define FLOPPY_WIDTH  331
#define FLOPPY_HEIGHT 191

#define POWER_X         0 
#define POWER_Y       738
#define POWER_WIDTH  1484
#define POWER_HEIGHT  292

#define VIDEO_X       (m_skin?140:0)
#define VIDEO_Y       (m_skin?124:0)
#define VIDEO_WIDTH   720
#define VIDEO_HEIGHT  500 /*XXX shouldn't this be 364*1.5? */

#define FLOPPY_NONE    -1
#define FLOPPY_ANIM0    0
#define FLOPPY_ANIM1    1
#define FLOPPY_ANIM2    2
#define FLOPPY_PRESENT  3
#define FLOPPY_ANIM_TICK 500

#define POWER_ANIM_NONE  -1
#define POWER_ANIM_STEPS 12
#define POWER_ANIM_TICK  40

#define KEYDIR_DOWN   -1
#define KEYDIR_UP      1
#define KEYDIR_ASCII   2

/* How many cycles should the CPU run for after receiving mouse/kbd event */
#define EVENT_HANDLE_CYCLES 10000

/* How often (in ms) should the emulation run */
#define EMULATION_TICK 20

#ifdef __WXOSX__
#include <sys/param.h>
#include <CoreFoundation/CoreFoundation.h>

/*
 * Returns the absolute path of the Resources directory for this app's
 * bundle. Used for loading images etc on Mac OS X.
 * 
 * NB: Be sure to free the path returned by this once you're done with it!
 */

char *getResourcesDir(void)
{
    CFURLRef pathCFURLRef;
    CFStringRef pathCFString;
    char *path;
    int pathLen;

    path = (char *)malloc(MAXPATHLEN);
    pathCFURLRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); // Get path to application bundle.
    pathCFString = CFURLCopyFileSystemPath(pathCFURLRef, kCFURLPOSIXPathStyle);
    CFStringGetCString(pathCFString, path, MAXPATHLEN, CFStringGetSystemEncoding());
    CFRelease(pathCFString);
    CFRelease(pathCFURLRef);

    pathLen = strlen(path);
    path[pathLen++] = '/';
    
    pathCFURLRef = CFBundleCopyResourcesDirectoryURL (CFBundleGetMainBundle()); // Get relative path to resources directory.
    pathCFString = CFURLCopyFileSystemPath(pathCFURLRef, kCFURLPOSIXPathStyle);
    CFStringGetCString(pathCFString, path + pathLen, MAXPATHLEN, CFStringGetSystemEncoding());
    CFRelease(pathCFString);
    CFRelease(pathCFURLRef);
    
    return path;
}
#endif
        
enum
{
    ID_FLOPPY_TIMER = 11001,
    ID_POWER_TIMER,
    ID_EMULATION_TIMER,
};

BEGIN_EVENT_TABLE(LisaCanvas, wxScrolledWindow)
    EVT_PAINT(LisaCanvas::OnPaint)
    EVT_SIZE(LisaCanvas::OnSize)
    EVT_MOTION(LisaCanvas::OnMouseEvent)
    EVT_LEFT_DOWN(LisaCanvas::OnMouseEvent)
    EVT_LEFT_UP(LisaCanvas::OnMouseEvent)
    EVT_KEY_DOWN(LisaCanvas::OnKeyDown)
    EVT_KEY_UP(LisaCanvas::OnKeyUp)
    EVT_CHAR(LisaCanvas::OnChar)
    EVT_TIMER(ID_FLOPPY_TIMER, LisaCanvas::OnFloppyTimer)
    EVT_TIMER(ID_POWER_TIMER, LisaCanvas::OnPowerTimer)
    EVT_TIMER(ID_EMULATION_TIMER, LisaCanvas::OnEmulationTimer)
END_EVENT_TABLE()

/* Load the skin images in from files and create wxMemoryDCs for them */
void LisaCanvas::SkinLoad(void)
{
#ifdef __WXOSX__
    char *rscDir = getResourcesDir();
    char *imgName = (char *)malloc(MAXPATHLEN);

    sprintf(imgName, "%s/lisaface.png", rscDir);
    m_lisa_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_lisa_dc = new wxMemoryDC(*m_lisa_bmp);
    
    sprintf(imgName, "%s/floppy0.png", rscDir);
    m_floppy0_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy0_dc = new wxMemoryDC(*m_floppy0_bmp);
    
    sprintf(imgName, "%s/floppy1.png", rscDir);
    m_floppy1_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy1_dc = new wxMemoryDC(*m_floppy1_bmp);
    
    sprintf(imgName, "%s/floppy2.png", rscDir);
    m_floppy2_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy2_dc = new wxMemoryDC(*m_floppy2_bmp);
    
    sprintf(imgName, "%s/floppy3.png", rscDir);
    m_floppy3_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy3_dc = new wxMemoryDC(*m_floppy3_bmp);
    
    sprintf(imgName, "%s/floppyN.png", rscDir);
    m_floppyN_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppyN_dc = new wxMemoryDC(*m_floppyN_bmp);
    
    sprintf(imgName, "%s/power_on.png", rscDir);
    m_poweron_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_poweron_dc = new wxMemoryDC(*m_poweron_bmp);
    
    free(rscDir);
#endif
}

/* Deallocate all the skin bitmaps and DCs. */
void LisaCanvas::SkinDispose(void)
{
    delete m_lisa_dc;
    delete m_floppy0_dc;
    delete m_floppy1_dc;
    delete m_floppy2_dc;
    delete m_floppy3_dc;
    delete m_floppyN_dc;
    delete m_poweron_dc;
    delete m_lisa_bmp;
    delete m_floppy0_bmp;
    delete m_floppy1_bmp;
    delete m_floppy2_bmp;
    delete m_floppy3_bmp;
    delete m_floppyN_bmp;
    delete m_poweron_bmp;
}

/* Create a VNC-style 'dot' cursor */
void LisaCanvas::CreateCursor(void)
{
    m_dot_cursor = NULL;
#if defined(__WXX11__) || defined(__WXGTK__) || defined(__WXOSX__)

#ifdef __WXOSX__
   /* LSB (0x01) is leftmost. First byte is top row. 1 is black/visible*/
   char cursor_bits[] = {0x00, 0x0e, 0x0e, 0x0e, 0x00};
   char mask_bits[]   = {0x1f, 0x1f, 0x1f, 0x1f, 0x1f};
#else
   char cursor_bits[] = {0xe0, 0xee, 0xee, 0xee, 0xe0};
   char mask_bits[]   = {0xe0, 0xe0, 0xe0, 0xe0, 0xe0};
#endif

   wxBitmap bmp = wxBitmap(cursor_bits, 8, 5);
   bmp.SetMask(new wxMask(wxBitmap(mask_bits, 8, 5)));

   wxImage img = bmp.ConvertToImage();
   img.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, "2");
   img.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, "2");

   m_dot_cursor = new wxCursor(img);
#endif
}

/* Create the video wxMemoryDC, and set up our fast drawing structures */
void LisaCanvas::InitVideo(void)
{
    /* Init Lisa's display bitmap */
    m_video_bmp = new wxBitmap(VIDEO_WIDTH, VIDEO_HEIGHT);
    m_video_dc = new wxMemoryDC(*m_video_bmp);
    
    m_video_byte_bmp = new wxBitmap(8, 257);
    m_video_byte_dc = new wxMemoryDC(*m_video_byte_bmp);
    
    m_vram = (unsigned char *) calloc(1, 32768);
    
    m_black = NULL;
    m_med_grey = NULL;
    m_white = NULL;
    
    UpdateContrast();
}

/*
 * Draw the temporary bitmaps used to render the onto the Lisa video DC.
 * These need to be updated every time the contrast setting changes.
 */ 
void LisaCanvas::UpdateContrast()
{
    /*
     * The Lisa hardware has a brightness potentiometer which we ignore for
     * the moment. contrast is in the range 0x00-0xff, where 0xff is a
     * completely back screen. The Lisa OS 3.1 preference's brightest setting
     * is 0x58, and the darkest is 0xa4. LOS's power off sequence dims the
     * screen in steps of 4 from the current value to x0fc.
     */
    
    /*
     * For the moment just keep the brightness logic really simple: black is
     * black, and white is the the brightest grey allowed by the contrast
     * variable. There are better approximations than this, but we can worry
     * about them later.
     */
    if (m_black) {
        delete m_black; m_black = NULL;
    }
    if (m_med_grey) {
        delete m_med_grey; m_med_grey = NULL;
    }
    if (m_white) {
        delete m_white; m_white = NULL;
    }
    int c = 0xff - contrast;
    m_black    = new wxColour(0,   0,   0  );
    m_med_grey = new wxColour(c/2, c/2, c/2);
    m_white    = new wxColour(c,   c,   c  );

    /*
     * Redraw our byte map. It has 257 rows of 8 pixels. The first 256 rows
     * represent what combination of 8 pixels to draw when a give byte is
     * found in the Lisa's video memory. The last row has a 50% grey used
     * for the 'enhanced' video mode.
     */
    m_video_byte_dc->SetBackground(wxBrush(*m_white));
    m_video_byte_dc->Clear();

    m_video_byte_dc->SetPen(wxPen(*m_black));
    int i, j;
    for(j=0; j<256; j++) {
        for(i=0; i<8; i++) {
            if (j & (0x80>>i)) m_video_byte_dc->DrawPoint(i, j);
        }
    }
    m_video_byte_dc->SetPen(wxPen(*m_med_grey));
    for(i=0;i<8;i++) {
        m_video_byte_dc->DrawPoint(i, 256);
    }
    
    /* Mark the entire screen as needing refreshing */
    m_video_dc->SetBackground(wxBrush(*m_black));
    m_video_dc->Clear();
    m_redraw_video_dc = true;
    
    contrast_changed = false;
}

LisaCanvas::LisaCanvas(wxWindow *parent)
           : wxScrolledWindow(parent, -1, wxPoint(0,0), wxSize(-1,-1),
           wxHSCROLL|wxVSCROLL)
{
    /* Initialise state */
    m_power = false;
    m_paused = false;
    m_power_step = POWER_ANIM_NONE;
    m_floppy = FLOPPY_NONE;
    m_floppy_to_insert = NULL;
    m_targ_floppy = FLOPPY_NONE;
    m_throttle_speed = 5000000;
    m_raw_keyboard = false;
    m_video_mode = VIDEO_UNCORRECTED;
    
    SkinLoad();
    
    CreateCursor();
    
    InitVideo();
    
    /*
     * We don't want to call UpdateWindowUI on the parent, because this
     * constructor is probably being called from the parent's constructor.
     */
    DoSetSkinOn(true, false);
    
    /* Black background for video when emu is off and no skin visible */
    SetBackgroundColour(*wxBLACK);
    
    m_floppy_timer = new wxTimer(this, ID_FLOPPY_TIMER);
    m_power_timer = new wxTimer(this, ID_POWER_TIMER);
    m_emulation_timer = new wxTimer(this, ID_EMULATION_TIMER);
    
    m_lisa_config = new LisaConfig();
    m_file_config = new wxFileConfig(_T("LisaEm"),
                        _T("sunder.NET"),
                        _T("/Users/bfoley/lisaem.conf"), //XXX
                        _T("/Users/bfoley/lisaem.conf"), //XXX
                        wxCONFIG_USE_LOCAL_FILE,
                        wxConvAuto());
    m_lisa_config->Load(m_file_config, floppy_ram);
    
    m_lisa_configframe = NULL;
    
    me = this; /* XXX. I know! Horrible! */
}

LisaCanvas::~LisaCanvas()
{
    m_floppy_timer->Stop();
    m_power_timer->Stop();
    m_emulation_timer->Stop();

    if (m_floppy_timer)     delete m_floppy_timer;
    if (m_power_timer)      delete m_power_timer;
    if (m_emulation_timer)  delete m_emulation_timer;
    
    if (m_video_dc)         delete m_video_dc;
    if (m_video_bmp)        delete m_video_bmp;

    if (m_dot_cursor)       delete m_dot_cursor;
    
    if (m_floppy_to_insert) free(m_floppy_to_insert);
    
    delete m_lisa_config;
    delete m_file_config;
    
    free(m_vram);
    
    SkinDispose();
}

void LisaCanvas::InsertFloppy(const wxString& floppy)
{
    if (m_targ_floppy == FLOPPY_PRESENT) return;
    
    m_targ_floppy = FLOPPY_PRESENT;
    m_floppy = FLOPPY_NONE;

    /*
     * We can't actually insert the floppy unless the emulation is running
     * because of how interrupts are handled, so queue up a a floppy to
     * insert.
     */
    const char *s = floppy.fn_str();
    m_floppy_to_insert = strdup((char *) s);
    m_floppy_timer->Start(FLOPPY_ANIM_TICK);
}

bool LisaCanvas::IsFloppyPresent()
{
    return m_targ_floppy == FLOPPY_PRESENT;
}

void LisaCanvas::EjectFloppy()
{
    if (m_targ_floppy == FLOPPY_NONE) return;

    m_targ_floppy = FLOPPY_NONE;
    m_floppy = FLOPPY_PRESENT;

    m_floppy_timer->Start(FLOPPY_ANIM_TICK);
}

/* Accessor for the emulation power state. Starts/stops the emu running */
void LisaCanvas::SetPowerOn(bool power)
{
    if (power == m_power) return;

    m_power = power;
    m_paused = false;
    if (m_power) {
        m_power_step = 0;
        StartEmulation();
    } else {
        m_power_step = POWER_ANIM_STEPS;
        StopEmulation();
    }
    
    m_power_timer->Start(POWER_ANIM_TICK);
    
    /* If power has been switched on, show the power light immediately */
    if (m_power) {
        RefreshVirtualRect(POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
    }
}
/*
 * Accessor for the emulation power state.
 * NB: The emu can be running but paused.
 */
bool LisaCanvas::IsPowerOn()
{
    return m_power;
}

void LisaCanvas::StartEmulation()
{    
    int ret=initialize_all_subsystems(m_lisa_config);
    if (ret != 0) {
        /* If -2 failed to load rom, pop up frame, and ask to reconfigure */
        printf("initialize_all_subsystems failed: %d\n", ret);
    }
    m_emulation_timer->Start(EMULATION_TICK);
    GetParent()->UpdateWindowUI();
}

/*
 * Accessor for whether or not the emulation is paused.
 * Pause/resume the emulation if it's running.
 */
void LisaCanvas::SetPaused(bool paused)
{
    if (!m_power) return;
    if (m_paused == paused) return;
    
    if (paused) {
        m_emulation_timer->Stop();
    } else {
        m_emulation_timer->Start();
    }
    m_paused = paused;
    GetParent()->UpdateWindowUI();
}

/*
 * Accessor for whether or not the emulation is paused.
 * NB, by definition if the power is off, the emulation is not paused.
 */
bool LisaCanvas::IsPaused()
{
    return m_paused;
}

void LisaCanvas::StopEmulation()
{
    m_emulation_timer->Stop();
    unvars();
    GetParent()->UpdateWindowUI();
}

void LisaCanvas::OnFloppyTimer(wxTimerEvent& WXUNUSED(event))
{
    printf("OnFloppyTimer: targ: %d cur: %d\n", m_targ_floppy, m_floppy);
    if (m_targ_floppy == FLOPPY_NONE) {
        m_floppy--;
    } else {
        m_floppy++;
    }
    
    if (m_floppy == m_targ_floppy) {
        m_floppy_timer->Stop();
        GetParent()->UpdateWindowUI();
        printf("OnFloppyTimer Final: %d %d\n", m_floppy, m_targ_floppy);
    }
    
    if (m_skin) {
        RefreshVirtualRect(FLOPPY_X, FLOPPY_Y, FLOPPY_WIDTH, FLOPPY_HEIGHT);
    }
}

void LisaCanvas::OnPowerTimer(wxTimerEvent& WXUNUSED(event))
{
    if (m_power) {
        if (m_power_step++ == POWER_ANIM_STEPS) {
            m_power_step = POWER_ANIM_NONE;
            m_power_timer->Stop();
        }
    } else {
        if (m_power_step-- == 0) {
            m_power_step = POWER_ANIM_NONE;
            m_power_timer->Stop();
            /* Power is finally off, update power light */
            if (m_skin) {
                RefreshVirtualRect(POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
            }
        }
    }
    
    RefreshVirtualRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
}

/* Accessor for the emulation top speed */
int LisaCanvas::GetThrottleSpeed(void)
{
    return m_throttle_speed;
}

/* Accessor for the emulation top speed */
void LisaCanvas::SetThrottleSpeed(int hertz)
{
    m_throttle_speed = hertz;
    GetParent()->UpdateWindowUI();
}

/* Accessor for whether we're passing raw keypress events to the emulator */
bool LisaCanvas::IsRawKeyboard(void)
{
    return m_raw_keyboard;
}

/* Accessor for whether we're passing raw keypress events to the emulator */
void LisaCanvas::SetRawKeyboard(bool raw)
{
    m_raw_keyboard = raw;
    GetParent()->UpdateWindowUI();
}

/* Accessor for how to render video on the simulated display */
int LisaCanvas::GetVideoMode()
{
    return m_video_mode;
}

/* Accessor for how to render video on the simulated display */
void LisaCanvas::SetVideoMode(int mode)
{
    if (mode == m_video_mode) return;
    m_video_mode = mode;
    
    /*
     * Mark the video as needing to be updated, displayed to screen, and
     * update the Frame's UI (eg menu options)
     */
    m_redraw_video_dc = true;
    RefreshVirtualRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    GetParent()->UpdateWindowUI();
}

void LisaCanvas::RunEmulationFor(int cycles)
{
    XTIMER prev_clock=cpu68k_clocks;
    do {
        reg68k_external_execute();

        seek_mouse_event();
        if (floppy_6504_wait && floppy_6504_wait<128) {
            floppy_6504_wait--;  // this is dumb! it should be a timer!
        }
        
        get_next_timer_event();
        
        /* If the power's been switched off, don't try to run any further */
        if (!m_power) break;
    } while (cpu68k_clocks - prev_clock < cycles);
}

void LisaCanvas::OnEmulationTimer(wxTimerEvent& WXUNUSED(event))
{
    static wxLongLong ts_prev = 0;
    wxLongLong cur = ::wxGetLocalTimeMillis();
    
    XTIMER prev_clock = cpu68k_clocks;
    
    if (floppy_just_ejected) {
        EjectFloppy();
        floppy_just_ejected = false;
    }
    
    if (m_floppy_to_insert) {
        floppy_insert(m_floppy_to_insert);
        free(m_floppy_to_insert);
        m_floppy_to_insert = NULL;
    }
    
    /*
     * When the powerup animation is running, only run the emu for a few
     * clock cycles. This helps keep the animation smooth.
     */
    if (m_power_step != POWER_ANIM_NONE) {
        RunEmulationFor(1000);
    } else {
        RunEmulationFor(m_throttle_speed / (1000/EMULATION_TICK));
    }
    check_running_lisa_os();
    
    UpdateVideo();
    
    if (ts_prev != 0) {
        //int diff = (cur - ts_prev).GetLo();
        //int diff2 = (::wxGetLocalTimeMillis() - cur).GetLo();
        //XXX if (diff2 > 5) printf("Emu tick: %d ms since last,  %d ms for %d CPU cycles\n", diff, diff2, (int)(cpu68k_clocks - prev_clock));
    }
    ts_prev = cur;
    
    /* Don't draw any video if we're still powering up */
    if (m_power_step != POWER_ANIM_NONE) return;
    
    /* Refresh the video */
    if (m_dirty_x_max != -1) {
        int width  = (m_dirty_x_max - m_dirty_x_min) + 1;
        int height = (m_dirty_y_max - m_dirty_y_min) + 1;
        
        //XXX printf("refreshing (%d,%d)-(%d,%d)\n", m_dirty_x_min, m_dirty_y_min, m_dirty_x_max, m_dirty_y_max);
            
        RefreshVirtualRect(VIDEO_X + m_dirty_x_min, VIDEO_Y + m_dirty_y_min,
            width, height);
        
        m_dirty_x_min = 999; m_dirty_x_max = -1;
        m_dirty_y_min = 999; m_dirty_y_max = -1;

    }

}

/*
 * Draw the latest changes to the video ram onto our video DC, and update
 * the dirty retangle to tell us what's changed.
 */
void LisaCanvas::UpdateVideo()
{
    if (contrast_changed) {
        UpdateContrast();
        m_dirty_x_min = 0;
        m_dirty_y_min = 0;
        m_dirty_x_max = VIDEO_WIDTH;
        m_dirty_y_max = VIDEO_HEIGHT;
    }

    switch (m_video_mode) {
        case VIDEO_CORRECTED:
            UpdateVideoCorrected(); break;
        case VIDEO_ENHANCED:
            UpdateVideoEnhanced(); break;
        default:
            UpdateVideoUncorrected();
    }

    m_redraw_video_dc = false;
}

/*
 * Redraw the video using a 1 to 1 correspondance between Lisa pixels and
 * display pixels. This makes the Lisa screen look squashed vertically, as
 * Lisa pixels are 1.5x taller than they are wide, but current displays all
 * use square pixels.
 */
void LisaCanvas::UpdateVideoUncorrected(void)
{
    int i, x, y;

    i = 0;
    for(y = 0; y < 364; y++) {
        for(x = 0; x < 720 ; x+=8) {
            unsigned char c = lisaram[videolatchaddress+i];
            if (c != m_vram[i] || m_redraw_video_dc) {
                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y + 68);
                m_dirty_y_max = max(m_dirty_y_max, y + 68);
                m_video_dc->Blit(x, y + 68, 8, 1, m_video_byte_dc,
                    0, c, wxCOPY, false);
            }
            i++;
        }
    }
    memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/*
 * Redraw the video in 'corrected' mode. This just does nearest neighbour
 * scaling in the vertical direction.
 */
void LisaCanvas::UpdateVideoCorrected(void)
{
    int x, y;

    for(y = 0; y < 500; y++) {
        int i =  (y * 364 / 500) * 90;
        for(x = 0; x < 720 ; x+=8) {
            unsigned char c = lisaram[videolatchaddress + i];
            if (c != m_vram[i] || m_redraw_video_dc) {
                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y);
                m_dirty_y_max = max(m_dirty_y_max, y);
                m_video_dc->Blit(x, y, 8, 1, m_video_byte_dc,
                    0, c, wxCOPY, false);
            }
            i++;
        }
    }
    memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/*
 * Redraw the video in 'enhanced' mode. This tries to choose intelligent
 * values for the pixels based on the three scanlines (top, middle, bottom):
 */
void LisaCanvas::UpdateVideoEnhanced(void)
{
    /*
     * This routine ends up drawing identically to (although much slower than) 
     * UpdateVideoCorrected, but displays any 50% grey pattern that it detects
     * as an actual grey.
     * It detects patterns by looking at three scanlines: the top, middle (or
     * current line) and bottom. It looks at 2x3 pixel blocks at a time to
     * find the characteristic grey patterns.
     */
    const static bool graymap[4*4*4]={
        0,0,0,0,  0,0,1,0,  0,1,0,0,  0,0,0,0,
        0,0,0,0,  0,0,0,0,  1,1,0,1,  0,1,0,0,
        0,0,0,0,  1,0,1,1,  0,0,0,0,  0,0,1,0,
        0,0,0,0,  0,0,1,1,  0,1,0,1,  0,0,0,0
    };
    
    int x, y;
    for(y = 0; y < 500; y++) {
        int line  = y * 364 / 500;
        int ti = max(line - 1, 0  ) * 90;
        int mi =     line           * 90;
        int bi = min(line + 1, 363) * 90;
        
        for(x = 0; x < 720 ; x+=8) {
            unsigned char t = lisaram[videolatchaddress + ti];
            unsigned char m = lisaram[videolatchaddress + mi];
            unsigned char b = lisaram[videolatchaddress + bi];
            
            int t3 = (t & 0xc0) >> 6, m3 = (m & 0xc0) >> 6, b3 = (b & 0xc0) >> 6;
            int t2 = (t & 0x30) >> 4, m2 = (m & 0x30) >> 4, b2 = (b & 0x30) >> 4;
            int t1 = (t & 0x0c) >> 2, m1 = (m & 0x0c) >> 2, b1 = (b & 0x0c) >> 2;
            int t0 =  t & 0x03,       m0 =  m & 0x03,       b0 =  b & 0x03;
            
            if (t != m_vram[ti] || m != m_vram[mi] ||
                b != m_vram[bi] || m_redraw_video_dc) {
                
                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y);
                m_dirty_y_max = max(m_dirty_y_max, y);

                m_video_dc->Blit(x, y, 8, 1, m_video_byte_dc,
                    0, m, wxCOPY, false);
                
                if (graymap[(t0 << 4) | (m0 << 2) | b0]) m_video_dc->Blit(
                    x + 6, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t1 << 4) | (m1 << 2) | b1]) m_video_dc->Blit(
                    x + 4, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t2 << 4) | (m2 << 2) | b2]) m_video_dc->Blit(
                    x + 2, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t3 << 4) | (m3 << 2) | b3]) m_video_dc->Blit(
                    x    , y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);

            }
            ti++;
            mi++;
            bi++;
       }
   }
   memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/* Accessor -- returns true if the skin is visible, false if not */
bool LisaCanvas::IsSkinOn(void)
{
    return m_skin;
}

/*
 * Accessor -- turns on/off the skin, and updates the canvas (and its
 * parent window) if any changes occur.
 */
void LisaCanvas::SetSkinOn(bool skin)
{
    if (skin == m_skin) return;
    DoSetSkinOn(skin, true);
}

/*
 * Turn on/off skin. Recalculate virtual window sizes, set maximum parent
 * frame size, resize the parent frame if necessary (ie it needs to be shrunk)
 * and invalidate the contents of the window.
 */
void LisaCanvas::DoSetSkinOn(bool skin, bool update_parent)
{
    int cw, ch, pmaxw, pmaxh;
    
    m_skin = skin;
    if (m_skin) {
        cw = LISA_WIDTH;
        ch = LISA_HEIGHT;
    } else {
        cw = VIDEO_WIDTH;
        ch = VIDEO_HEIGHT;
    }
    
    /* Set the size we want this window to be */
    SetMaxSize(wxSize(cw, ch));
    SetVirtualSize(wxSize(cw, ch));
    SetScrollbars(1, 1, cw, ch);
    
    /*
     * Calculate the overhead required by the parent window, and set the
     * parent's maximum size based on this.
     */
    wxSize pws = GetParent()->GetSize();
    wxSize pcs = GetParent()->GetClientSize();
    
    pmaxw = cw + pws.GetWidth() - pcs.GetWidth();
    pmaxh = ch + pws.GetHeight() - pcs.GetHeight();
    
    GetParent()->SetMaxSize(wxSize(pmaxw, pmaxh));
    
    /*
     * By changing the parent window's max size, we might make its current
     * size larger than the max in some dimension. Force the window size to
     * honour its maximum. Somehow you'd expect that wx would do this for you
     * itself, but looks like it doesn't...
     */ 
    pmaxw = min(pmaxw, pws.GetWidth());
    pmaxh = min(pmaxh, pws.GetHeight());
    
    GetParent()->SetSize(pmaxw, pmaxh);

    /*
     * This handles the case when we toggle from skinned window to
     * unskinned, and the window size happens to be exactly the virtual size
     * of the unskinned one. Without calling this fixup, the scrollbars
     * stay in place even though they're not needed, because normally the
     * scrollbar fixup is only called when the window size changes, not
     * when the virtual size changes. As per the comment in OnSize, this
     * is an unfortunate hack :(
     */
    FixupScrollbars();

    /*
     * Get the parent UI to update (so the statusbar etc repaint properly),
     * and refresh our client area since we've just toggled between skin/none
     */
    if (update_parent) GetParent()->UpdateWindowUI();
    Refresh();
}

void LisaCanvas::OnSize(wxSizeEvent& WXUNUSED(event))
{
    /*
     * This is a hack, but unfortunately a necessary one. wxScrolledWindow
     * in wxWindows 2.8 (and presumably earlier) mishandles the situation
     * where you have a wxSW whose window size is smaller than its virtual
     * size (and so scrollbars are visible), which is then resized so
     * WS = VS. The problem is the scrollbars are visible, so client
     * size < VS, and so the scrollbars remain visible. It stays like this
     * until the window is made big enough so that
     * WS = (VS.GetWidth() + scrollbarwidth, VS.GetHeight() + scrollbarheight)
     * So we have to patch this one up by hand. <sigh>
     */
    FixupScrollbars();
}

/* Work around a bug in wxWindows 2.8 as described in OnSize(...) */
void LisaCanvas::FixupScrollbars()
{
    wxSize ws = GetSize();
    wxSize ms = GetMaxSize();

    if (ms.GetWidth() == ws.GetWidth()) {
        SetScrollbar(wxHORIZONTAL, 0, 0, 0);
    }
    if (ms.GetHeight() == ws.GetHeight()) {
        SetScrollbar(wxVERTICAL, 0, 0, 0);
    }
}

/*
 * Even though wxScrolledWindow is supposed to implement a nice canvas
 * abstraction for you, you have to do the mapping from virtual to
 * client coordinates when you're doing a RefreshRect().
 */
void LisaCanvas::RefreshVirtualRect(int x, int y, int width, int height)
{
    int cx, cy, cwidth, cheight;
    wxRect rect = wxRect(x, y, width, height);
    
    GetViewStart(&cx, &cy);
    GetClientSize(&cwidth, &cheight);

    /* Offset our rectangle to translate from virtual to client coordinates */
    rect.Offset(-cx, -cy);

    /*
     * If the refresh rect goes off the top/left of the client rect, crop
     * the rect to the top/left and decrease the width/height appropriately.
     */
    if (rect.x < 0) { rect.width  += rect.x; rect.x = 0; }
    if (rect.y < 0) { rect.height += rect.y; rect.y = 0; }
    
    /*
     * If the refresh rect goes off the bottom/right of the client rect,
     * crop it.
     */
    if (rect.x + rect.width > cwidth) rect.width = max(0, cwidth - rect.x);
    if (rect.y + rect.height > cheight) rect.height = max(0, cheight - rect.y);

    /* If we end up with a non-empty rectangle, refresh it! */
    if (rect.width > 0 && rect.height > 0) RefreshRect(rect);
}

/*
 * Take the specified rectange in virtual (ie canvas) coordinates, convert
 * it to client coords and clip it. If the resulting rectangle is non-empty
 * (ie the bitmap dst isn't the edge of the window), blit it.
 */
void LisaCanvas::BlitVirtualRect(wxDC* src, int x, int y, int width, int height)
{
    wxRect rect = wxRect(x, y, width, height);
    wxPaintDC dc(this);

    int cx, cy, cwidth, cheight; /* client rect in virtual coordinates */
    int sx, sy;                  /* Offset into bitmap */
    
    sx = 0; sy = 0;
    GetViewStart(&cx, &cy);
    GetClientSize(&cwidth, &cheight);
    
    /* Offset blit destination rect to translate virtual->client coords */
    rect.Offset(-cx, -cy);
    
    /*
     * If the blit destination rect goes off the left/top of the client,
     * crop the dest rect, and update offset into bitmap accordingly.
     */
    if (rect.x < 0) { sx -= rect.x; rect.width  += rect.x; rect.x = 0; }
    if (rect.y < 0) { sy -= rect.y; rect.height += rect.y; rect.y = 0; }
    
    /*
     * If the blit destination rect goes off the bottom/right of the client,
     * crop the dest rect.
     */
    if (rect.x + rect.width > cwidth) rect.width = max(0, cwidth - rect.x);
    if (rect.y + rect.height > cheight) rect.height = max(0, cheight - rect.y);
    
    if (rect.width > 0 && rect.height > 0) {
        dc.Blit(rect.x, rect.y, rect.width, rect.height, src, sx, sy, wxCOPY);
    }
}

void LisaCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    wxRect video_rect = wxRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    if (m_skin) {
        /* Draw any skin that's necessary */
        BlitVirtualRect(m_lisa_dc, LISA_X, LISA_Y, LISA_WIDTH, LISA_HEIGHT);

        /* Draw any floppy animation that's necessary */
        wxMemoryDC *floppy_dc;
        switch (m_floppy) {
            case FLOPPY_ANIM0:   floppy_dc = m_floppy0_dc; break;
            case FLOPPY_ANIM1:   floppy_dc = m_floppy1_dc; break;
            case FLOPPY_ANIM2:   floppy_dc = m_floppy2_dc; break;
            case FLOPPY_PRESENT: floppy_dc = m_floppy3_dc; break;
            default:             floppy_dc = m_floppyN_dc;
        }
        BlitVirtualRect(floppy_dc, FLOPPY_X, FLOPPY_Y, FLOPPY_WIDTH, FLOPPY_HEIGHT);

        /* If the power's on (or we're shutting down), draw the power light */
        if (m_power || m_power_step != POWER_ANIM_NONE) {
            BlitVirtualRect(m_poweron_dc, POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
        }
    }
    
    /* If we're not powering up/down and the power is on, draw the video */
    if (m_power_step == POWER_ANIM_NONE && m_power) {
        BlitVirtualRect(m_video_dc, VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
        return;
    }
    
    /* If we're powering up/down, show the 'lightning flash' on the screen */
    if (m_power_step != POWER_ANIM_NONE) {
        wxPaintDC dc(this);
        DoPrepareDC(dc);
    
        int cx = VIDEO_X + VIDEO_WIDTH  / 2;
        int cy = VIDEO_Y + VIDEO_HEIGHT / 2;
        int w = m_power_step;
        int h = VIDEO_HEIGHT * m_power_step / POWER_ANIM_STEPS;
        
        /* Blacken the screen */
        dc.SetPen(*wxBLACK_PEN);
        dc.SetBrush(*wxBLACK_BRUSH);
        dc.DrawRectangle(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
        
        /* Draw the central flash */
        dc.SetPen(*wxWHITE_PEN);
        dc.SetBrush(*wxWHITE_BRUSH);
        dc.DrawRectangle(cx - w / 2, cy - h / 2, w, h);
    }
}

/*
 * Handle all of MouseDown/MouseUp/MouseMove here because of the way
 * the COPS chip receives handles the real Lisa mouse
 */
void LisaCanvas::OnMouseEvent(wxMouseEvent& event)
{
    wxPoint pos = event.GetPosition();

    /* Take into account the fact that the window might be scrolled */
    int cx, cy;
    GetViewStart(&cx, &cy);
    pos.x += cx;
    pos.y += cy;
    
    /*
     * Change the cursor to a dot when inside the video area.
     * NB, *if* this has a significant overhead, it might be better to
     * make the video rectangle a sub-window of this one, and just set
     * the cursor for the sub-window. This would let the window system
     * do all the tracking for us, and would avoid the cost of lots of
     * unnecessary SetCursor() calls.
     */
    wxRect video_rect = wxRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    if (m_dot_cursor) {
        if (video_rect.Contains(pos)) {
            SetCursor(*m_dot_cursor);
        } else {
            SetCursor(wxNullCursor);
        }
    }
    
    /* If the emulation is running, let it know the mouse has been moved */
    if (m_power && video_rect.Contains(pos)) {
        static wxLongLong lastup;
        wxLongLong now = ::wxGetLocalTimeMillis();
        
        int up = event.LeftUp();
        
        int x, y;
        x = pos.x - VIDEO_X;
        if (m_video_mode == VIDEO_UNCORRECTED) {
            y = pos.y - VIDEO_Y - 68;
        } else {
            y = (pos.y - VIDEO_Y) * 364 / 500;
        } 
        
        add_mouse_event(x, y, up?-1:event.LeftDown()?1:0);
        
        // double click hack  - fixme BUG BUG BUG - fixme
        if (up) {
            if (now-lastup < 500) {
                 add_mouse_event(x, y,  1);
                 add_mouse_event(x, y, -1);
                 add_mouse_event(x, y,  1);
                 add_mouse_event(x, y, -1);
            }
            lastup = now;
        }
        RunEmulationFor(EVENT_HANDLE_CYCLES);
    }
}

void LisaCanvas::OnKeyDown(wxKeyEvent& event)
{
    /* Let the emulation know about the keydown (in raw kbd mode) */
    if (m_raw_keyboard) {
        SendKeyEvent(event.GetKeyCode(), KEYDIR_DOWN);
    } else {
        event.Skip();
    }
}

void LisaCanvas::OnKeyUp(wxKeyEvent& event)
{
    /* Let the emulation know about the keyup (in raw kbd mode) */
    if (m_raw_keyboard) {
        SendKeyEvent(event.GetKeyCode(), KEYDIR_UP);
    } else {
        event.Skip();
    }
}

void LisaCanvas::OnChar(wxKeyEvent& event)
{
    if (m_raw_keyboard) {
        event.Skip();
        return;
    }
    
    /* Let the emulation know about a simulated keypress (in 'ASCII' mode) */
    int kc = event.GetKeyCode();
    
    /* Avoid inserting extra ASCII 1,2,4 when shift/alt/control are down. */
    if (kc == WXK_SHIFT || kc == WXK_ALT || kc == WXK_CONTROL) return;
    SendKeyEvent(kc, KEYDIR_ASCII);
}

void LisaCanvas::SendKeyEvent(int keycode, int keydir)
{
    if (!m_power || m_paused) return;
    
    int lisakey = 0;
    switch (keycode)
    {
        case 'a':                  lisakey = KEYCODE_A;          break;
        case 'b':                  lisakey = KEYCODE_B;          break;
        case 'c':                  lisakey = KEYCODE_C;          break;
        case 'd':                  lisakey = KEYCODE_D;          break;
        case 'e':                  lisakey = KEYCODE_E;          break;
        case 'f':                  lisakey = KEYCODE_F;          break;
        case 'g':                  lisakey = KEYCODE_G;          break;
        case 'h':                  lisakey = KEYCODE_H;          break;
        case 'i':                  lisakey = KEYCODE_I;          break;
        case 'j':                  lisakey = KEYCODE_J;          break;
        case 'k':                  lisakey = KEYCODE_K;          break;
        case 'l':                  lisakey = KEYCODE_L;          break;
        case 'm':                  lisakey = KEYCODE_M;          break;
        case 'n':                  lisakey = KEYCODE_N;          break;
        case 'o':                  lisakey = KEYCODE_O;          break;
        case 'p':                  lisakey = KEYCODE_P;          break;
        case 'q':                  lisakey = KEYCODE_Q;          break;
        case 'r':                  lisakey = KEYCODE_R;          break;
        case 's':                  lisakey = KEYCODE_S;          break;
        case 't':                  lisakey = KEYCODE_T;          break;
        case 'u':                  lisakey = KEYCODE_U;          break;
        case 'v':                  lisakey = KEYCODE_V;          break;
        case 'w':                  lisakey = KEYCODE_W;          break;
        case 'x':                  lisakey = KEYCODE_X;          break;
        case 'y':                  lisakey = KEYCODE_Y;          break;
        case 'z':                  lisakey = KEYCODE_Z;          break;
        case 'A' :                 lisakey = KEYCODE_A;          break;
        case 'B' :                 lisakey = KEYCODE_B;          break;
        case 'C' :                 lisakey = KEYCODE_C;          break;
        case 'D' :                 lisakey = KEYCODE_D;          break;
        case 'E' :                 lisakey = KEYCODE_E;          break;
        case 'F' :                 lisakey = KEYCODE_F;          break;
        case 'G' :                 lisakey = KEYCODE_G;          break;
        case 'H' :                 lisakey = KEYCODE_H;          break;
        case 'I' :                 lisakey = KEYCODE_I;          break;
        case 'J' :                 lisakey = KEYCODE_J;          break;
        case 'K' :                 lisakey = KEYCODE_K;          break;
        case 'L' :                 lisakey = KEYCODE_L;          break;
        case 'M' :                 lisakey = KEYCODE_M;          break;
        case 'N' :                 lisakey = KEYCODE_N;          break;
        case 'O' :                 lisakey = KEYCODE_O;          break;
        case 'P' :                 lisakey = KEYCODE_P;          break;
        case 'Q' :                 lisakey = KEYCODE_Q;          break;
        case 'R' :                 lisakey = KEYCODE_R;          break;
        case 'S' :                 lisakey = KEYCODE_S;          break;
        case 'T' :                 lisakey = KEYCODE_T;          break;
        case 'U' :                 lisakey = KEYCODE_U;          break;
        case 'V' :                 lisakey = KEYCODE_V;          break;
        case 'W' :                 lisakey = KEYCODE_W;          break;
        case 'X' :                 lisakey = KEYCODE_X;          break;
        case 'Y' :                 lisakey = KEYCODE_Y;          break;
        case 'Z' :                 lisakey = KEYCODE_Z;          break;
        case '0' :                 lisakey = KEYCODE_0;          break;
        case '1' :                 lisakey = KEYCODE_1;          break;
        case '2' :                 lisakey = KEYCODE_2;          break;
        case '3' :                 lisakey = KEYCODE_3;          break;
        case '4' :                 lisakey = KEYCODE_4;          break;
        case '5' :                 lisakey = KEYCODE_5;          break;
        case '6' :                 lisakey = KEYCODE_6;          break;
        case '7' :                 lisakey = KEYCODE_7;          break;
        case '8' :                 lisakey = KEYCODE_8;          break;
        case '9' :                 lisakey = KEYCODE_9;          break;
        case WXK_NUMPAD0:          lisakey = KEYCODE_0NUM;       break;
        case WXK_NUMPAD1:          lisakey = KEYCODE_1NUM;       break;
        case WXK_NUMPAD2:          lisakey = KEYCODE_2NUM;       break;
        case WXK_NUMPAD3:          lisakey = KEYCODE_3NUM;       break;
        case WXK_NUMPAD4:          lisakey = KEYCODE_4NUM;       break;
        case WXK_NUMPAD5:          lisakey = KEYCODE_5NUM;       break;
        case WXK_NUMPAD6:          lisakey = KEYCODE_6NUM;       break;
        case WXK_NUMPAD7:          lisakey = KEYCODE_7NUM;       break;
        case WXK_NUMPAD8:          lisakey = KEYCODE_8NUM;       break;
        case WXK_NUMPAD9:          lisakey = KEYCODE_9NUM;       break;
        case WXK_BACK:             lisakey = KEYCODE_BACKSPACE;  break;
        case WXK_TAB:              lisakey = KEYCODE_TAB;        break;
        case WXK_RETURN:           lisakey = KEYCODE_RETURN;     break;
        case WXK_ESCAPE:           lisakey = KEYCODE_CLEAR;      break;
        case WXK_SPACE:            lisakey = KEYCODE_SPACE;      break;
        case WXK_DELETE:           lisakey = KEYCODE_BACKSPACE;  break;
        case        '~':
        case        '`':           lisakey = KEYCODE_TILDE;      break;
                                                                 
        case        '_':                                         
        case        '-':           lisakey = KEYCODE_MINUS;      break;
                                                                 
        case        '+':                                         
        case        '=':           lisakey = KEYCODE_PLUS;       break;
                                                                 
        case        '{':                                         
        case        '[':           lisakey = KEYCODE_OBRAK;      break;
                                                                 
        case        '}':                                         
        case        ']':           lisakey = KEYCODE_CBRAK;      break;
                                                                 
                                                                 
        case        '|':                                         
        case        '\\':          lisakey = KEYCODE_BSLASH;     break;
                                                                 
        case        ':':                                         
        case        ';':           lisakey = KEYCODE_COLON;      break;
                                             
        case        '"':                                        
        case        '\'':          lisakey = KEYCODE_QUOTE;      break;
                                                                
        case        '<':                                        
        case        ',':           lisakey = KEYCODE_COMMA;      break;
                                                                  
        case        '>':                                        
        case        '.':           lisakey = KEYCODE_DOT;        break;
                                                              
        case        '?':                                      
        case        '/':           lisakey = KEYCODE_FSLASHQ;    break;
                                                                
        case WXK_NUMPAD_LEFT:      lisakey = KEYCODE_CURSORL;    break;
        case WXK_NUMPAD_UP:        lisakey = KEYCODE_CURSORU;    break;
        case WXK_NUMPAD_RIGHT:     lisakey = KEYCODE_CURSORR;    break;
        case WXK_NUMPAD_DOWN :     lisakey = KEYCODE_CURSORD;    break;
                                                                
                                                                
        case WXK_NUMPAD_INSERT:    lisakey = KEYCODE_LENTER;     break;
        case WXK_NUMPAD_DELETE:    lisakey = KEYCODE_BACKSPACE;  break;
                                                                
        case WXK_NUMPAD_MULTIPLY:  lisakey = KEYCODE_STAR_NUM;   break;
        case WXK_NUMPAD_ADD:       lisakey = KEYCODE_PLUS_NUM;   break;
        case WXK_NUMPAD_SEPARATOR:                              
        case WXK_NUMPAD_SUBTRACT:  lisakey = KEYCODE_MINUS_NUM;  break;
        case WXK_NUMPAD_DECIMAL:   lisakey = KEYCODE_DOTNUM;     break;
        case WXK_NUMPAD_DIVIDE:    lisakey = KEYCODE_FSLASH;     break;
                                                                
        case WXK_NUMPAD_ENTER:     lisakey = KEYCODE_ENTERNUM;   break;
        case WXK_NUMLOCK:          lisakey = KEYCODE_CLEAR;      break;
    }
    
    if (keydir == KEYDIR_ASCII) {
        keystroke_cops(keycode);
    } else {
        if (keydir == KEYDIR_DOWN) lisakey |= KEY_DOWN; 
        if (lisakey & ~KEY_DOWN) send_cops_keycode(lisakey);
    }
    
    /*
     * Run the emulation for a short amount of time so it can receive
     * and handle the event in a timely fashion.
     */
    RunEmulationFor(EVENT_HANDLE_CYCLES);
}

/*
 * Ripped from lisaem_wx.cpp. Needs to be cleaned up a bit: there *needs*
 * to be a seperation of concerns: the emulation itself should be
 * reponsible for setting up/tearing down itself. The GUI can poke it
 * with events (eg "there's a profile connected", "we use this ROM",
 * "power on now please", "reset everything"), but the GUI shouldn't need
 * to know how to do it. All the GUI does is watch the emulation and
 * represent it on screen, and take user input and channel it into the emu.
 * This will pay back bigtime when we have a couple of frontends: eg the
 * SDL console driven version, and a proper Cocoa frontend.
 */
 

uint8 evenparity(uint8 data)            // from http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
{
 uint32 v=data;
//v ^= v >> 16;       // not needed since we're working on a byte - only here for completeness, but commented out
//v ^= v >> 8;        // ditto
  v ^= v >> 4;
  v &= 0xf;
  return !(  (0x6996 >> v) & 1  );
}

/* Connects a Dual Parallel port card to the specified slot (numbered 0-2). */
void connect_2x_parallel_to_slot(int slot)
{
    int low, high, v;
    switch (slot)
    {
        case 0:
            get_exs0_pending_irq = get_exs0_pending_irq_2xpar;
            low = Ox0000_slot1l; high = Ox2000_slot1h; v = 3;
            break;
        case 1:
            get_exs1_pending_irq = get_exs1_pending_irq_2xpar;
            low = Ox4000_slot2l; high = Ox6000_slot2h; v = 5;
            break;
        case 2:
            get_exs2_pending_irq = get_exs2_pending_irq_2xpar;
            low = Ox8000_slot3l; high = Oxa000_slot3h; v = 7;
            break;
        default:
           ALERT_LOG(0, "Unknown slot number %d, should be 0-2!", slot);
           return;
    }

    mem68k_memptr    [low] = lisa_mptr_2x_parallel_l;
    mem68k_fetch_byte[low] = lisa_rb_2x_parallel_l;
    mem68k_fetch_word[low] = lisa_rw_2x_parallel_l;
    mem68k_fetch_long[low] = lisa_rl_2x_parallel_l;
    mem68k_store_byte[low] = lisa_wb_2x_parallel_l;
    mem68k_store_word[low] = lisa_ww_2x_parallel_l;
    mem68k_store_long[low] = lisa_wl_2x_parallel_l;

    mem68k_memptr    [high] = lisa_mptr_2x_parallel_h;
    mem68k_fetch_byte[high] = lisa_rb_2x_parallel_h;
    mem68k_fetch_word[high] = lisa_rw_2x_parallel_h;
    mem68k_fetch_long[high] = lisa_rl_2x_parallel_h;
    mem68k_store_byte[high] = lisa_wb_2x_parallel_h;
    mem68k_store_word[high] = lisa_ww_2x_parallel_h;
    mem68k_store_long[high] = lisa_wl_2x_parallel_h;

    reset_via(v);
    reset_via(v+1);
    via[v].active = 1;
    via[v+1].active = 1;
}

/* Connects a printer/profile to the specified VIA */
void connect_device_to_via(int v, wxString device, wxString *file)
{
    if (device.IsSameAs("ADMP", false)) {
        via[v].ADMP = (ImageWriterType *) malloc(sizeof(ImageWriterType));
        via[v].ADMP->page = NULL;
        ImageWriter_LisaEm_Init(via[v].ADMP, 0, 1, 0);  // &ADMP,pcl,ps,xlib
        DEBUG_LOG(0, "Attached ADMP to VIA#%d", v);
    }

    if (device.IsSameAs("PROFILE", false)) {
        if (file->Len()==0) {
            if (v == 2) *file = _T("lisaem-profile.dc42");
            if (v == 3) *file = _T("lisaem-s1h-profile.dc42");
            if (v == 4) *file = _T("lisaem-s1l-profile.dc42");
            if (v == 5) *file = _T("lisaem-s2h-profile.dc42");
            if (v == 6) *file = _T("lisaem-s2l-profile.dc42");
            if (v == 7) *file = _T("lisaem-s3h-profile.dc42");
            if (v == 8) *file = _T("lisaem-s3l-profile.dc42");
        }
        DEBUG_LOG(0, "Attempting to attach VIA#%d to profile %s", v, file->c_str());

        via[v].ProFile = (ProFileType *)malloc(sizeof(ProFileType));
        int i = profile_mount((char *)file->c_str(), via[v].ProFile);
        if (i) {
            free(via[v].ProFile);
            via[v].ProFile = NULL;
            DEBUG_LOG(0, "Couldn't get profile because: %d",i);
        } else {
            ProfileReset(via[v].ProFile);
        }
    }
}

/*
 * Connect some (virtual) device to one of the serial ports. 0 is Serial A,
 * 1 is Serial B. A number of the parameters passed in may be modified by
 * this code.
 */
void connect_device_to_serial(int port, FILE **scc_port_F, uint8 *serial,
    wxString *setting, wxString *param, int *scc_telnet_port,
    ImageWriterType **scc_IW)
{
    if (port !=0 && port != 1) {
        DEBUG_LOG(0, "Warning Serial port is not A/B: %d", port);
        return;
    }

    if (setting->IsSameAs("LOOPBACK",false)) {
        *scc_port_F = NULL;
        *serial = SCC_LOOPBACKPLUG;
        return;
    }

    if (setting->IsSameAs("NOTHING", false) || setting->IsSameAs("NULL", false) || setting->Len()==0) {
        *scc_port_F = NULL;
        *serial = SCC_NOTHING;
        *setting = "NOTHING";  // fill in for writing into ini file
        return;
    }

    if (setting->IsSameAs("FILE", false))
    {
        *scc_port_F = fopen(*param, "r+b");
        if (!*scc_port_F) {
            wxString err = wxString();
            err.Printf("Could not map Serial port %c to file %s", (port==0?'A':'B'), param->c_str());
            wxMessageBox(err, _T("Serial port configuration"),  wxICON_INFORMATION | wxOK);
            *scc_port_F = NULL;
            *serial = SCC_NOTHING;
        } else {
            *serial = SCC_FILE;
        }
        return;
    }

    if (setting->IsSameAs("PIPE", false)) {
        *scc_port_F = popen(*param, "r+b");
        if (!*scc_port_F) {
            wxString err = wxString();
            err.Printf("Could not map Serial port %c to pipe %s", (port==0?'A':'B'), param->c_str());
            wxMessageBox(err, _T("Serial port configuration"),  wxICON_INFORMATION | wxOK);
            *scc_port_F = NULL;
            *serial = SCC_NOTHING;
        } else {
            *serial = SCC_PIPE;
        }
        return;
    }

#ifndef __MSVCRT__
    if (setting->IsSameAs("TELNETD", false)) {
        unsigned long x;
        *scc_port_F = NULL;
        *serial = SCC_TELNETD;

        *scc_telnet_port = (param->ToULong(&x, 10)==false) ? 9300+port : x;
        init_telnet_serial_port(port);
        return;
    }
#endif

    // Not sure if we'll keep the image writer a separate program or if it
    // will be built in in the future.  This is if it's built in:
    if (setting->IsSameAs("IMAGEWRITER-PS", false)) {
        *scc_port_F = NULL;
        *serial = SCC_IMAGEWRITER_PS;

        if ((*scc_IW=(ImageWriterType *)malloc(sizeof(ImageWriterType)))==NULL) {
            wxString err = wxString();
            err.Printf("Could not attach PostScript ImageWriter to Serial port %c", (port==0?'A':'B'));
            wxMessageBox(err, _T("Serial port configuration"), wxICON_INFORMATION | wxOK);
            *serial = SCC_NOTHING;
        } else {
            (*scc_IW)->page = NULL;
            ImageWriter_LisaEm_Init(*scc_IW, 0, 1, 0);
        }
        return;
    }

    if (setting->IsSameAs("IMAGEWRITER-PCL", false)) {
        *scc_port_F = NULL;
        *serial = SCC_IMAGEWRITER_PCL;

        if ((*scc_IW=(ImageWriterType *)malloc(sizeof(ImageWriterType)))==NULL) {
            wxString err = wxString();
            err.Printf("Could not attach PCL ImageWriter to Serial port %c", (port==0?'A':'B'));
            wxMessageBox(err, _T("Serial port configuration"), wxICON_INFORMATION | wxOK);
            *serial = SCC_NOTHING;
        } else {
            (*scc_IW)->page = NULL;
            ImageWriter_LisaEm_Init(*scc_IW, 1, 0, 0);
        }
        return;
    }

    DEBUG_LOG(0, "Warning: unrecognised Serial %c setting '%s'", port==0?'A':'B', setting->c_str());
}

int initialize_all_subsystems(LisaConfig *lisa_config)
{
    buglog=stderr;
    DEBUG_LOG(0,"initializing all subsystems...");

    // initialize parity array (it's 2x faster to use an array than to call the fn)
   DEBUG_LOG(0,"Parity calculation cache array...");
   {uint16 i;
   for (i=0; i<256; i++) eparity[i]=evenparity((uint8) i);
   }

   segment1=0; segment2=0; start=1;

   // needs to be above read_config_files
   scc_a_port=NULL;
   scc_b_port=NULL;
   scc_a_IW=NULL;
   scc_b_IW=NULL;
   serial_a=0;
   serial_b=0;

   init_floppy(lisa_config->iorom);

   bitdepth=8;                             // have to get this from the X Server side...


   DEBUG_LOG(0,"serial number");
   serialnum[0]=0x00;                      // "real" ones will be loaded from the settings file
   serialnum[1]=0x00;
   serialnum[2]=0x00;
   serialnum[3]=0x00;
   serialnum[4]=0x00;
   serialnum[5]=0x00;
   serialnum[6]=0x00;
   serialnum[7]=0x00;
   serialnumshiftcount=0; serialnumshift=0;


   // Is there no explicit way to init the COPS?
   DEBUG_LOG(0,"copsqueue");
   copsqueuelen = 0;
   memset(copsqueue,0,MAXCOPSQUEUE);
   lisa_clock.year = 0;         // flag to get real time from host OS on first Lisa timer request.


   DEBUG_LOG(0,"irq handlers");
   init_IRQ();


   DEBUG_LOG(0,"vias");
   init_vias();


   DEBUG_LOG(0,"profile hd's");
   init_Profiles();

       // removed 2005.11.10
       // switch(maxlisaram)
       // {
       //  case 512  : TWOMEGMLIM=0x0007ffff; break;
       //  case 1024 : TWOMEGMLIM=0x000fffff; break; **** bug here ***
       //  case 1536 : TWOMEGMLIM=0x001fFFFF; break;  // this might not wrap around property!
       //  default:    TWOMEGMLIM=0x001fffff; maxlisaram=2048; break;
       // }

        TWOMEGMLIM=0x001fffff;

       // Simulate physical Lisa memory boards
       switch(lisa_config->mymaxlisaram)
       {
        case 512  : maxlisaram=0x100000;  minlisaram=0x080000;  break;     // single 512KB board in slot 1
        case 1024 : maxlisaram=0x180000;  minlisaram=0x080000;  break;     // two 512KB boards in slot1, slot 2
        case 1536 : maxlisaram=0x200000;  minlisaram=0x080000;  break;     // 512KB board in slot1, 1024KB board in slot 2
        default:    maxlisaram=0x200000;  minlisaram=0x000000;  break;     // two 1024KB boards in slot 1,2
       }

        // maxlisaram! New!  And! Improved! Now available in bytes at a register Store Near You!  OpCodes are Standing by!
        // Act now!  don't delay!  Limited Time Offer!  JSR Now!  Restrictions Apply!  Before engaging in any strenous programming
        // activity, you should always consult your MMU today!  Not 0xFD1C insured!


       TWOMEGMLIM=maxlisaram-1;
    videolatchaddress=maxlisaram-0x10000;

    videolatch=(maxlisaram>>15)-1; // last page of ram is by default what the video latch gets set to.
    lastvideolatch=videolatch;  lastvideolatchaddress=videolatchaddress;

    if (lisaram) free(lisaram);          // remove old junk if it exists

    lisaram=(uint8 *)malloc(maxlisaram+512);     // added 512 bytes at the end to avoid overflows.
    memset(lisaram,0xff,maxlisaram+128);

    if (lisa_config->kbid) set_keyboard_id(lisa_config->kbid);  else set_keyboard_id(-1);


     // Parse sn and correct if needed  ////////////////////////////////////////////////////////////////////////////////////////
 	{


         //char mybuffer[5];
         int i,j; char c;
         char *s=(char *)(lisa_config->myserial.c_str());
         for (i=0; (c=s[i]); i++)
             if ( !ishex(c) )
 			{
                 //ALERT_LOG(1,"Warning, invalid serial number: (%s) reset to default.\n",s );
                 lisa_config->myserial.Printf("%s",LISA_CONFIG_DEFAULTSERIAL); break;
 			}

         if (i<31) DEBUG_LOG(1,"Warning: serial # less than 32 bytes (%d)!\n",i);

         for (i=0,j=0; i<32; i+=2,j++)
                 serialnum240[j]=(gethex(s[i])<<4)|gethex(s[i|1]);

         vidfixromchk(serialnum240);



         lisa_config->myserial.Printf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                         serialnum240[0],                 serialnum240[1],
                         serialnum240[2],                 serialnum240[3],
                         serialnum240[4],                 serialnum240[5],
                         serialnum240[6],                 serialnum240[7],
                         serialnum240[8],                 serialnum240[9],
                         serialnum240[10],                serialnum240[11],
                         serialnum240[12],                serialnum240[13],
                         serialnum240[14],                serialnum240[15]);

         DEBUG_LOG(0,"Serial # used: %s", lisa_config->myserial.c_str());


     }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

     // If either serial port is set as loopback, both must be.
     if (lisa_config->serial2_setting.IsSameAs("LOOPBACK", false) ||
         lisa_config->serial1_setting.IsSameAs("LOOPBACK", false)) {
         lisa_config->serial1_setting = "LOOPBACK";
         lisa_config->serial2_setting = "LOOPBACK";
     }


     connect_device_to_serial(0, &scc_a_port_F, &serial_a,
         &lisa_config->serial1_setting, &lisa_config->serial1_param,
         &scc_a_telnet_port, &scc_a_IW);


     connect_device_to_serial(1, &scc_b_port_F, &serial_b,
         &lisa_config->serial2_setting, &lisa_config->serial2_param,
         &scc_b_telnet_port, &scc_b_IW);

     DEBUG_LOG(0,"Initializing SCC Z8530");
     initialize_scc();

     if (!lisaram) {
         wxMessageBox(_T("Could not allocate memory for the Lisa to use."),
             _T("Out of Memory!"), wxICON_INFORMATION | wxOK);
         return 23;
     }

   DEBUG_LOG(0,"setting motherboard latches");
   softmemerror=0; harderror=0; videoirq=0; bustimeout=0; videobit=0;

   DEBUG_LOG(0,"Checking host CPU bit order sanity.");
   reg68k_sanity_check_bitorder();

   DEBUG_LOG(0,"generator cpu_68k fn");
   cpu68k_init();

   DEBUG_LOG(0,"lisa memory management unit (A30N-V2.0)");
   init_lisa_mmu();

   DEBUG_LOG(0,"initializing ipc allocator");
   init_ipct_allocator();

   DEBUG_LOG(0,"Rom to use: %s",(char *)(lisa_config->rompath.c_str() ));

   if (!lisa_config->rompath.Len()) return -2;

   if (lisa_config->rompath.Len())
   {
       DEBUG_LOG(0,"Loading Lisa ROM");
       if      (read_dtc_rom((char *)(lisa_config->rompath.c_str()),   lisarom)==0) {DEBUG_LOG(0,"Load of DTC assembled ROM successful"); fixromchk();}
       else if (read_split_rom((char *)(lisa_config->rompath.c_str()), lisarom)==0) {DEBUG_LOG(0,"Load of split ROM"); fixromchk();}
       else if (read_rom((char *)(lisa_config->rompath.c_str()),       lisarom)==0) {DEBUG_LOG(0,"Load of normal ROM successful"); fixromchk();} // do not call fixromchk here!
       else
       {

           wxMessageBox(_T("Could not open/find a suitable boot ROM"), _T("ROM Needed!!!"), wxICON_INFORMATION | wxOK);


           // Homer: No TV and no beer make Homer something something");
           // Marge: Go crazy?");
           // Homer: Don't mind if I do!");
           return -2;
       }
 	  //lisa_config->save();
   }
   else
   {
           wxMessageBox(_T("Could not open/find a suitable boot ROM or path is empty."), _T("ROM Needed"), wxICON_INFORMATION | wxOK);
           return -3;
   }
   if (has_xl_screenmod())
     {

      // This warning falg isn't really a configuration setting as such, but leave it here for the moment
      if (!lisa_config->saw_3a_warning)
      wxMessageBox(_T("This is the XL Screen Modification ROM! You will only be able to run MacWorks."),
                       _T("3A ROM!"),   wxICON_INFORMATION | wxOK);


      lisa_config->saw_3a_warning=1;
      lisa_vid_size_x=608;
      lisa_vid_size_y=431;
      effective_lisa_vid_size_x=608;
      effective_lisa_vid_size_y=431;

      screen_origin_x=140+56;
      screen_origin_y=130+34;

      lisa_vid_size_xbytes=76;
      has_lisa_xl_screenmod=1;
     }
     else
     {
      lisa_vid_size_x=720;
      lisa_vid_size_y=364;
      effective_lisa_vid_size_x=720;
      effective_lisa_vid_size_y=500;

      screen_origin_x=140;
      screen_origin_y=130;

      lisa_vid_size_xbytes=90;
      has_lisa_xl_screenmod=0;
     }

     ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

     // Handle Parallel Ports

     // Connect profile/printer to builtin parallel port
     connect_device_to_via(2, lisa_config->parallel, &lisa_config->parallelp);

     // ----------------------------------------------------------------------------------

     // Dual Parallel Port Expansion Cards

     memset(dualparallelrom,0xff,2048);

     // First, we have to load the card ROM, without it, it won't work.
      if (lisa_config->dualrom.Len())
      {
       if (read_parallel_card_rom((char *)(lisa_config->dualrom.c_str()))==0)
          {
           // for now only Dual Parallel Port cards are supported.

             if (lisa_config->slot1.IsSameAs("dualparallel",false) )
               {
                       connect_2x_parallel_to_slot(0);
                       connect_device_to_via(3, lisa_config->s1h, &lisa_config->s1hp);
                       connect_device_to_via(4, lisa_config->s1l, &lisa_config->s1lp);
               }

             if (lisa_config->slot2.IsSameAs("dualparallel",false) )
               {
                       connect_2x_parallel_to_slot(1);
                       connect_device_to_via(5, lisa_config->s2h, &lisa_config->s2hp);
                       connect_device_to_via(6, lisa_config->s2l, &lisa_config->s2lp);
               }


             if (lisa_config->slot3.IsSameAs("dualparallel",false))
               {
                       connect_2x_parallel_to_slot(2);
                       connect_device_to_via(7, lisa_config->s3h, &lisa_config->s3hp);
                       connect_device_to_via(8, lisa_config->s3l, &lisa_config->s3lp);
               }


          }
       else
          {
           wxMessageBox(_T("Could not open/find a suitable Parallel Card ROM. Expansion slots disabled."),
                        _T("Parallel Card ROM Needed"),
                           wxICON_INFORMATION | wxOK);
          }
      }

       ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////



       ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

       DEBUG_LOG(0,"initializing video");
       disable_vidram(); videoramdirty|=9;


       DEBUG_LOG(0,"Resetting cpu68k");
       cpu68k_reset();

       DEBUG_LOG(0,"done initializing...");

       debug_off();


     return 0;
}

/*
 * This is here for the C code in the emu to talk to the canvas. For the
 * moment we need it to keep compatibility with the lisaem_wx.cpp frontend.
 * It really isn't very nice though; I'm sure there are better ways of doing
 * most, if not all, of it: callbacks, the canvas itself watching for
 * events once every iteration etc.
 * As DNA put it:
 * For a while they flew on, motionless against the starry sweep of the
 * Galaxy, itself motionless against the infinite sweep of the Universe.
 * And then they turned around. "It'll have to go," the men of Krikkit said.
 */
void invalidate_configframe(void)
{
    printf("invalidate_configframe()\n");
}

extern "C" void eject_floppy_animation(void)
{
    floppy_just_ejected = true;
}

extern "C" void floppy_motor_sounds(int track)
{
    printf("floppy_motor_sounds(%d)\n", track);
}

extern "C" void messagebox(char *s, char *t)
{
    printf("messagebox(\"%s\", \"%s\")\n", s, t);
}

extern "C" void lisa_powered_off(void)
{
    me->SetPowerOn(false);
}

extern "C" void lisa_rebooted(void)
{
    me->SetPowerOn(false);
    me->SetPowerOn(true);
}

extern "C" void save_pram(void)
{
    printf("save_pram()\n");
}

extern "C" void contrastchange(void)
{
    contrast_changed = true;
    // printf("contrastchange() (contrast: 0x%02x)\n", contrast);
}

extern "C" void sound_off(void)
{
    printf("sound_off()\n");
}

extern "C" void sound_play(uint16 t2)
{
    printf("sound_play(%d)\n", t2);
}

extern "C" void rename_rompath(char *rompath)
{
     printf("rename_rompath(%s)\n", rompath);
}

extern "C" void LisaScreenRefresh(void)
{
    printf("LisaScreenRefresh()\n");
}