/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "video/x11/x11_input.hh"
#include "main/main.hh"
#include "video/x11/x11_display.hh"
#include "video/x11/mitshm.hh"
#include "video/display.hh"
#include "device/event.hh"
#include "device/keys.hh"
#include "device/kernel.hh"
#include "image/context.hh"

extern int i4_global_native_argc;
extern char **i4_global_native_argv;


int i4_system_choose_option(char **choices)
{
  return 0;
}

i4_bool x11_input_class::open_display()
{
  // get the default display name from the environment variable DISPLAY
  char ds_name[512];

  strcpy(ds_name,getenv("DISPLAY"));
  if (ds_name[0]==0)
    strcpy(ds_name,"unix:0.0");

  if (getenv("X_REPEAT_ON"))
    repeat_on=i4_T;

  // check the command line args for another display
  w32 i=1;
  for (;i0)
  {
    XMatchVisualInfo(display, screen_num, vis_info.depth, vis_info.c_class, &vis_info);
    v->visual = vis_info.visual;
    return v;
  } 
  else if (depth==16)
    return find_visual_with_depth(15);
  else
    return 0;
}


// makes a null cursor
static Cursor x11_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;
}

i4_bool x11_input_class::create_window(sw32 x, sw32 y, w32 w, w32 h,
                                       i4_display_class *_i4_display,
                                       i4_bool takeup_fullscreen,
                                       XVisualInfo *visual)
{
  i4_display=_i4_display;

  if (!open_display()) 
    return i4_F;

  XVisualInfo vis_info;
  int items;
  XEvent report;
  XTextProperty winName;



  // this will be 8, 16, or 32, (most likely 16 for now)
  my_visual = visual;


  if (!my_visual)
    return i4_F;

  screen_num  = DefaultScreen(display);


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

  int attribmask = CWColormap | CWBorderPixel;
  XSetWindowAttributes attribs;
  attribs.border_pixel = 0;
  attribs.colormap = tmpcmap;

  w=(w+3)&(~3);  
  mainwin=XCreateWindow(display,
                        XRootWindow(display,my_visual->screen),
                        x,y,
                        w,h,
                        0,
                        my_visual->depth,
                        InputOutput,
                        my_visual->visual,
                        attribmask,
                        &attribs);
  XFreeColormap(display,tmpcmap);

  XSelectInput(display,mainwin, 
               KeyPressMask     | VisibilityChangeMask | ButtonPressMask | ButtonReleaseMask |
               ButtonMotionMask | PointerMotionMask    | KeyReleaseMask  | ExposureMask      | 
               StructureNotifyMask | EnterWindowMask | LeaveWindowMask);

  XGCValues values;
  gc=XCreateGC(display,mainwin,0,&values);
  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

  i4_kernel.add_device(this);

  XDefineCursor(display, mainwin, x11_CreateNullCursor(display, mainwin));


  wm_delete_window = XInternAtom (display, "WM_DELETE_WINDOW", True);
  wm_protocols = XInternAtom (display, "WM_PROTOCOLS", True);

  Atom prot[2];
  prot[0]=wm_delete_window;
  prot[1]=wm_protocols;
  
  XSetWMProtocols(display, mainwin, prot, 2);
  

  return i4_T;
}


void x11_input_class::destroy_window()
{ 
  i4_kernel.remove_device(this);

  XFreeGC(display, gc);
  XFree((char *)my_visual);

  close_display();

//   if (I4_SCREEN_DEPTH==8)
//     XFreeColormap(display,xcolor_map);
}



