#include <iostream.h> // cout testing

#include <prcore/prcore.hpp>
using namespace prcore;

extern void kbStaticHook(unsigned char key, char ascii, bool state);

extern void mouseButtonStaticHook(XButtonEvent *event, bool state);
extern void mouseMovementStaticHook(XButtonEvent *event);

static bool DestroyWindowOnClose = true;
static WindowBase *window;

// IMPLEMENT KEYBOARD REMAPPING HERE 
// another way to do this must be found
// ie. direct access to keyboard
// but this should suffice.. 
// xfree is HORRIBLY documented.. ugh..

static int remap(int code)
{
  switch ( code )
    {
    case XK_Escape:		return KEYCODE_ESC;
    case XK_0:			return KEYCODE_0;
    case XK_1:			return KEYCODE_1;
    case XK_2:			return KEYCODE_2;
    case XK_3:			return KEYCODE_3;
    case XK_4:			return KEYCODE_4;
    case XK_5:			return KEYCODE_5;
    case XK_6:			return KEYCODE_6;
    case XK_7:			return KEYCODE_7;
    case XK_8:			return KEYCODE_8;
    case XK_9:			return KEYCODE_9;
    case XK_a:			return KEYCODE_A;
    case XK_b:			return KEYCODE_B;
    case XK_c:			return KEYCODE_C;
    case XK_d:			return KEYCODE_D;
    case XK_e:			return KEYCODE_E;
    case XK_f:			return KEYCODE_F;
    case XK_g:			return KEYCODE_G;
    case XK_h:			return KEYCODE_H;
    case XK_i:			return KEYCODE_I;
    case XK_j:			return KEYCODE_J;
    case XK_k:			return KEYCODE_K;
    case XK_l:			return KEYCODE_L;
    case XK_m:			return KEYCODE_M;
    case XK_n:			return KEYCODE_N;
    case XK_o:			return KEYCODE_O;
    case XK_p:			return KEYCODE_P;
    case XK_q:			return KEYCODE_Q;
    case XK_r:			return KEYCODE_R;
    case XK_s:			return KEYCODE_S;
    case XK_t:			return KEYCODE_T;
    case XK_u:			return KEYCODE_U;
    case XK_v:			return KEYCODE_V;
    case XK_w:			return KEYCODE_W;
    case XK_x:			return KEYCODE_X;
    case XK_y:			return KEYCODE_Y;
    case XK_z:			return KEYCODE_Z;
    case XK_F1:			return KEYCODE_F1;
    case XK_F2:			return KEYCODE_F2;
    case XK_F3:			return KEYCODE_F3;
    case XK_F4:			return KEYCODE_F4;
    case XK_F5:			return KEYCODE_F5;
    case XK_F6:			return KEYCODE_F6;
    case XK_F7:			return KEYCODE_F7;
    case XK_F8:			return KEYCODE_F8;
    case XK_F9:			return KEYCODE_F9;
    case XK_F10:		return KEYCODE_F10;
    case XK_F11:		return KEYCODE_F11;
    case XK_F12:		return KEYCODE_F12;
    case XK_KP_0:	return KEYCODE_NUMPAD0;
    case XK_KP_1:	return KEYCODE_NUMPAD1;
    case XK_KP_2:	return KEYCODE_NUMPAD2;
    case XK_KP_3:	return KEYCODE_NUMPAD3;
    case XK_KP_4:	return KEYCODE_NUMPAD4;
    case XK_KP_5:	return KEYCODE_NUMPAD5;
    case XK_KP_6:	return KEYCODE_NUMPAD6;
    case XK_KP_7:	return KEYCODE_NUMPAD7;
    case XK_KP_8:	return KEYCODE_NUMPAD8;
    case XK_KP_9:	return KEYCODE_NUMPAD9;
    case XK_Num_Lock:	return KEYCODE_NUMLOCK;
    case XK_KP_Divide:		return KEYCODE_DIVIDE;
    case XK_KP_Multiply:	return KEYCODE_MULTIPLY;
    case XK_KP_Subtract:	return KEYCODE_SUBTRACT;
    case XK_KP_Add:		return KEYCODE_ADDITION;
    case XK_KP_Decimal:	return KEYCODE_DECIMAL;
    case XK_BackSpace:		return KEYCODE_BACKSPACE;
    case XK_Tab:		return KEYCODE_TAB;
    case XK_Return:		return KEYCODE_RETURN;
    case XK_Control_L:	        return KEYCODE_LEFT_CTRL;
    case XK_Control_R:	        return KEYCODE_RIGHT_CTRL;
    case XK_Shift_L:		return KEYCODE_LEFT_SHIFT;
    case XK_Shift_R:		return KEYCODE_RIGHT_SHIFT;
    case XK_space:		return KEYCODE_SPACE;
    case XK_Sys_Req:		return KEYCODE_PRNT_SCRN;
    case XK_Scroll_Lock:	return KEYCODE_SCROLL_LOCK;
    case XK_Page_Up:		return KEYCODE_PGUP;
    case XK_Page_Down:		return KEYCODE_PGDN;
    case XK_Insert:		return KEYCODE_INS;
    case XK_Delete:		return KEYCODE_DEL;
    case XK_Home:		return KEYCODE_HOME;
    case XK_End:		return KEYCODE_END;
    case XK_Left:		return KEYCODE_LEFT;
    case XK_Right:		return KEYCODE_RIGHT;
    case XK_Up:			return KEYCODE_UP;
    case XK_Down:		return KEYCODE_DOWN;
    case XK_Alt_L:              return KEYCODE_LEFT_ALT;
    case XK_Alt_R:              return KEYCODE_RIGHT_ALT;
    case XK_Caps_Lock:          return KEYCODE_CAPSLOCK;
    case XK_KP_Enter:              return KEYCODE_ENTER;
    default:			return 0;
    }

	// the following cannot be handled by the EventKeyboard()
	// ----------------------------------------------------------------
	// KEYCODE_PRNT_SCREEN
	
}

