/** VGB: portable GameBoy emulator ***************************/
/**                                                         **/
/**                           Unix.c                        **/
/**                                                         **/
/** This file contains Unix/X-dependent subroutines and     **/
/** drivers.                                                **/
/**                                                         **/
/** Copyright (C) Marat Fayzullin 1994,1995,1996            **/
/**               Elan Feingold   1995                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/

#define USE_XPAL  /* We are using XPal[] to determine colors */

/** Standard Unix/X #includes ********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "GB.h"


/** MIT Shared Memory Extension for X ************************/
#ifdef MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
XShmSegmentInfo SHMInfo;
int UseSHM=1;
#endif


/** Various X-related variables ******************************/
Display *Dsp;
Window Wnd;
Colormap DefaultCMap;
XImage *Img;
GC DefaultGC;
unsigned long White,Black;


/** Various variables and short functions ********************/
#define WIDTH  176  /* Width (>=176, must be divisible by 8) */
#define HEIGHT 144  /* Height (>=144)                        */

char *Title = "Virtual GameBoy Unix/X 0.7";

int  SaveCPU  = 1;

byte *XBuf,*ZBuf,XPal[12];

char *ColorNames[12] =
{
  "white","#989898","#585858","black",
  "white","#989898","#585858","black",
  "white","#989898","#585858","black"
};

void OnBreak(int Arg) { CPURunning=0; }


/** InitMachine **********************************************/
/** Allocate resources needed by Unix/X-dependent code.     **/
/*************************************************************/
int InitMachine()
{
  Screen *Scr;
  XEvent E;
  XGCValues values;
  XColor Color,C;
  int J;

  if(Verbose)
    printf("Initializing Unix/X drivers:\n  Opening display...");
  Dsp=XOpenDisplay(NULL);
  if(!Dsp) { if(Verbose) printf("FAILED\n");return(0); }

  Scr=DefaultScreenOfDisplay(Dsp);
  White=WhitePixelOfScreen(Scr);
  Black=BlackPixelOfScreen(Scr);
  DefaultGC=DefaultGCOfScreen(Scr);
  DefaultCMap=DefaultColormapOfScreen(Scr);

  if(Verbose) printf("OK\n  Opening window...");
  Wnd=
    XCreateSimpleWindow
    (Dsp,RootWindowOfScreen(Scr),0,0,160,144,0,White,Black);
  if(!Wnd) { if(Verbose) printf("FAILED\n");return(0); }

  {
    XSizeHints Hints;
    XWMHints WMHints;

    Hints.flags=PSize|PMinSize|PMaxSize;
    Hints.min_width=Hints.max_width=Hints.base_width=160;
    Hints.min_height=Hints.max_height=Hints.base_height=144;
    WMHints.input=True;WMHints.flags=InputHint;
    XSetWMHints(Dsp,Wnd,&WMHints);
    XSetWMNormalHints(Dsp,Wnd,&Hints);
    XStoreName(Dsp,Wnd,Title);
  }

  XSelectInput
  (Dsp,Wnd,FocusChangeMask|ExposureMask|KeyPressMask|KeyReleaseMask);
  XMapRaised(Dsp,Wnd);
  XClearWindow(Dsp,Wnd);
  XAutoRepeatOff(Dsp);
  XWindowEvent(Dsp,Wnd,ExposureMask,&E);

  if(Verbose) printf("OK\n  Allocating Z-buffer...");
  if(!(ZBuf=malloc(2*WIDTH*HEIGHT/8))) return(0);

#ifdef MITSHM
  if(UseSHM)
  {
    if(Verbose) printf("OK\n  Using shared memory:\n    Creating image...");
    Img=
      XShmCreateImage
      (Dsp,DefaultVisualOfScreen(Scr),8,ZPixmap,NULL,&SHMInfo,WIDTH,HEIGHT);
    if(!Img) { if(Verbose) printf("FAILED\n");return(0); }

    if(Verbose) printf("OK\n    Getting SHM info...");
    SHMInfo.shmid=
      shmget(IPC_PRIVATE,Img->bytes_per_line*Img->height,IPC_CREAT|0777);
    if(SHMInfo.shmid<0) { if(Verbose) printf("FAILED\n");return(0); }

    if(Verbose) printf("OK\n    Allocating SHM...");
    XBuf=(byte *)(Img->data=SHMInfo.shmaddr=shmat(SHMInfo.shmid,0,0));
    if(!XBuf) { if(Verbose) printf("FAILED\n");return(0); }

    SHMInfo.readOnly=False;
    if(Verbose) printf("OK\n    Attaching SHM...");
    if(!XShmAttach(Dsp,&SHMInfo))
    { if(Verbose) printf("FAILED\n");return(0); }
  }
  else
#endif
  {
    if(Verbose) printf("OK\n  Allocating screen buffer...");
    XBuf=(byte *)malloc(sizeof(byte)*HEIGHT*WIDTH);
    if(!XBuf) { if(Verbose) printf("FAILED\n");return(0); }

    if(Verbose) printf("OK\n  Creating image...");
    Img=
      XCreateImage
      (Dsp,DefaultVisualOfScreen(Scr),8,ZPixmap,0,XBuf,WIDTH,HEIGHT,8,0);
    if(!Img) { if(Verbose) printf("FAILED\n");return(0); }
  }

  if(Verbose) puts("OK");

  for(J=0;J<12;J++)
    XPal[J]=XAllocNamedColor(Dsp,DefaultCMap,ColorNames[J],&Color,&C)?
              Color.pixel : J&2? White:Black;

  signal(SIGHUP,OnBreak);signal(SIGINT,OnBreak);
  signal(SIGQUIT,OnBreak);signal(SIGTERM,OnBreak);

  return(1);
}


