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

#ifndef NO_XSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#include <signal.h>
#endif

#include <string.h>
#ifdef _AIX
#include <strings.h>
#endif

#include "filter.hpp"
#include "globals.hpp"
#include "system.h"
#include "video.hpp"
#include "dos.h"
#include "xinclude.h"
#include "macs.hpp"
#include "bitmap.h"
#include "image.hpp"
#include "jmalloc.hpp"

unsigned char current_background;
extern unsigned int xres,yres;
extern palette *lastl;
int X_xoff,Y_yoff,vmode;
image *screen;
char display_name[120];

Window root;
Display *display;
int screen_num;
Screen *screen_ptr;
unsigned border_width,depth;
Colormap XCMap;
Window mainwin;
GC gc;
XFontStruct *font_info;
XVisualInfo *my_visual;
Visual *X_visual;

static int pixel_scale=1;

uchar last_load_palette[256*4];  // store word alligned for faster access




// ========================================================================
// makes a null cursor
// ========================================================================

static Cursor CreateNullCursor(Display *display, Window root)
{
    Pixmap cursormask; 
    XGCValues xgc;
    GC gc;
    XColor dummycolour;
    Cursor cursor;

    cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/);
    xgc.function = GXclear;
    gc =  XCreateGC(display, cursormask, GCFunction, &xgc);
    XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
    dummycolour.pixel = 0;
    dummycolour.red = 0;
    dummycolour.flags = 04;
    cursor = XCreatePixmapCursor(display, cursormask, cursormask,
          &dummycolour,&dummycolour, 0,0);
    XFreePixmap(display,cursormask);
    XFreeGC(display,gc);
    return cursor;
}



#ifndef NO_XSHM
// ************** SHM Vars *******************
int shm_base,shm_error_base,shm_finish_event,doShm=0;


struct event_node
{
  XEvent report;  
  event_node *next;  
} ;


void wait_shm_finish()
{
  event_node *en,*first=NULL;
  while (1)
  { 
    en=(event_node *)jmalloc(sizeof(event_node),"shm_wait : event");
    XNextEvent(display, &en->report);

    // if this is the finishing event, put all the stored events back
    // on the que
#ifndef NO_XSHM
    if (en->report.type==shm_base+ShmCompletion)
    {
      jfree(en);
      
      while (first)
      {
	XPutBackEvent(display,&first->report);
	en=first;
	first=first->next;
	jfree(en);	
      }
      return ;      
    } else // put the event on the que to be puback
#endif
    {
      en->next=first;     // put the back in reverse order
      first=en;
    }    
  }
}

    
#endif


int get_vmode()
{ return vmode; }

void getGC(Window win, GC *gc, XFontStruct *font_info)
{
  XGCValues values;
  *gc=XCreateGC(display,win,0,&values);
  XSetFont(display, *gc, font_info->fid);
  XSetForeground(display, *gc, BlackPixel(display,screen_num));
  XSetLineAttributes(display, *gc, 1, LineSolid, CapRound, JoinRound);
}


class XImage_Info             // stored in the extended desciptor
{
public :
  
#ifndef NO_XSHM
  XShmSegmentInfo X_shminfo;    
#endif
  XImage *XImg;
} ;