WindowBase::WindowBase()
{
  mMainBreak = false;

  mWinHandle.win = (Window)NULL;
  mWinHandle.dpy = NULL;


}
WindowBase::~WindowBase()
{
  Close();
}


bool WindowBase::Open(int width, int height, const char *name, int style)
{

  Colormap ColorMap;

  // MUCH WORK TO BE DONE HERE

  if(IsOpen())
    Close();

  // open connection to local xserver
  mWinHandle.dpy = XOpenDisplay(NULL);

  if(mWinHandle.dpy == NULL)
      PRCORE_EXCEPTION("WindowBase::Open", "Unable to establish connection to X Server" );

  // deal with styles here

  if(style && WINDOW_USES3D)
    {

      // reasons for doing this..

      // it was possible to update the colormap of a window
      // but it is impossible? to change the visualinfo of a window
      // using the method commented out in opengl.cpp
      // the gl commands were passed successfully, and the first call
      // to glXswapbuffer worked fine, but the subsequent ones, failed?
      // at least, it had a blank screen
      // now, this sucks, because you can't really choose if you want a stencil 
      // buffer or not, so, basically, I'll just implement something
      // pretty basic, and well.. someone else can figure it out

      if(!glXQueryExtension(mWinHandle.dpy,NULL,NULL))
	{
	  printf("GLX extension not found\n");
	  return NULL;
	}
     
     

      int attrib[] = {GLX_DOUBLEBUFFER, GLX_RGBA, GLX_DEPTH_SIZE, 12,
		      GLX_RED_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_GREEN_SIZE, 1, None};

      
      mWinHandle.VisualInfo = glXChooseVisual(mWinHandle.dpy, DefaultScreen(mWinHandle.dpy), attrib);
     if(mWinHandle.VisualInfo == NULL)
	{
	  printf("No visual availible\n");
	  return NULL;
	}

      
    }

  else
    {
      //NO WINDOW_USES3D
      int  numvis;
      long flags;
      
      XVisualInfo rvinfo;

      //      rvinfo.class  = TrueColor;
      rvinfo.screen = DefaultScreen(mWinHandle.dpy);
      // flags = VisualClassMask | VisualScreenMask;
      flags = VisualScreenMask;
      mWinHandle.VisualInfo = XGetVisualInfo(mWinHandle.dpy, flags, &rvinfo, &numvis);

      //      mWinHandle.VisualInfo = XGetVisualInfo(mWinHandle.dpy, VisualNoMask, 0,0);

      if(mWinHandle.VisualInfo == NULL)
	{
	  printf("No visual availible\n");
	  return NULL;
	}
    }

/* Create the color map for the new window */
  ColorMap = XCreateColormap(mWinHandle.dpy, RootWindow(mWinHandle.dpy, mWinHandle.VisualInfo->screen), mWinHandle.VisualInfo->visual, AllocNone);
  
  XSetWindowAttributes attributes;

  attributes.background_pixmap = None;
  attributes.background_pixel=WhitePixel(mWinHandle.dpy, DefaultScreen(mWinHandle.dpy));
  attributes.colormap = ColorMap;
  attributes.border_pixel = 0;
  attributes.override_redirect = True; // thanks miQ (opengl.org linux forum)
  attributes.event_mask = PointerMotionMask |  ButtonPressMask| ButtonReleaseMask | ExposureMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
  // attributes.event_mask = 0;
  mWinHandle.win = XCreateWindow(mWinHandle.dpy, // display
				 RootWindow(mWinHandle.dpy, mWinHandle.VisualInfo->screen), // parent window
				 0, // x
				 0, // y
				 width,            // width
				 width,                      // height
				 0, // border width
				 mWinHandle.VisualInfo->depth, // depth?
				 InputOutput, // class
				 mWinHandle.VisualInfo->visual, // visual
				 CWBackPixmap | CWBackPixel | CWColormap | CWBorderPixel | CWEventMask | CWColormap | CWOverrideRedirect, // value mask
				 &attributes); // attributes

 
  XSetStandardProperties(mWinHandle.dpy,
                         mWinHandle.win,
                         name,
                         "Prophecy3D",
                         None, // icon pixmap (could be interesting)
                         NULL, // argv
                         0, // argc
                         (XSizeHints *)NULL); // hints

  /* Handle WM_DELETE_WINDOW */
  mWinHandle.delete_atom = XInternAtom(mWinHandle.dpy, "WM_DELETE_WINDOW", False);

  XSetWMProtocols(mWinHandle.dpy, mWinHandle.win, &mWinHandle.delete_atom, 1);


  // Create Empty Cursor

  char zero = 0;

  mWinHandle.EmptyPixmap=XCreatePixmapFromBitmapData(mWinHandle.dpy, mWinHandle.win, &zero,1,1,0,0,1); 
  
  XColor Black;
  memset(&Black,0,sizeof(Black));

  mWinHandle.EmptyCursor = XCreatePixmapCursor (mWinHandle.dpy, mWinHandle.EmptyPixmap, mWinHandle.EmptyPixmap,&Black, &Black, 0, 0);


  // Ok this is a REAL SHIT way to go after this
  // could go at this by catching evrey key event and checking if the 
  // value has been repeated
  XAutoRepeatOff(mWinHandle.dpy);

  XMapRaised(mWinHandle.dpy, mWinHandle.win);

  // May want to shut off screen saver?

  ShowWindow();

  window = this;
  mMainBreak = false;

  return true;
}

