/**
 * TrueReality - Display/local_process/X/X.c
 * Copyright (C) 1998 Niki W. Waibel
 *
 * This programm is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this programm; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/





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

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "../../../config.h"
#include "../../../type_sizes.h"
#include "../../../memory.h"
#include "../../../main.h"
#include "X.h"

#ifdef UNIX
#  include <signal.h>
#  include <sys/types.h>
#  include <unistd.h>
#endif

#ifdef DEBUG
#  include "../../../debug.h"
#endif

#ifdef SHM
#  include <sys/ipc.h>
#  include <sys/shm.h>
#  include <X11/extensions/XShm.h>
#endif





Display         *dsp;
Screen          *scr;
Window          win;
XImage          *img;
GC              gc;
unsigned long   white, black;
KeySym          keysym;

char            title[] = "TrueReality from \"Niki W. Waibel\"";
int             WIDTH   = 640;
int             HEIGHT  = 480;

PIXEL           *XBuf   = NULL;
long            flags   = 0;









/*******************************************************************************
*                                                                              *
* int OpenVisual()                                                             *
* int CloseVisual()                                                            *
*                                                                              *
********************************************************************************
*                                                                              *
* These routines are used by the emulator to open and to close a visual.       *
*                                                                              *
* Both return a negative int on error and zero on success.                     *
*                                                                              *
*******************************************************************************/





int OpenVisual()
{
   int pid;



   display = LOCAL_DISPLAY_PROCESS;



#ifdef UNIX

   /*** forking into display loop ***/

   pid = fork();

   switch(pid)
   {
      case -1:   /* error during fork */
         perror("OpenVisual");
         fprintf(stderr, "\n\nCould not fork into display loop!\n\n");
         return(-1);

      case  0:   /* child (processes window events) */
         if(InitDisplay() < 0)
            display = NO_DISPLAY;

         catch_signals();

         display_loop();

         exit(0);
   }

   /* parent is here */

#endif

   display_pid = pid;

   return(0);

} /* int OpenVisual() */





int CloseVisual()
{
#ifdef UNIX
   kill(display_pid, SIGTERM);
#endif

   return(0);

} /* int CloseVisual() */










/*******************************************************************************
*                                                                              *
* int InitDisplay()                                                            *
*                                                                              *
********************************************************************************
*                                                                              *
* This routine is used to initialize the visual.                               *
* It should print serious information of the current status.                   *
* It must NOT be called by the emulator. It's called from 'int OpenVisual()'.  *
* The name is not standard but it should be used!                              *
*                                                                              *
* The routine returns a negative int on error and zero on success.             *
*                                                                              *
*******************************************************************************/





int InitDisplay()
{
   printf("--------------------------------------------------------------------------------\n");
   printf("Initializing Unix/X display:\n");
   

   
   printf("Opening display ..."); fflush(stdout);

   dsp = XOpenDisplay(NULL);
   
   if(!dsp)
   {
      printf(" FAILED\n\n"); fflush(stdout);
      return(-1);
   }

   scr     = DefaultScreenOfDisplay(dsp);
   
   white   = WhitePixelOfScreen(scr);
   black   = BlackPixelOfScreen(scr);
   
  

   printf(" OK\n"
          "Creating window ..."); fflush(stdout);

   win = XCreateSimpleWindow(dsp, RootWindowOfScreen(scr), 0, 0, WIDTH, HEIGHT, 0, white, black);
   
   if(!win)
   {
      printf(" FAILED\n"); fflush(stdout);
      return(-1);
   }



   {
      XSizeHints Hints;
      XWMHints WMHints;



      Hints.flags       = PSize | PMinSize | PMaxSize;
      Hints.min_width   = 320;
      Hints.max_width   = 640;
      Hints.base_width  = WIDTH;
      Hints.min_height  = 240;
      Hints.max_height  = 480;
      Hints.base_height = HEIGHT;
      WMHints.input     = True;
      WMHints.flags     = InputHint;
  
      XSetWMHints(dsp, win, &WMHints);
      XSetWMNormalHints(dsp, win, &Hints);
      XStoreName(dsp, win, title);
   }



   /* input events */
   /*
   XSelectInput(dsp, win, ButtonPressMask | FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask);
   */
   XSelectInput(dsp, win, ExposureMask | KeyPressMask | KeyReleaseMask);
   

   
   /* display window */
   
   printf(" OK\n"
          "Mapping window ..."); fflush(stdout);

   XMapRaised(dsp, win);

   printf(" OK\n"
          "Clearing window ..."); fflush(stdout);

   XClearWindow(dsp, win);

   /* XAutoRepeatOff(dsp); */



   printf(" OK\n"
          "Creating graphics context ..."); fflush(stdout);

   gc = XCreateGC(dsp, win, 0, 0);

   printf(" OK\n");



#ifdef SHM
   if(UseSHM)
   {
      printf("Creating image in shared memory ..." ); fflush(stdout);
      
      img = XShmCreateImage(dsp, DefaultVisualOfScreen(scr), sizeof(PIXEL)*8, ZPixmap, NULL, &SHMInfo, WIDTH, HEIGHT);
      
      printf(" OK\n"                     
             "Attaching shared memory ..."); fflush(stdout);
      
      if(!XShmAttach(dsp, &SHMInfo))
      {
         printf(" FAILED\n");
         return(-1); 
      }         
   }
   else
#endif
   {
      printf("Creating image ..." ); fflush(stdout);
      
      img = XCreateImage(dsp, DefaultVisualOfScreen(scr), 8*sizeof(PIXEL), ZPixmap, 0, (char *)XBuf, WIDTH, HEIGHT, 8*sizeof(PIXEL), 0);
   
      if(!img)
      { 
         printf(" FAILED\n");
         return(-1);
      }
   }
   
   XBuf = (PIXEL *) img->data = rdram_4M;


   printf(" OK\n");



   printf("--------------------------------------------------------------------------------\n");

   return(0);
   
} /* int InitDisplay() */