void image::make_page(short width, short height, unsigned char *page_buffer)
{
  XImage_Info *xi;  
  if (special && !special->static_mem)
  {
    xi=new XImage_Info;
    special->extended_descriptor=(void *)xi;    
#ifndef NO_XSHM
    if (doShm)
    {
      width=(width+3)&(0xffffffff-3);
      // create the image
      xi->XImg = XShmCreateImage(display,
				 X_visual,
				 my_visual->depth,
				 ZPixmap,
				 0,
				 &xi->X_shminfo,
				 width*pixel_scale,
				 height*pixel_scale);

	  w = width = xi->XImg->bytes_per_line / pixel_scale;

      // create the shared memory segment
      xi->X_shminfo.shmid = shmget (IPC_PRIVATE,
	  	width*height*pixel_scale*pixel_scale, IPC_CREAT | 0777);
      ERROR(xi->X_shminfo.shmid>=0,"shmget() failed, go figure");

      xi->X_shminfo.readOnly=False;

      // attach to the shared memory segment to us
      xi->XImg->data = xi->X_shminfo.shmaddr =
		(char *) shmat(xi->X_shminfo.shmid, 0, 0);
      ERROR(xi->XImg->data,"shmat() failed, go figure");

      // get the X server to attach to it to the X server
      ERROR(XShmAttach(display, &xi->X_shminfo),"XShmAttach() failed, go figure");
      XSync(display,False); // make sure segment gets attached
      ERROR(shmctl(xi->X_shminfo.shmid,IPC_RMID,NULL)==0,"shmctl failed, why?"); 

	  if (pixel_scale == 1)
	    data=(unsigned char *) (xi->XImg->data);
	  else
	    data=(unsigned char *) jmalloc(width*height, "unscaled image");
    } else 
#endif
    {
      width=(width+3)&(0xffffffff-3);        
      if (!page_buffer)
        page_buffer=
			(unsigned char *)jmalloc(width*height*pixel_scale*pixel_scale,
			"image::data");
      
      xi->XImg = XCreateImage(	display,
    				X_visual,
    				my_visual->depth,
    				ZPixmap,
    				0,
    				(char *)page_buffer,
    				width*pixel_scale,
			        height*pixel_scale,
    				32,
    				0 );
      ERROR(xi->XImg,"XCreateImage failed");

	  w = width = xi->XImg->bytes_per_line / pixel_scale;

	  if (pixel_scale == 1)
  	    data=(unsigned char *) (xi->XImg->data);
  	  else
	    data=(unsigned char *) jmalloc(width*height, "unscaled image");
    }    
  }
  else 
  {
    if (!page_buffer)
      data=(unsigned char *)jmalloc(width*height,"image::data");
    else data=page_buffer;
  }

  if (special)
    special->resize(width,height);

}

void image::delete_page()
{ 
  XImage_Info *xi;  
  if (special && !special->static_mem && special->extended_descriptor)
  {    
    xi=(XImage_Info *)special->extended_descriptor;    
#ifndef NO_XSHM
    if (doShm)
    {    
      XFlush(display);
      XSync(display,False);  // maker sure anything with this image is drawn!

      // Dettach the memory from the server
      ERROR(XShmDetach(display, &xi->X_shminfo),"XShmDetach() failed, go figure");

      XSync(display,False);  // maker sure server detached

      // detach the memory from us, it will be deleted!
      ERROR(shmdt(xi->X_shminfo.shmaddr)>=0,"shmdt failed, oops");

      xi->XImg->data=NULL;  // tell X not to try to free the memory, cause we already did.

      XDestroyImage(xi->XImg);    

      if (pixel_scale > 1)
        jfree(data);

    } else
#endif
    {
      if (!special->static_mem)
        jfree(xi->XImg->data);
      xi->XImg->data=NULL;                  // make sure X doesn't try to free static memory
      XDestroyImage(xi->XImg);    

      if (pixel_scale > 1)
        jfree(data);
    }
    delete xi;
  }
  else if (!special)
    jfree(data);      
}


#ifndef NO_XSHM
#ifdef __sgi
void clean_shm(...)
#else
void clean_shm(int why)      // on exit, delete all images, so shm get de-allocated.
#endif
{
  while (image_list.first())
  {
    image *i=(image *)image_list.first();
    delete i;
  }
}
#endif