bool WindowBase::Close()
  {
  if( IsOpen() )
    {
      EventClose();
      XAutoRepeatOn(mWinHandle.dpy);

      
      if(DestroyWindowOnClose)
	{
	  XFreeCursor (mWinHandle.dpy, mWinHandle.EmptyCursor);
	  mWinHandle.EmptyCursor = 0;
	  XFreePixmap (mWinHandle.dpy, mWinHandle.EmptyPixmap);
	  mWinHandle.EmptyPixmap = 0;

	  // this motherfucker hardcrashes my x server when GLX is on!!
	  // probably because glx is still running.. baah
	  XCloseDisplay(mWinHandle.dpy); // so does this
	
	}
      DestroyWindowOnClose = true;

      mWinHandle.win = (Window)NULL;
      mWinHandle.dpy = NULL;
    }
    return true;
}

void WindowBase::Rename(const char* name)
{
  //XStoreName(mWinHandle.dpy, mWinHandle.win, name);

  XTextProperty title;
    
  XStringListToTextProperty((char **)&name, 1, &title);
  XSetWMName(mWinHandle.dpy,mWinHandle.win, &title);
  XFree(title.value);
  XSync(mWinHandle.dpy, False);

}

void WindowBase::Resize(int width, int height)
{
  XResizeWindow(mWinHandle.dpy, mWinHandle.win,width,height);
}

void WindowBase::Move(int x, int y)
{
  XMoveWindow(mWinHandle.dpy, mWinHandle.win,x,y);
}

void WindowBase::ShowCursor()
{ 
  XUndefineCursor(mWinHandle.dpy,mWinHandle.win);
}

void WindowBase::HideCursor()
{
  XDefineCursor (mWinHandle.dpy, mWinHandle.win, mWinHandle.EmptyCursor);     
}

void WindowBase::ShowWindow()
{

  // there is some problems I believe with these functions..
  XRaiseWindow(mWinHandle.dpy, mWinHandle.win);
}

void WindowBase::HideWindow()
{
  // by using XUnmapWindow() if takes the window from the 'task' bar 
  // in gnome
  
  // weird things happend when you call this too many times..
  // seems like X11 hangs..

  // XLowerWindow(mWinHandle.dpy, mWinHandle.win);
  
  // Lets treat this as Iconify Window

  XIconifyWindow(mWinHandle.dpy,mWinHandle.win,DefaultScreen(mWinHandle.dpy));

 
  

}