void x11_input_class::get_x_time(w32 xtick, i4_time_class &t)
{
  i4_time_class now;

  if (need_first_time)
  {
    first_time=xtick;
    i4_start_time=now;
    need_first_time=i4_F;
  }

  w32 xtime=xtick-first_time;
  
  t = i4_start_time;
  t.add_milli(xtime);
              
  if (nowadd_both_dirty(xev.xexpose.x,
                                    xev.xexpose.y,
                                    xev.xexpose.x+xev.xexpose.width,
                                    xev.xexpose.y+xev.xexpose.height);
          }
        } break;


        case ClientMessage:
        {
          /* Client messages are the means of the window manager
           *  communicating with a program. We'll first check to
           *  see if this is really the window manager talking
           *  to us.
           */
          if (xev.xclient.message_type == wm_protocols)
          {
            if ((Atom) xev.xclient.data.l[0] == wm_delete_window)
	    {
              i4_display_close_event_class dc(i4_display);
              send_event_to_agents(&dc, i4_device_class::FLAG_DISPLAY_CLOSE);
            }
          }
        } break;

        //{{{  not implemented yet
        case ConfigureNotify :
        {
          XFlush(display);

          int new_width=xev.xconfigure.width&~3;  // must be word alligned
          int new_height=xev.xconfigure.height;
          if (new_width!=xev.xconfigure.width)
            XResizeWindow(display,mainwin,new_width,xev.xconfigure.height);

          XFlush(display);

          resize(new_width, new_height);
   
          i4_display_change_event_class d_change(i4_display,
                                                 i4_display_change_event_class::SIZE_CHANGE);
          
          send_event_to_agents(&d_change,FLAG_DISPLAY_CHANGE);
        } break;
        
        case MotionNotify : 
        {
          motion_occured=i4_T;
          final_x=xev.xmotion.x;
          final_y=xev.xmotion.y;
        } break;
          
        case ButtonRelease :
        case ButtonPress :
        {   
          i4_mouse_button_event_class::btype but;
          switch (xev.xbutton.button)
          {
            case 1 : but=i4_mouse_button_event_class::LEFT; break;
            case 3 : but=i4_mouse_button_event_class::RIGHT; break;
            case 2 : but=i4_mouse_button_event_class::CENTER; break;
          }

          i4_time_class now;
          if (xev.type == ButtonRelease)
          {
            i4_mouse_button_up_event_class up(but, mouse_x, mouse_y,
                                              now, last_up[but]);
            send_event_to_agents(&up,FLAG_MOUSE_BUTTON_UP);
            last_up[but]=now;
          }
          else
          {
            i4_mouse_button_down_event_class up(but, mouse_x, mouse_y,
                                                now, last_down[but]);
            send_event_to_agents(&up,FLAG_MOUSE_BUTTON_DOWN);
            last_down[but]=now;
          }
        } break;

        case KeyPress :
        case KeyRelease :
        {
          char buf;
          KeySym ks;
          XLookupString(&xev.xkey,&buf,1,&ks,NULL); 
          w16 key, key_code;

          switch (ks)
          { 
            case XK_Down :    key_code=I4_DOWN; break;
            case XK_Up :      key_code=I4_UP;  break;
            case XK_Left :    key_code=I4_LEFT;  break;
            case XK_Right :     key_code=I4_RIGHT; break;
            case XK_Control_L :   
            { 
              key_code=I4_CTRL_L; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_CTRL_L;  
              else
                modifier_state&=~I4_MODIFIER_CTRL_L;  
            } break;

            case XK_Control_R :   
            { 
              key_code=I4_CTRL_R; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_CTRL_R;  
              else
                modifier_state&=~I4_MODIFIER_CTRL_R;
            } break;

            case XK_Alt_L :   
            { 
              key_code=I4_ALT_L; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_ALT_L;  
              else
                modifier_state&=~I4_MODIFIER_ALT_L;
            } break;

            case XK_Alt_R :   
            { 
              key_code=I4_ALT_R; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_ALT_R;  
              else
                modifier_state&=~I4_MODIFIER_ALT_R;
            } break;


            case XK_Shift_L :   
            { 
              key_code=I4_SHIFT_L; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_SHIFT_L;  
              else
                modifier_state&=~I4_MODIFIER_SHIFT_L;
            } break;

            case XK_Shift_R :
            { 
              key_code=I4_SHIFT_R; 
              if (xev.type==KeyPress)
                modifier_state|=I4_MODIFIER_SHIFT_R;  
              else
                modifier_state&=~I4_MODIFIER_SHIFT_R;
            } break;


            case XK_Num_Lock :          key_code=I4_NUM_LOCK;  break;
            case XK_Home :    key_code=I4_HOME;  break;
            case XK_End :     key_code=I4_END;  break;
            case XK_BackSpace :         key_code=I4_BACKSPACE;  break;
            case XK_Tab :   key_code=I4_TAB;  break;
            case XK_Return :    key_code=I4_ENTER;  break;
            case XK_Caps_Lock :         key_code=I4_CAPS;  break;
            case XK_Escape :    key_code=I4_ESC;  break;
            case XK_F1 :                key_code=I4_F1; break;
            case XK_F2 :                key_code=I4_F2; break;
            case XK_F3 :                key_code=I4_F3; break;
            case XK_F4 :                key_code=I4_F4; break;
            case XK_F5 :                key_code=I4_F5; break;
            case XK_F6 :                key_code=I4_F6; break;
            case XK_F7 :                key_code=I4_F7; break;
            case XK_F8 :                key_code=I4_F8; break;
            case XK_F9 :                key_code=I4_F9; break;
            case XK_F10 :               key_code=I4_F10; break;
            case XK_Insert :            key_code=I4_INSERT; break;
            case XK_Page_Up :           key_code=I4_PAGEUP; break;
            case XK_Page_Down :         key_code=I4_PAGEDOWN; break;
            case XK_Delete :            key_code=I4_DEL; break;

            case XK_KP_0 :              key_code=I4_KP0; break;
            case XK_KP_1 :              key_code=I4_KP1; break;
            case XK_KP_2 :              key_code=I4_KP2; break;
            case XK_KP_3 :              key_code=I4_KP3; break;
            case XK_KP_4 :              key_code=I4_KP4; break;
            case XK_KP_5 :              key_code=I4_KP5; break;
            case XK_KP_6 :              key_code=I4_KP6; break;
            case XK_KP_7 :              key_code=I4_KP7; break;
            case XK_KP_8 :              key_code=I4_KP8; break;
            case XK_KP_9 :              key_code=I4_KP9; break;

            case XK_KP_Insert :         key_code=I4_KP0; break;
            case XK_KP_End :            key_code=I4_KP1; break;
            case XK_KP_Down :           key_code=I4_KP2; break;
            case XK_KP_Page_Down :      key_code=I4_KP3; break;
            case XK_KP_Left :           key_code=I4_KP4; break;
            case XK_KP_Begin :          key_code=I4_KP5; break;
            case XK_KP_Right :          key_code=I4_KP6; break;
            case XK_KP_Home :           key_code=I4_KP7; break;
            case XK_KP_Up :             key_code=I4_KP8; break;
            case XK_KP_Page_Up :        key_code=I4_KP9; break;

						case XK_KP_Delete :
						case XK_KP_Decimal :				key_code=I4_KPPERIOD; break;

            case ' ':                   key_code=I4_SPACE; break;
            case '`':
            case '~':                   key_code='`'; break;
            case '-':
            case '_':                   key_code='-'; break;
            case '=':
            case '+':                   key_code='='; break;
            case '[':
            case '{':                   key_code='['; break;
            case ']':
            case '}':                   key_code=']'; break;
            case '\\':
            case '|':                   key_code='\\'; break;
            case ';':
            case ':':                   key_code=';'; break;
            case '\'':
            case '"':                   key_code='\''; break;
            case ',':
            case '<':                   key_code=','; break;
            case '.':
            case '>':                   key_code='.'; break;
            case '/':
            case '?':                   key_code='/'; break;

            case ')':                   key_code='0'; break;
            case '!':                   key_code='1'; break;
            case '@':                   key_code='2'; break;
            case '#':                   key_code='3'; break;
            case '$':                   key_code='4'; break;
            case '%':                   key_code='5'; break;
            case '^':                   key_code='6'; break;
            case '&':                   key_code='7'; break;
            case '*':                   key_code='8'; break;
            case '(':                   key_code='9'; break;

            default :
              if ((ks>=XK_A && ks<=XK_Z) || (ks>=XK_0 && ks<=XK_9))
                key_code = ks;
              else if (ks>=XK_a && ks<=XK_z)
                key_code = ks + 'A' - 'a';
              else
                key_code=0;
          }

          if (key_code)
          {
            if (xev.type==KeyPress)
            {
              i4_time_class t;
              get_x_time(xev.xkey.time, t);
              
              key = i4_key_translate(key_code,1,modifier_state);
              i4_key_press_event_class ev(key, key_code, modifier_state, t);

              send_event_to_agents(&ev,FLAG_KEY_PRESS);    
            }
            else 
            {
              key = i4_key_translate(key_code,0, modifier_state);

              i4_time_class t;
              get_x_time(xev.xkey.time, t);
              
              i4_key_release_event_class ev(key, key_code, modifier_state, t);
              send_event_to_agents(&ev,FLAG_KEY_RELEASE);    
            }
          }
        } break;

        case EnterNotify:
          if (!repeat_on)
            XAutoRepeatOff(display);
          break;

        case LeaveNotify:
          if (!repeat_on)
            XAutoRepeatOn(display);
          break;
      }
    }
  }

          
  if (motion_occured && (final_x!=mouse_x || final_y!=mouse_y))
  {    
    i4_mouse_move_event_class move(mouse_x, mouse_y, final_x, final_y);
    send_event_to_agents(&move,FLAG_MOUSE_MOVE);

    if (mouse_locked)
      XWarpPointer(display, None, mainwin, 0,0, 0,0, mouse_x, mouse_y);
    else
    {
      mouse_x=final_x;
      mouse_y=final_y;
    }   
  }

  return i4_F;
}