void set_mode(int mode, int argc, char **argv)
{
  unsigned char *page;
  vmode=mode;
  XVisualInfo vis_info;
  int items,i;
  XEvent report;
  XWMHints *wm_hints;
  XClassHint *class_hints;
  XTextProperty winName,iconName;
  XSizeHints *size_hints;
  char *win_name=argv[0],        // name the window and the icon the executable name
       *icon_name=argv[0];
  Pixmap icon_pixmap;
  
  // get the default display name from the enviroment variable DISPLAY
  char *ds_name=getenv("DISPLAY");
  if (!ds_name) ds_name="unix:0.0";
  strcpy(display_name,ds_name); 
 
  int suppress_shm=0;

  // check for command line display name
  for (i=1;i<argc;i++)
  {
    if (!strcasecmp(argv[i],"-display") || !strcasecmp(argv[i],"-disp"))
    {
      i++;      
      strcpy(display_name,argv[i]);  
    }    
    else if (!strcasecmp(argv[i],"-noshm"))
      suppress_shm=1;
    else if (!strcasecmp(argv[i],"-pixel_scale"))
      pixel_scale=atoi(argv[i+1]);
  }
    
  display=XOpenDisplay(display_name);  
  if (!display)
  {
    printf("Cound not connect to X server named '%s'\n",display_name);
    exit(1);
  }

#ifndef NO_XSHM
  // check for the MITSHM extension
  int major_op;
  
  if (suppress_shm)
    doShm=0;
  else
    doShm = XQueryExtension(display,"MIT-SHM",&major_op,&shm_base,&shm_error_base);

  // make sure it's a local connection
  if (doShm)
  {    
    char *d = display_name;
    while (*d && (*d != ':')) d++;
    if (*d) *d = 0;
    if (strcasecmp(display_name, "unix") && display_name[0]!=0) 
      doShm = 0;   
  }

  if (doShm)
  {
    printf("Using MITSHM extension!\n");
    int sigs[29]={SIGHUP,SIGINT,SIGQUIT,SIGILL,SIGTRAP,
		SIGABRT,SIGIOT,SIGBUS,SIGFPE,SIGKILL,
		SIGUSR1,SIGSEGV,SIGUSR2,SIGPIPE,SIGALRM,
                SIGTERM,SIGCHLD,SIGCONT,SIGSTOP,
		SIGTSTP,SIGTTIN,SIGTTOU,SIGIO,
		SIGURG,SIGXCPU,SIGXFSZ,SIGVTALRM,SIGPROF,
		SIGWINCH};

    for (int i=0;i<29;i++)
      signal(sigs[i],clean_shm);
  }
#endif
  depth=8;
  screen_num = DefaultScreen(display);
  screen_ptr = DefaultScreenOfDisplay(display);
  ERROR(XGetGeometry(display,RootWindow(display,screen_num), &root,
	&X_xoff,&Y_yoff,&xres,&yres,&border_width,&depth),
	"can't get root window attributes");

  {
    vis_info.c_class=PseudoColor;       
    vis_info.depth=8;
    my_visual=XGetVisualInfo(display,VisualClassMask | VisualDepthMask, &vis_info,&items);
    X_visual=my_visual->visual;

    if (items>0)
    {
      int rc;

      vmode=XWINDOWS_256;

      rc=XMatchVisualInfo(display, screen_num, 8, PseudoColor, &vis_info);
      ERROR(rc, "What the hell? Non-8 bit Psuedo color..\n");
      X_visual = my_visual->visual = vis_info.visual;
      //    ERROR(my_visual->depth==8,"What the hell? Non-8 bit Psuedo color..\n");
      printf("Using 8 bit Psuedo color\n");
    }
    else
    { 
      printf("X windows screen type not supported\n");
      exit(0); 
    }
  }

  xres=320; yres=200;
  for (i=1;i<argc;i++)
  { if (!strcmp(argv[i],"-size"))
    { i++; if (!sscanf(argv[i],"%d",&xres)) xres=320;
      i++; if (!sscanf(argv[i],"%d",&yres)) yres=200;
    }
  }


  Colormap tmpcmap;
  
  tmpcmap = XCreateColormap(display, XRootWindow(display,
						my_visual->screen), X_visual, AllocNone);

  int attribmask = CWColormap | CWBorderPixel;
  XSetWindowAttributes attribs;
  attribs.border_pixel = 0;
  attribs.colormap = tmpcmap;
  
  mainwin=XCreateWindow(display,
			XRootWindow(display,my_visual->screen),
			0,0,xres*pixel_scale, yres*pixel_scale,
			0,
			my_visual->depth,
			InputOutput,
			X_visual,
			attribmask,
			&attribs);
  xres--; yres--;


  icon_pixmap=XCreateBitmapFromData(display,mainwin,bitmap_bits,
	bitmap_width, bitmap_height);
  ERROR((size_hints=XAllocSizeHints()),"memory allocation error");
  ERROR((wm_hints=XAllocWMHints()),"memory allocation error");
  ERROR((class_hints=XAllocClassHint()),"memory allocation error");

  int lock=0;
  for (i=1;i<argc;i++)
    if (!strcmp(argv[i],"-lock_size"))
     lock=1;

  if (lock)
  {
    size_hints->flags=PPosition | PSize | PMinSize | PMaxSize;
    size_hints->min_width  = (xres+1)*pixel_scale;
    size_hints->min_height = (yres+1)*pixel_scale;
    size_hints->max_width  = (xres+1)*pixel_scale;
    size_hints->max_height = (yres+1)*pixel_scale;
  }
  else
  {
    size_hints->flags=PPosition | PSize | PMinSize;
    size_hints->min_width  = 320;
    size_hints->min_height = 200;
  }


  ERROR(XStringListToTextProperty(&win_name,1,&winName),"alloc failed");
  ERROR(XStringListToTextProperty(&icon_name,1,&iconName),"alloc fialed");
  wm_hints->initial_state=NormalState;  // not iconified at first
  wm_hints->input=1;                  // needs keyboard input
  wm_hints->icon_pixmap=icon_pixmap;
  wm_hints->flags=StateHint | IconPixmapHint | InputHint;
  class_hints->res_name=argv[0];
  class_hints->res_class="(C) 1995 Crack dot Com, Jonathan Clark";
  XSetWMProperties(display,mainwin,&winName,&iconName,argv,argc,size_hints,
	wm_hints,class_hints);
  XSelectInput(display,mainwin,ExposureMask|KeyPressMask|ButtonPressMask|
	StructureNotifyMask);
  ERROR(font_info=XLoadQueryFont(display,"9x15"),"cannot open 9x15");
  getGC(mainwin,&gc,font_info);

/* try to determine what type of monitor we are using */


  // detect which type of screen the X client will run on...
  // if they requested 24 bit mode, check for that type of display
 


  XSetBackground(display,gc,BlackPixel(display,screen_num));

  XMapWindow(display,mainwin);
  do
  { XNextEvent(display, &report);
  } while (report.type!= Expose);     // wait for our window to pop up


  XDefineCursor(display,mainwin, CreateNullCursor(display,mainwin));

  screen=new image(xres+1,yres+1,NULL,2);  

  XCMap=XCreateColormap(display,mainwin,X_visual,AllocAll);  
  screen->clear();
  update_dirty(screen);

  for (i=1;i<argc;i++)
  { if (!strcmp(argv[i],"-grab_pointer"))
    {
      i++;
      int sec;
      if (i<argc && sscanf(argv[i],"%d",&sec))
      { printf("-grab_pointer : delaying for %d seconds\n",sec);
	sleep(sec);
      }
      if (XGrabPointer(display,mainwin,True,
		       PointerMotionMask|ButtonMotionMask|
		       Button1MotionMask|
		       Button2MotionMask|
		       Button3MotionMask|
		       Button4MotionMask|
		       Button5MotionMask,
		       GrabModeAsync,
		       GrabModeAsync,
		       mainwin,
		       None,
		       CurrentTime)==GrabSuccess)
        printf("Pointer grab in effect!\n");
      else
        printf("Unable to grab pointer  :(\n");
    }
  }
    
}

