/*****************************************************************************/
/*                                                                           */
/*                                 console.c                                 */
/*                                                                           */
/*                  Console plugin for the sim65 simulator                   */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 2003-2012, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>

/* common */
#include "attrib.h"

/* sim65 */
#include "chipif.h"



/*****************************************************************************/
/*                                   Forwards                                */
/*****************************************************************************/



static int ScreenInitChip (const struct SimData* Data);
/* Initialize the chip, return an error code */

static void* ScreenCreateInstance (unsigned Addr, unsigned Range, void* CfgInfo);
/* Create a new chip instance */

static void ScreenDestroyInstance (void* Data);
/* Destroy a chip instance */

static void ScreenWrite (void* Data, unsigned Offs, unsigned char Val);
/* Write user data */

static unsigned char ScreenRead (void* Data, unsigned Offs);
/* Read user data */

static void ScreenDrawBorder (void);
/* Draw the complete border */

static void ScreenDrawChar (unsigned Offs);
/* Draw one character at the given position */

static void ScreenDrawAllChars (void);
/* Redraw the complete interior screen */

static void ScreenEventLoop (void);
/* Get all waiting events and handle them */



/*****************************************************************************/
/*                                Global data                                */
/*****************************************************************************/



/* The SimData pointer we get when InitChip is called */
static const SimData* Sim;

/* Control data passed to the main program */
static const struct ChipData CData[] = {
    {
        "VIDEOSCREEN",          /* Name of the chip */
        CHIPDATA_TYPE_CHIP,     /* Type of the chip */
        CHIPDATA_VER_MAJOR,     /* Version information */
        CHIPDATA_VER_MINOR,

        /* -- Exported functions -- */
        ScreenInitChip,
        ScreenCreateInstance,
	ScreenDestroyInstance,
        ScreenWrite,
        ScreenWrite,
        ScreenRead,
        ScreenRead
    },
};

/* Defines for console screen */
static const XColor GreenColor = {
    0,  32*256, 141*256, 32*256, 0, 0           /* green */
};
static const XColor AmberColor = {
    0,  255*256, 204*256, 51*256, 0, 0          /* amber */
};
static const XColor WhiteColor = {
    0,  224*256, 224*256, 224*256, 0, 0         /* white */
};
static const XColor BgColor = {
    0,  0*256, 0*256, 0*256, 0, 0               /* black */
};


/*****************************************************************************/
/*                                     Data                                  */
/*****************************************************************************/



/* Screen instance data */
typedef struct ScreenInstance ScreenInstance;
struct ScreenInstance {

    /* Settings passed from the simulator */
    unsigned            Addr;           /* Address of the chip */
    unsigned            Range;          /* Memory range */

    /* X variables */
    Display*            ScreenDisplay;
    Window              ScreenWindow;
    int                 Screen;
    GC                  ScreenGC;

    /* Windows rows and columns */
    unsigned            Rows;
    unsigned            Cols;

    /* Window dimensions, determined by char resolution and char set */
    unsigned            XTotal;
    unsigned            YTotal;

    /* Usable area within the window */
    unsigned            XSize;
    unsigned            YSize;

    /* Offset of the usable area */
    unsigned            XOffs;
    unsigned            YOffs;

    /* Character height */
    unsigned            CharHeight;

    /* Fore- and background color */
    XColor              FgColor;
    XColor              BgColor;

    /* A list of 4 rectangles used to draw the border */
    XRectangle          Border[4];

    /* The virtual screen we are writing to. */
    unsigned            MemSize;
    unsigned char*      Mem;

    /* The font data */
    unsigned            FontDataSize;
    unsigned char*      FontData;

};

/* If we have a video ram window, place it's instance data here */
static ScreenInstance* VScreen = 0;



/*****************************************************************************/
/*                               Exported function                           */
/*****************************************************************************/



int GetChipData (const ChipData** Data, unsigned* Count)
{
    /* Pass the control structure to the caller */
    *Data  = CData;
    *Count = sizeof (CData) / sizeof (CData[0]);

    /* Call was successful */
    return 0;
}



/*****************************************************************************/
/*                             Helper functions                              */
/*****************************************************************************/



static long CfgGetNum (void* CfgInfo, const char* AttrName, long Min, long Max, long Def)
/* Read a number from the attributes. Check against Min/Max. Return the
 * number or Def if it doesn't exist.
 */
{
    long Val;

    /* Read the attribute if it does exist */
    if (Sim->GetCfgNum (CfgInfo, AttrName, &Val)) {
        /* Check it */
        if (Val < Min || Val > Max) {
            Sim->Error ("Range error for attribute `%s'", AttrName);
        }

        /* Return it */
        return Val;

    } else {

        /* Return the default */
        return Def;

    }
}