/*******************************************************************************
*                                                                              *
* int DestroyDisplay()                                                         *
*                                                                              *
********************************************************************************
*                                                                              *
* This routine is used to destroy the visual.                                  *
* It should print serious information of the current status.                   *
* It must NOT be called by the emulator.                                       *
* The name is not standard but it should be used!                              *
*                                                                              *
* The routine returns a negative int on error and zero on success.             *
*                                                                              *
*******************************************************************************/





int DestroyDisplay()
{
   printf("--------------------------------------------------------------------------------\n");

   printf("Destroying Unix/X display:\n");

#ifdef SHM
   if(UseSHM)
   {
      printf("Detaching shared memory ..."); fflush(stdout);

      XShmDetach(dsp, &SHMInfo);

      printf(" OK\n"); fflush(stdout);
   }
#endif SHM

   printf("Destroying image ..."); fflush(stdout);

   free(img);

   printf(" OK\n"
          "Destroying graphics context ..."); fflush(stdout);

   XFreeGC(dsp, gc);

   printf(" OK\n"
          "Unmapping Window ..."); fflush(stdout);

   XUnmapWindow(dsp, win);

   printf(" OK\n"
          "Destroying Window ..."); fflush(stdout);

   XDestroyWindow(dsp, win);
   
   printf(" OK\n"
          "Closing display ..."); fflush(stdout);

   XDestroyWindow(dsp, win);
   
   printf(" OK\n");
   


   printf("--------------------------------------------------------------------------------\n");

   return(0);

} /* int DestroyDisplay() */









void PrintText(char *text, int x, int y)
{
   XSetForeground(dsp, gc, white);
   XSetBackground(dsp, gc, black);

   XDrawImageString(dsp, win, gc, x, y, text, strlen(text));
   
   XFlush(dsp);
   
} /* void PrintText(char *text, int x, int y) */





#ifdef DEBUG_DISPLAY

void PrintDisplayMode(flags)
{
   printf("Displaymode:\n");
   

   
   switch(flags & 0x0003)
   {
      case 0:
         printf("   blank (no data, no sync)\n");
         break;

      case 1:
         printf("   reserved\n");
         break;

      case 2:
         printf("   5/5/5/3 (\"16\" bit)\n");
         break;
         
      case 3:
         printf("   8/8/8/8 (32 bit)\n");
         break;
         
   } /* switch(flags & 0x0003) */
   

   
   if(flags & 0x0004)
      printf("   gamma_dither_enable\n");
   if(flags & 0x0008)
      printf("   gamma_enable\n");
   if(flags & 0x0010)
      printf("   divot_enable\n");
   if(flags & 0x0040)
      printf("   serrate\n");
   

   
   printf("   anti-alias (aa) mode\n");
   
   switch((flags & 0x0300) >> 8)
   {
      case 0:
         printf("      aa & resamp (always fetch extra lines)\n");
         break;

      case 1:
         printf("      aa & resamp (fetch extra lines if needed)\n");
         break;

      case 2:
         printf("      resamp only (treat as all fully covered)\n");
         break;
         
      case 3:
         printf("      neither (replicate pixels, no interpolate)\n");
         break;
         
   } /* switch((flags & 0x0300) >> 8) */
   
} /* void doPrintDisplayMode(flags) */

#endif