void close_graphics()
{
  if (lastl) delete lastl; lastl=NULL;
  delete screen;
  XUnloadFont(display,font_info->fid);
  XFreeGC(display,gc);
  XFree((char *)my_visual);
  XCloseDisplay(display);
}

void scale_image(image *im, int src_x, int src_y, int src_w, int src_h)
{

  uchar *src;
  ushort *dst;
  ushort pixel;
  int dx;
  int y;
  int next_dst_line;
  int w;
  XImage_Info *xi=(XImage_Info *)im->special->extended_descriptor;

  if (pixel_scale == 2)
  {
	  src=im->scan_line(src_y)+src_x;
      dst=(ushort *) (xi->XImg->data+src_y*xi->XImg->bytes_per_line*2+src_x*2);
	  next_dst_line = xi->XImg->bytes_per_line/2;
	  w = im->width();

	  for (y=src_h;y;y--)
	  {
		for (dx=0;dx<src_w;dx++)
		{
		  pixel = *(src+dx);
		  pixel |= pixel<<8;
		  *(dst+dx) = pixel;
		  *(dst+dx+next_dst_line) = pixel;
		}
		src += w;
		dst += xi->XImg->bytes_per_line;
	  }

  }

}

void copy_24part(uchar *Xdata, image *im, int x1, int y1, int x2, int y2)
{
  uchar *src=im->scan_line(y1)+x1;
  ushort *dst=(ushort *)Xdata+(y1*2)*im->width()+x1;

  int src_add=im->width()-(x2-x1+1);

  int x,y,w=(x2-x1+1)*2;
  ushort v;
  for (y=y1;y<=y2;y++)
  {
    for (x=x1;x<=x2;x++)
    {
      v=*(src++);
      *(dst++)=(v|(v<<8));
    }    
    dst=(ushort *) ((uchar *)dst+src_add*2);
    memcpy(dst,(uchar *)dst-im->width()*2,w);
    dst=(ushort *)((uchar *)dst+im->width()*2);

    src+=src_add;
  }
}