/*****************************************************************************/
/*                              Console screen                               */
/*****************************************************************************/



static int ScreenInitChip (const struct SimData* Data)
/* Initialize the chip, return an error code */
{
    /* Remember the pointer */
    Sim = Data;

    /* Always successful */
    return 0;
}



static void* ScreenCreateInstance (unsigned Addr, unsigned Range, void* CfgInfo)
/* Create a new chip instance */
{
    char*       Name;
    FILE*       F;
    unsigned    ColorDepth;
    Colormap    CM;
    XSizeHints  SizeHints;
    XWMHints    WMHints;
    Cursor      C;
    unsigned    CharColor;


    /* Allocate the instance data */
    ScreenInstance* V = VScreen = Sim->Malloc (sizeof (ScreenInstance));

    /* Remember a few settings */
    V->Addr  = Addr;
    V->Range = Range;

    /* Character height is 8 or given as attribute */
    V->CharHeight = (unsigned) CfgGetNum (CfgInfo, "charheight", 8, 16, 8);

    /* Allocate memory for the font */
    V->FontDataSize = V->CharHeight * 256;
    V->FontData = Sim->Malloc (V->FontDataSize);

    /* We must have a "fontdata" attribute. Get it. */
    if (Sim->GetCfgStr (CfgInfo, "fontdata", &Name) == 0) {
        /* Attribute not found */
        Sim->Error ("Attribute `fontdata' missing");        /* ### */
    }

    /* Open the file with the given name */
    F = fopen (Name, "rb");
    if (F == 0) {
        Sim->Error ("Cannot open `%s': %s", Name, strerror (errno));
    }

    /* Read the file into the memory */
    if (fread (V->FontData, 1, V->FontDataSize, F) != V->FontDataSize) {
        Sim->Warning ("Font data file `%s' seems to be corrupt", Name);
    }

    /* Close the file */
    fclose (F);

    /* Free the file name */
    Sim->Free (Name);

    /* Read screen rows and columns */
    V->Rows = (unsigned) CfgGetNum (CfgInfo, "rows", 15, 75, 25);
    V->Cols = (unsigned) CfgGetNum (CfgInfo, "cols", 32, 132, 80);

    /* Allocate screen memory and clear it */
    V->MemSize = V->Rows * V->Cols;
    V->Mem = Sim->Malloc (V->MemSize);
    memset (V->Mem, ' ', V->MemSize);

    /* Setup the window geometry */
    V->XSize  = V->Cols * 8;
    V->YSize  = V->Rows * V->CharHeight;
    V->XTotal = V->XSize + 20;
    V->YTotal = V->YSize + 20;
    V->XOffs  = (V->XTotal - V->XSize) / 2;
    V->YOffs  = (V->YTotal - V->YSize) / 2;

    /* Setup the rectanges used to draw the exterior */
    V->Border[0].x      = 0;
    V->Border[0].y      = 0;
    V->Border[0].width  = V->XTotal;
    V->Border[0].height = V->YOffs;
    V->Border[1].x      = 0;
    V->Border[1].y      = V->YOffs + V->YSize;
    V->Border[1].width  = V->XTotal;
    V->Border[1].height = V->YOffs;
    V->Border[2].x      = 0;
    V->Border[2].y      = V->YOffs;
    V->Border[2].width  = V->XOffs;
    V->Border[2].height = V->YSize;
    V->Border[3].x      = V->XOffs + V->XSize;
    V->Border[3].y      = V->YOffs;
    V->Border[3].width  = V->XOffs;
    V->Border[3].height = V->YSize;

    /* Open the X display. */
    V->ScreenDisplay = XOpenDisplay ("");
    if (V->ScreenDisplay == NULL) {
        Sim->Error ("Screen: Cannot open X display");
    }

    /* Get a screen */
    V->Screen = DefaultScreen (V->ScreenDisplay);

    /* Check the available colors. For now, we expect direct colors, so we
     * will check for a color depth of at least 16.
     */
    ColorDepth = XDefaultDepth (V->ScreenDisplay, V->Screen);
    if (ColorDepth < 16) {
        /* OOPS */
        Sim->Error ("Screen: Need color display");
    }

    /* Determine the character color */
    CharColor = (unsigned) CfgGetNum (CfgInfo, "charcolor", 0, 2, 0);

    /* Get all needed colors */
    switch (CharColor) {
        case 1:         V->FgColor = AmberColor;        break;
        case 2:         V->FgColor = WhiteColor;        break;
        default:        V->FgColor = GreenColor;        break;
    }
    V->BgColor = BgColor;
    CM = DefaultColormap (V->ScreenDisplay, V->Screen);
    if (XAllocColor (V->ScreenDisplay, CM, &V->FgColor) == 0) {
        Sim->Error ("Screen: Cannot allocate foreground color");
    }
    if (XAllocColor (V->ScreenDisplay, CM, &V->BgColor) == 0) {
        Sim->Error ("Screen: Cannot allocate background color");
    }

    /* Set up the size hints structure */
    SizeHints.x          = 0;
    SizeHints.y          = 0;
    SizeHints.flags      = PPosition | PSize | PMinSize | PMaxSize | PResizeInc;
    SizeHints.width      = V->XTotal;
    SizeHints.height     = V->YTotal;
    SizeHints.min_width  = V->XTotal;
    SizeHints.min_height = V->YTotal;
    SizeHints.max_width  = V->XTotal;
    SizeHints.max_height = V->YTotal;
    SizeHints.width_inc  = 0;
    SizeHints.height_inc = 0;
    WMHints.flags        = InputHint;
    WMHints.input        = True;

    /* Create the window */
    V->ScreenWindow = XCreateSimpleWindow (V->ScreenDisplay,
                                           DefaultRootWindow (V->ScreenDisplay),
                                           SizeHints.x,
                                           SizeHints.y,
                                           SizeHints.width,
                                           SizeHints.height,
                                           5,
                                           V->FgColor.pixel,
                                           V->BgColor.pixel);

    /* Set the standard window properties */
    XSetStandardProperties (V->ScreenDisplay,           /* Display */
                            V->ScreenWindow,            /* Window */
                            "sim65 console screen",     /* Window name */
                            "sim65 console screen",     /* Icon name */
                            None,                       /* Icon Pixmap */
                            0,                          /* argv */
                            0,                          /* argc */
                            &SizeHints);                /* Hints */
    XSetWMHints (V->ScreenDisplay, V->ScreenWindow, &WMHints);

    /* GC creation and initialization */
    V->ScreenGC = XCreateGC (V->ScreenDisplay, V->ScreenWindow, 0, 0);

    /* Set the cursor to show over the console window */
    C = XCreateFontCursor (V->ScreenDisplay, XC_pirate);
    XDefineCursor (V->ScreenDisplay, V->ScreenWindow, C);

    /* Select input events */
    XSelectInput (V->ScreenDisplay, V->ScreenWindow, ExposureMask | StructureNotifyMask | KeyPressMask);

    /* Show the window */
    XMapRaised (V->ScreenDisplay, V->ScreenWindow);

    /* Handle events */
    ScreenEventLoop ();

    /* Return the instance data */
    return V;
}