/** TrashMachine *********************************************/  
/** Deallocate all resources taken by InitMachine().        **/
/*************************************************************/
void TrashMachine()
{
  if(Verbose) printf("Shutting down...\n");

  if(Dsp&&Wnd)
  {
#ifdef MITSHM
    if(UseSHM)
    {
      XShmDetach(Dsp,&SHMInfo);
      if(SHMInfo.shmaddr) shmdt(SHMInfo.shmaddr);
      if(SHMInfo.shmid>=0) shmctl(SHMInfo.shmid,IPC_RMID,0);
    }
    else
#endif MITSHM
    if(Img) XDestroyImage(Img);
  }
  if(ZBuf) free(ZBuf);
  if(Dsp) { XAutoRepeatOn(Dsp);XCloseDisplay(Dsp); }
}


/** PutImage *************************************************/
/** Put an image on the screen.                             **/
/*************************************************************/
void PutImage()
{
#ifdef MITSHM
  if(UseSHM)
    XShmPutImage(Dsp,Wnd,DefaultGC,Img,(WIDTH-160)/2,0,0,0,160,144,False);
  else
#endif
  XPutImage(Dsp,Wnd,DefaultGC,Img,(WIDTH-160)/2,0,0,0,160,144);
  XFlush(Dsp);
}


/** Joystick *************************************************/
/** Return the current joystick state.                      **/
/*************************************************************/
byte Joystick(void)
{
  static byte JoyState = 0xFF;
  XEvent E;
  word J;

  if(XCheckWindowEvent(Dsp,Wnd,KeyPressMask|KeyReleaseMask,&E))
  {
    J=XLookupKeysym((XKeyEvent *)&E,0);
    if(E.type==KeyPress)
      switch(J)
      {
        case XK_F12:
        case XK_Escape: CPURunning=0;break;
#ifdef DEBUG
        case XK_F1:     Trace=!Trace;break;
        case XK_F2:     puts("\033[H\033[2J*** REGISTERS: ***");
                        for(J=0xFF40;J<0xFF50;J++)
                          printf("(%Xh) = %Xh\n",J,RAM[J]);
                        printf("ISWITCH = %Xh\n",ISWITCH);
                        break;
        case XK_F3:     puts("\033[H\033[2J*** SPRITES: ***");
                        for(J=0xFE9C;J<0xFE9C+4*40;J+=4)
                          printf
                          (
                            "SPRITE %d: %d,%d   Pat %d   Attr %d\n",
                            (J-0xFE9C)/4,RAM[J+1],RAM[J],RAM[J+2],RAM[J+3]
                          );
                        break;
#endif
        case XK_Return: JoyState&=0x7F;break;
        case XK_Tab:    JoyState&=0xBF;break;
        case XK_Down:   JoyState&=0xF7;break;
        case XK_Up:     JoyState&=0xFB;break;
        case XK_Left:   JoyState&=0xFD;break;
        case XK_Right:  JoyState&=0xFE;break;
        case XK_z: case XK_x: case XK_c: case XK_v:
        case XK_b: case XK_n: case XK_m: 
        case XK_Z: case XK_X: case XK_C: case XK_V:
        case XK_B: case XK_N: case XK_M: 
        case XK_Alt_L:  JoyState&=0xDF;break;
        case XK_a: case XK_s: case XK_d: case XK_f:
        case XK_g: case XK_h: case XK_j:
        case XK_A: case XK_S: case XK_D: case XK_F:
        case XK_G: case XK_H: case XK_J:
        case XK_space:  JoyState&=0xEF;break;
      }
    else
     switch(J)
      {
        case XK_Return: JoyState|=0x80;break;
        case XK_Tab:    JoyState|=0x40;break;
        case XK_Down:   JoyState|=0x08;break;
        case XK_Up:     JoyState|=0x04;break;
        case XK_Left:   JoyState|=0x02;break;
        case XK_Right:  JoyState|=0x01;break;
        case XK_z: case XK_x: case XK_c: case XK_v:
        case XK_b: case XK_n: case XK_m: 
        case XK_Z: case XK_X: case XK_C: case XK_V:
        case XK_B: case XK_N: case XK_M: 
        case XK_Alt_L:  JoyState|=0x20;break;
        case XK_a: case XK_s: case XK_d: case XK_f:
        case XK_g: case XK_h: case XK_j:
        case XK_A: case XK_S: case XK_D: case XK_F:
        case XK_G: case XK_H: case XK_J:
        case XK_space:  JoyState|=0x10;break;
      }
  }

  for(J=0;XCheckWindowEvent(Dsp,Wnd,FocusChangeMask,&E);)
    J=(E.type==FocusOut);
  if(SaveCPU&&J)
  {
    XAutoRepeatOn(Dsp);
    while(!XCheckWindowEvent(Dsp,Wnd,FocusChangeMask,&E)&&CPURunning)
    {
      if(XCheckWindowEvent(Dsp,Wnd,ExposureMask,&E)) PutImage();
      XPeekEvent(Dsp,&E);
    }
    XAutoRepeatOff(Dsp);
  }

  return(JoyState);
}


/*** SIOSend ****************************************************/
/*** Send a byte onto the serial line.                        ***/
/****************************************************************/
byte SIOSend(register byte V) { return(0); }


/*** SIOReceive *************************************************/
/*** Receive a byte from the serial line. Returns 1 on        ***/
/*** success, 0 otherwise.                                    ***/
/****************************************************************/
byte SIOReceive(register byte *V) { return(0); }


/****************************************************************/
/*** Write value into sound chip register (Reg #0 at FF10h).  ***/
/****************************************************************/
void Sound(byte R,byte V) { return; }


/** Common.h ****************************************************/
/** Parts of the drivers common for Unix/X and MSDOS.          **/
/****************************************************************/
#include "Common.h"