void WindowBase::MainLoop()
{
  XEvent event;
  KeySym keysym;
  XSizeHints sizehints;

  while(mMainBreak==false)
    {
      if(!EventMain())
	break;

      if(XPending(mWinHandle.dpy) > 0)
      {
	XNextEvent(mWinHandle.dpy, &event);
	  
	  switch(event.type)
	    {
	    case Expose:
	      if(window)
		{
		  window->EventDraw();
		}
	      break;

	    case ButtonPress:
	      printf("ButtonPress\n");
	      mouseButtonStaticHook((XButtonEvent *)&event,true); 
	      break;

	    case ButtonRelease: 
	      printf("ButtonRelease\n");
	      mouseButtonStaticHook((XButtonEvent *)&event,false); 
	      break;

	    case ConfigureNotify:
	      printf("ConfigureNotify\n");
	      if(window)
		{
		  // dont know why I'm storing these data
		  // .. ooh
		  // this should be like the win32 RECT struct
		  sizehints.x=event.xconfigure.x;
		  sizehints.y=event.xconfigure.y;
		  sizehints.width = event.xconfigure.width;
		  sizehints.height = event.xconfigure.height;
		  sizehints.base_width = event.xconfigure.width;
		  sizehints.base_height = event.xconfigure.height;

		  window->EventSize(event.xconfigure.width, event.xconfigure.height);
		}
	      break;

	    case MotionNotify:
	      // all right two seperate functions is sloppy
	      printf("MotionNotify\n");
	      mouseMovementStaticHook((XButtonEvent *)&event);
	      break;

	    case EnterNotify:
	      // ok.. this not like windows.. since focus is set
	      // when mouse cursor is actually on teh screen
	      // Also.. can look at  Input Focus Events.. that might be 
	      // better
	      if(window)
		window->EventFocus(true);  
		
	      break;
	      
	      // use FocusChangeMask
	      // case FocusIn:
	      //case FocusOut:

	     
	    case LeaveNotify:
	      if(window)
		window->EventFocus(false);
		
	      break;

	    case KeyPress:
	      
	      printf("KeyPress\n");

	      keysym = XLookupKeysym((XKeyEvent *)&event,0);
	      kbStaticHook(remap(keysym),0,true);
	      window->EventKeyboard(remap(keysym),true);
	      break;

	    case KeyRelease:
      	      printf("KeyRelease\n");

	            keysym = XLookupKeysym((XKeyEvent *)&event,0);
	      kbStaticHook(remap(keysym),0,false);
	      window->EventKeyboard(remap(keysym),false);
	      
	      break;
	    
	    case ClientMessage:
	      printf("ClientMessage\n");

	      if(event.xclient.data.l[0] == mWinHandle.delete_atom)
		{
		mMainBreak=true;		
		}
	      break;

	      // ColormapNotify events may be usefull if the colormap of the
	      // window is to be changed.. ie. opengl initialisation

	      // XInstallColormap or XUninstallColormap

	      // set ColormapChangeMask bit
	    default:
	      break;
	 
	    }
      }
    }
}
   
      
	



void WindowBase::MainBreak()
{
  mMainBreak = true;
}

bool WindowBase::EventMain()
{
	// default handler
	// -> keep running ( return true )
	return true;
}


void WindowBase::EventDraw()
{
	// default handler
	// -> do nothing
}


void WindowBase::EventSize(int width, int height)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventKeyboard(int keycode, bool press)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventFocus(bool enter)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventClose()
{
	// default handler
	// -> do nothing
}

bool WindowBase::IsOpen() const
{
  return mWinHandle.win ? true : false;
}

int WindowBase::GetStyle() const
{
  return mWinStyle;
}

WinHandle WindowBase::GetHandle() const
{
  return mWinHandle;
}

Rect WindowBase::GetRect() const
{
  Rect rect;
  XWindowAttributes attrib;

  XGetWindowAttributes(mWinHandle.dpy, mWinHandle.win,&attrib);
  rect.x=attrib.x;
  rect.y=attrib.y;
  rect.width=attrib.width;
  rect.height=attrib.height;

  return rect;
}

float WindowBase::GetAspect() const
{
  Rect rect=GetRect();
  return rect.GetAspect();
}


void MessageBox(const char* name, const char* text, ...)
{
	// parse string
	va_list ArgList;
	char buf[1024];
	va_start( ArgList,text ) ;
	vsprintf( buf,text,ArgList );
	va_end( ArgList );

	// popup window
	//	::MessageBox( NULL, buf, name, MB_OK );
	
	printf(buf); // for now!
}