void put_part_image(Window win, image *im, int x, int y, int x1, int y1, int x2, int y2)
{
  CHECK(im->special);
  XImage_Info *xi=(XImage_Info *)im->special->extended_descriptor;

  if (pixel_scale>1)
    scale_image(im,x1,y1,x2-x1+1,y2-y1+1);

#ifndef NO_XSHM
  if (doShm)
  {    
    XEvent ev;
    XSync(display,False);
    if (XCheckTypedEvent(display, ConfigureNotify,&ev)==False)
    {
      XShmPutImage(display,win,gc,xi->XImg,x1*pixel_scale,y1*pixel_scale,
	    x*pixel_scale,y*pixel_scale,(x2-x1+1)*pixel_scale,
		(y2-y1+1)*pixel_scale,True);
      // wait for the Put to finish
      wait_shm_finish();
    } else     // screen size changed,  better wait till this event is handled cause put might be invalid
      XPutBackEvent(display,&ev);

  }  
  else  
#endif
    XPutImage(display,win,gc,xi->XImg,x1*pixel_scale,y1*pixel_scale,
      x*pixel_scale,y*pixel_scale,(x2-x1+1)*pixel_scale,(y2-y1+1)*pixel_scale);

}

void put_image(image *im, int x, int y)
{
  put_part_image(mainwin,im,x,y,0,0,im->width()-1,im->height()-1);  
}



void update_dirty_window(Window win, image *im, int xoff, int yoff)
{

  int count;
  dirty_rect *dr,*q;
  image *Xim;
  CHECK(im->special);  // make sure the image has the ablity to contain dirty areas
  if (im->special->keep_dirt==0)
    put_image(im,xoff,yoff);
  else
  {
    count=im->special->dirties.number_nodes();
    if (!count) return;  // if nothing to update, return
    dr= (dirty_rect *) (im->special->dirties.first());
    while (count>0)
    {
      put_part_image(win,im,xoff+dr->dx1,yoff+dr->dy1,dr->dx1,dr->dy1,dr->dx2,dr->dy2);     
//      XDrawRectangle(display,mainwin,gc,xoff+dr->dx1,yoff+dr->dy1,
//		     xoff+dr->dx2-dr->dx1+1,yoff+dr->dy2-dr->dy1+1);
      q=dr;
      dr=(dirty_rect *) (dr->next());
      im->special->dirties.unlink((linked_node *)q);
      delete q;
      count--;
    }
  }
//  XFlush(display);
}

void update_dirty(image *im, int xoff, int yoff)
{ update_dirty_window(mainwin,im,xoff,yoff); }