static void ScreenDestroyInstance (void* Data)
/* Destroy a chip instance */
{
    /* Cast the data pointer */
    ScreenInstance* V = Data;

    /* Free X resources */
    XUndefineCursor (V->ScreenDisplay, V->ScreenWindow);
    XFreeGC (V->ScreenDisplay, V->ScreenGC);
    XDestroyWindow (V->ScreenDisplay, V->ScreenWindow);
    XCloseDisplay (V->ScreenDisplay);

    /* Clear the global pointer */
    VScreen = 0;

    /* Free the instance data */
    Sim->Free (V->FontData);
    Sim->Free (V->Mem);
    Sim->Free (V);
}



static void ScreenWrite (void* Data, unsigned Offs, unsigned char Val)
/* Write user data */
{
    /* Cast the data pointer */
    ScreenInstance* V = Data;

    /* Check the offset */
    if (Offs >= V->MemSize) {
        Sim->Break ("Screen: Accessing invalid memory at $%06X", V->Addr + Offs);
        return;
    }

    /* Write the value */
    V->Mem[Offs] = Val;

    /* Schedule a redraw */
    ScreenDrawChar (Offs);

    /* Call the event loop */
    ScreenEventLoop ();
}



static unsigned char ScreenRead (void* Data, unsigned Offs)
/* Read user data */
{
    /* Cast the data pointer */
    ScreenInstance* V = Data;

    /* Check the offset */
    if (Offs >= sizeof (V->Mem)) {
        Sim->Break ("Screen: Accessing invalid memory at $%06X", V->Addr + Offs);
        return 0xFF;
    } else {
        return V->Mem[Offs];
    }
}



static void ScreenDrawBorder (void)
/* Draw the complete border */
{
    if (VScreen) {
        /* Set the border color */
        XSetForeground (VScreen->ScreenDisplay, VScreen->ScreenGC, VScreen->BgColor.pixel);

        /* Fill all rectangles that make the border */
        XFillRectangles (VScreen->ScreenDisplay, VScreen->ScreenWindow, VScreen->ScreenGC,
                         VScreen->Border, sizeof (VScreen->Border) / sizeof (VScreen->Border[0]));
    }
}