void RefreshVisual()
{
   long new_width;


   
   /* video flags */
   
   flags = doReadMemWord(0xffffffff04400000ULL);
#ifdef DEBUG_DISPLAY
   if(debug)
      PrintDisplayMode(flags);
#endif
   
   
   
   /* video with */
   
   new_width = doReadMemWord(0xffffffff04400008ULL);
   if(new_width != WIDTH)
   {
      if(new_width < 320 || new_width > 640)
         WIDTH = 640;
      else
         WIDTH = (int)new_width;

      HEIGHT = WIDTH * 3 / 4;

      XResizeWindow(dsp, win, WIDTH, HEIGHT);
   }

#ifdef DEBUG_DISPLAY
   if(debug)
      printf("Display width : %d\n"
             "Display height: %d\n", WIDTH, HEIGHT);
#endif

   
   /* display it! */

   if(new_width >= 320 && new_width <= 640)
   {
      if(flags & 0x0003)
      {
         /* video origin */
      
         img->data = (char *)XBuf + doReadMemWord(0xffffffff04400004ULL);

         PutImage(0, 0, WIDTH, HEIGHT);
      }
      else
      {
         PrintText("DISPLAY: blank (no data, no sync)", 20, 20);
      }
   }
   else
   {
      if(!(flags & 0x0003))
         PrintText("DISPLAY: blank (no data, no sync)", 20, 20);
         
      PrintText("DISPLAY: wrong width (640 will be used!)", 20, 40);
   }
   
} /* void RefreshVisual() */





void PutImage(int X, int Y, int W, int H)
{
#ifdef SHM
   if(UseSHM)
      XShmPutImage(dsp, win, gc, img, X, Y, (WIDTH-W)/2, (HEIGHT-H)/2, W, H, False);
   else
#endif
      XPutImage(dsp, win, gc, img, X, Y, (WIDTH-W)/2, (HEIGHT-H)/2, W, H);
   
   XFlush(dsp);
   
}





void GetVisualName(char *name, int *n)
{
   strncpy(name, DISPLAYNAME, (size_t) *n);
   
   *n = strlen(DISPLAYNAME);
   
} /* void GetVisualName() */





void CheckVisual()
{
   XEvent event;
   KeySym key;

   
   
   if(XCheckWindowEvent(dsp, win, ButtonPressMask | ButtonReleaseMask, &event))
   {
      puts("ButtonPressMask | ButtonReleaseMask");
   }

   if(XCheckWindowEvent(dsp, win, KeyPressMask | KeyReleaseMask, &event))
   {
      puts("KeyPressMask | KeyReleaseMask");
      
      key = XLookupKeysym((XKeyEvent *)&event, 0);
      
      if(event.type == KeyPress)
         switch(key)
         {
            case XK_Escape:
               SendIO("shutdown");
               
            case XK_F8:
               RefreshVisual();
               break;
         }
      else
         switch(key)
         {
         }
   }

   if(XCheckWindowEvent(dsp, win, ExposureMask, &event))
   {
      puts("ExposureMask");
      RefreshVisual();
   }
   
}





void display_loop()
{
   XEvent event;
/*   KeySym key; */
   int    i;
   char   text[80];



   for(;;)
   {
      /* read next event */
      XNextEvent(dsp, &event);

      switch(event.type)
      {
         case(Expose):
            if(event.xexpose.count == 0)
               RefreshVisual();
            break;

         case(MappingNotify):
            XRefreshKeyboardMapping((XMappingEvent *)&event);
            break;

/*
         case(ButtonPress):
            XDrawImageString(event.xbutton.display, event.xbutton.window, DefGC,
 event.xbutton.x, event.xbutton.y, hi, strlen(hi));
            break;
*/

         case(KeyPress):
            i = XLookupString((XKeyEvent *)&event, text, 10, &keysym, 0);

            if(i == 1)
               switch(text[0])
               {
                  case '!':
                     CloseVisual();

                  case 'r':
                     RefreshVisual();
                     break;
               }
            break;

      } /* end switch(event.type) */
   
   } /* for(;;) */

} /* void display_loop() */





void catch_signals()
{
   signal(SIGHUP,  signal_CloseVisual);
   signal(SIGINT,  signal_CloseVisual);
   signal(SIGQUIT, signal_CloseVisual);
   signal(SIGTERM, signal_CloseVisual);

   signal(SIGUSR1, signal_CheckVisual);
   signal(SIGUSR2, signal_RefreshVisual);

}





void signal_CloseVisual(int arg)
{
   signal(arg, SIG_IGN);

   DestroyDisplay();

   exit(0);

}





void signal_CheckVisual(int arg)
{
   signal(SIGUSR1, SIG_IGN);

   CheckVisual();

   signal(SIGUSR1, signal_CheckVisual);

}





void signal_RefreshVisual(int arg)
{
   signal(SIGUSR2, SIG_IGN);

   RefreshVisual();

   signal(SIGUSR2, signal_RefreshVisual);

}