void palette::load()
{
  if (lastl)
    delete lastl;
  lastl=copy();

  {
    int i;
    if (get_vmode()!=XWINDOWS_2)
    {
      XColor color;
      color.flags=DoRed|DoBlue|DoGreen;
      for (i=0;i<ncolors;i++)
      {
	color.pixel=i;
	color.red=red(i)<<8|0xff; color.green=green(i)<<8|0xff; color.blue=blue(i)<<8|0xff;
	XStoreColor(display,XCMap,&color);
      }
      XSetWindowColormap(display,mainwin,XCMap);
    }
    else lastl->black_white();
    current_background=bg;
  }
}

struct hist_entry
{
  long total;
  unsigned char org;
} ;

static int histcompare(const void *i, const void *j)
{ 
  if (((hist_entry *)i)->org==0) return -1;  // make sure the background gets allocated
  else return(((hist_entry *)j)->total - ((hist_entry *)i)->total); 
}


void palette::load_nice()
{
  int i,fail,y,x,wd,j;
  long closest_val,closest_pixel,k;
  char *gotten;
  image *im;
  unsigned char *sl;
  palette *Xpal;
  hist_entry *histogram;
  Colormap stdcm;
  XColor color;

  if (get_vmode()!=XWINDOWS_2)  
  {
    filter f(ncolors);

    histogram=(hist_entry *)jmalloc(sizeof(hist_entry)*ncolors,"palette::histogram"); 
    gotten=(char *)jmalloc(ncolors,"palette::gotten_colors");

    memset(gotten,0,ncolors);
    for (i=0;i<ncolors;i++)
    { histogram[i].total=0;
      histogram[i].org=i;
    }

    for (i=image_list.number_nodes(),im=(image *)image_list.first();i;i--,im=(image *)im->next()) 
    { 
      if (im!=screen)
      { 
        wd=im->width();
        for (y=im->height();y;y--)
        {
          sl=im->scan_line(y-1);
          for (x=0;x<wd;x++)
            histogram[sl[x]].total++;
        }
      }
    }
    qsort(histogram,ncolors,sizeof(hist_entry),histcompare);
    stdcm=XDefaultColormap(display,screen_num);
    Xpal=NULL;
    for (i=0,fail=0;i<ncolors;i++)
    {
      color.red=red(histogram[i].org)<<8; 
      color.green=green(histogram[i].org)<<8; 
      color.blue=blue(histogram[i].org)<<8; 

      if (XAllocColor(display,stdcm,&color))
        f.set(histogram[i].org,color.pixel);
      else  // if we couldn't allocate that color from X, find the closest.
      {
        if (!Xpal)
        { Xpal=new palette(ncolors);
          for (j=0;j<ncolors;j++)
          {
            color.pixel=j;
            XQueryColor(display,stdcm,&color);
            Xpal->set(j,color.red>>8,color.green>>8,color.blue>>8);
          }
        }
        closest_val=0xfffffff;
        for (j=0;j<ncolors;j++)
        {
          k=Xpal->red(j)*Xpal->red(j)+Xpal->green(j)*Xpal->green(j)+
            Xpal->blue(j)*Xpal->blue(j);
          if (k<closest_val)
          { closest_val=k;
            closest_pixel=j;
          }
        }
        f.set(histogram[i].org,closest_pixel);
      }
    }
    if (!Xpal)
      Xpal=new palette(ncolors);
    // store the color mapping we created back to the palette.
    for (j=0;j<ncolors;j++)
      Xpal->set(j,red(f.get_mapping(j)),green(f.get_mapping(j)),blue(f.get_mapping(j)));
    for (j=0;j<ncolors;j++)
      set(j,Xpal->red(j),Xpal->green(j),Xpal->blue(j));  
    bg=f.get_mapping(bg);
      
    // now remap all the images to fit this colormap!
    for (i=image_list.number_nodes(),im=(image *)image_list.first();i;i--,im=(image *)im->next()) 
      f.apply(im);

    delete Xpal;
    jfree(histogram);
    jfree(gotten);
  }
  current_background=bg;
}