static void ScreenDrawChar (unsigned Offs)
/* Draw one character at the given position */
{
    unsigned    Row, Col;
    XPoint      Points[128];
    unsigned    PCount;

    /* Get the character from the video RAM */
    unsigned char C = VScreen->Mem[Offs];

    /* Calculate the offset for the character data in the character ROM */
    unsigned char* D = VScreen->FontData + (C * VScreen->CharHeight);

    /* Calculate the coords for the output */
    unsigned X = VScreen->XOffs + (Offs % VScreen->Cols) * 8;
    unsigned Y = VScreen->YOffs + (Offs / VScreen->Cols) * VScreen->CharHeight;

    /* Clear the character area with the background color */
    XSetForeground (VScreen->ScreenDisplay, VScreen->ScreenGC, VScreen->BgColor.pixel);
    XFillRectangle (VScreen->ScreenDisplay, VScreen->ScreenWindow, VScreen->ScreenGC, X, Y, 8, VScreen->CharHeight);

    /* Prepare the foreground pixels */
    PCount = 0;
    for (Row = 0; Row < VScreen->CharHeight; ++Row) {

        /* Get next byte from char rom */
        unsigned Data = *D++;

        /* Make pixels from this byte */
        for (Col = 0; Col < 8; ++Col) {
            if (Data & 0x80) {
                /* Foreground pixel */
                Points[PCount].x = X + Col;
                Points[PCount].y = Y + Row;
                ++PCount;
            }
            Data <<= 1;
        }
    }
    if (PCount) {
        /* Set the character color */
        XSetForeground (VScreen->ScreenDisplay, VScreen->ScreenGC, VScreen->FgColor.pixel);

        /* Draw the pixels */
        XDrawPoints (VScreen->ScreenDisplay, VScreen->ScreenWindow, VScreen->ScreenGC,
                     Points, PCount, CoordModeOrigin);
    }
}



static void ScreenDrawArea (unsigned X1, unsigned Y1, unsigned X2, unsigned Y2)
/* Update an area of the interior screen */
{
    unsigned X, Y;

    /* Check if we have to draw anything */
    if (X2 < VScreen->XOffs || Y2 < VScreen->YOffs ||
        X1 >= VScreen->XOffs + VScreen->XSize ||
        Y1 >= VScreen->YOffs + VScreen->YSize) {
        /* Completely outside */
        return;
    }

    /* Make the coordinates relative to the interior */
    X1 -= VScreen->XOffs;
    Y1 -= VScreen->YOffs;
    X2 -= VScreen->XOffs;
    Y2 -= VScreen->YOffs;

    /* Loop updating characters */
    for (Y = Y1; Y <= Y2; Y += 8) {
        for (X = X1; X <= X2; X += 8) {
            ScreenDrawChar ((Y / 8) * 40 + (X / 8));
        }
    }
}



static void ScreenDrawAllChars (void)
/* Redraw the complete interior screen */
{
    unsigned I;
    for (I = 0; I < 25*40; ++I) {
        ScreenDrawChar (I);
    }
}



static void ScreenEventLoop (void)
/* Get all waiting events and handle them */
{
    unsigned X1, Y1, X2, Y2;

    /* Read input events */
    while (XEventsQueued (VScreen->ScreenDisplay, QueuedAfterFlush) != 0) {

        /* Read an event */
        XEvent Event;
        XNextEvent (VScreen->ScreenDisplay, &Event);

        switch (Event.type) {

            case Expose:
		/* Calculate the area to redraw, then update the screen */
                X1 = Event.xexpose.x;
                Y1 = Event.xexpose.y;
                X2 = Event.xexpose.x + Event.xexpose.width - 1;
                Y2 = Event.xexpose.y + Event.xexpose.height - 1;
                if (X1 < VScreen->XOffs || X2 > VScreen->XOffs + VScreen->XSize ||
                    Y1 < VScreen->YOffs || Y2 > VScreen->YOffs + VScreen->YSize) {
                    /* Update the border */
                    ScreenDrawBorder ();
                }
                ScreenDrawArea (X1, Y1, X2, Y2);
                break;

            case MappingNotify:
                XRefreshKeyboardMapping (&Event.xmapping);
                break;

            case KeyPress:
                break;

            default:
                /* Ignore anything else */
                break;

        }
    }

    /* Flush the outgoing event queue */
    XFlush (VScreen->ScreenDisplay);
}



