#define FL_INTERNALS 1
#include "Frame.H"
#include <X11/Xproto.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <FL/filename.H>
#include "config.h"
#ifdef SHOW_CLOCK
#include <time.h>
#include <signal.h>
#endif

////////////////////////////////////////////////////////////////

static const char* program_name;
static int initializing;

static int xerror_handler(Display* d, XErrorEvent* e) {
  if (initializing && (e->request_code == X_ChangeWindowAttributes) &&
      e->error_code == BadAccess)
    Fl::fatal("Another window manager is running.  You must exit it before running %s.", program_name);
#ifndef DEBUG
  if (e->error_code == BadWindow) return 0;
  if (e->error_code == BadColor) return 0;
#endif
  char buf1[128], buf2[128];
  sprintf(buf1, "XRequest.%d", e->request_code);
  XGetErrorDatabaseText(d,"",buf1,buf1,buf2,128);
  XGetErrorText(d, e->error_code, buf1, 128);
  Fl::warning("%s: %s: %s 0x%lx", program_name, buf2, buf1, e->resourceid);
  return 0;
}

////////////////////////////////////////////////////////////////
// The Fl_Root class looks like a window to fltk but is actually the
// screen's root window.  This is done by using set_xid to "show" it
// rather than have fltk create the window.

class Fl_Root : public Fl_Window {
  int handle(int);
public:
  Fl_Root() : Fl_Window(0,0,Fl::w(),Fl::h()) {
#if FL_MAJOR_VERSION > 1
    clear_double_buffer();
#endif
  }
  void show() {
    if (!shown()) Fl_X::set_xid(this, RootWindow(fl_display, fl_screen));
  }
  void flush() {}
};
Fl_Window *Root;

extern void ShowMenu();
extern int Handle_Hotkey();
extern void Grab_Hotkeys();

int Fl_Root::handle(int e) {
  if (e == FL_PUSH) {
    ShowMenu();
    return 1;
  }
  return 0;
}

#if CLICK_RAISES || CLICK_TO_TYPE
extern void click_raise(Frame*);
#endif

// fltk calls this for any events it does not understand:
static int flwm_event_handler(int e) {
  if (!e) { // XEvent that fltk did not understand.
    XWindow window = fl_xevent->xany.window;
    // unfortunately most of the redirect events put the interesting
    // window id in a different place:
    switch (fl_xevent->type) {
    case CirculateNotify:
    case CirculateRequest:
    case ConfigureNotify:
    case ConfigureRequest:
    case CreateNotify:
    case DestroyNotify:
    case GravityNotify:
    case MapNotify:
    case MapRequest:
    case ReparentNotify:
    case UnmapNotify:
      window = fl_xevent->xmaprequest.window;
    }
    for (Frame* c = Frame::first; c; c = c->next)
      if (c->window() == window || fl_xid(c) == window)
#if CLICK_RAISES || CLICK_TO_TYPE
	if (fl_xevent->type == ButtonPress) {click_raise(c); return 1;}
	else
#endif
	  return c->handle(fl_xevent);
    switch (fl_xevent->type) {
    case ButtonPress:
      printf("got a button press in main\n");
      return 0;
    case ConfigureRequest: {
      const XConfigureRequestEvent *e = &(fl_xevent->xconfigurerequest);
      XConfigureWindow(fl_display, e->window,
		       e->value_mask&~(CWSibling|CWStackMode),
		       (XWindowChanges*)&(e->x));
      return 1;}
    case MapRequest: {
      const XMapRequestEvent* e = &(fl_xevent->xmaprequest);
      (void)new Frame(e->window);
      return 1;}
#if FL_MAJOR_VERSION<2
    // this was needed for *some* earlier versions of fltk
    case KeyRelease:
      if (!Fl::grab()) return 0;
      Fl::e_keysym =
	XKeycodeToKeysym(fl_display, fl_xevent->xkey.keycode, 0);
      goto KEYUP;
#endif
    }
  } else if (e == FL_KEYUP) {
#if FL_MAJOR_VERSION<2
  KEYUP:
#endif
    if (!Fl::grab()) return 0;
    // when alt key released, pretend they hit enter & pick menu item
    if (Fl::event_key()==FL_Alt_L || Fl::event_key()==FL_Alt_R) {
      Fl::e_keysym = FL_Enter;
#if FL_MAJOR_VERSION>1
      return Fl::modal()->handle(FL_KEYBOARD);
#else
      return Fl::grab()->handle(FL_KEYBOARD);
#endif
    }
    return 0;
  } else if (e == FL_SHORTCUT || e == FL_KEYBOARD) {
#if FL_MAJOR_VERSION == 1 && FL_MINOR_VERSION == 0 && FL_PATCH_VERSION < 3
    // make the tab keys work in the menus in older fltk's:
    // (they do not cycle around however, so a new fltk is a good idea)
    if (Fl::grab()) {
      // make fltk's menus resond to tab + shift+tab:
      if (Fl::event_key() == FL_Tab) {
	if (Fl::event_state() & FL_SHIFT) goto J1;
	Fl::e_keysym = FL_Down;
      } else if (Fl::event_key() == 0xFE20) {
	J1: Fl::e_keysym = FL_Up;
      } else return 0;
      return Fl::grab()->handle(FL_KEYBOARD);
    }
#endif
    return Handle_Hotkey();
  }
  return 0;
}

#if DESKTOPS
extern void init_desktops();
extern Atom _win_workspace;
extern Atom _win_workspace_count;
extern Atom _win_workspace_names;
#endif

extern Atom _win_state;
extern Atom _win_hints;

#ifdef SHOW_CLOCK
int clock_period = 1;
int clock_oldmin = 61;
int clock_alarm_on = 0;
char clock_buf[80];

struct sigaction flwm_clock_alarm_start = {0,}, flwm_clock_alarm_stop = {0,};

void flwm_update_clock(void*) {
    time_t newtime;
    struct tm *tm_p;

    // get current time
    time(&newtime);
    tm_p = localtime(&newtime);

    // Update a window frame if necessary
    if (Frame::activeFrame() && tm_p->tm_min != clock_oldmin) {
	if (clock_oldmin != 61)
	    clock_period = 60;  // now that we're in sync, only update 1/minute
	clock_oldmin = tm_p->tm_min;
	strftime(clock_buf, 80, SHOW_CLOCK, tm_p);
	Frame::activeFrame()->redraw_clock();
    }
    // Now reschedule the timeout
    Fl::remove_timeout(flwm_update_clock);
    Fl::add_timeout(clock_period, flwm_update_clock);
}

void flwm_clock_alarm_on(int) {
    clock_alarm_on = 1;
    Frame::activeFrame()->redraw_clock();
}

void flwm_clock_alarm_off(int) {
    clock_alarm_on = 0;
    Frame::activeFrame()->redraw_clock();
}
#endif

static const char* cfg, *cbg;
#if FL_MAJOR_VERSION>1
static fltk::Cursor* cursor = FL_CURSOR_ARROW;
extern FL_API fltk::Color fl_cursor_fg;
extern FL_API fltk::Color fl_cursor_bg;
#else
static int cursor = FL_CURSOR_ARROW;
#endif
bool test_mode = false;

static void initialize() {

  Display* d = fl_display;

  if (test_mode) {
    XWindow w = XCreateSimpleWindow(d, RootWindow(d, fl_screen),
                                    100, 100, 200, 300, 10,
                                    BlackPixel(fl_display, 0),
                                    // WhitePixel(fl_display, 0));
                                    0x1234);
    Frame* frame = new Frame(w);
    frame->label("flwm test window");
    XSelectInput(d, w,
                 ExposureMask | StructureNotifyMask |
                 KeyPressMask | KeyReleaseMask | FocusChangeMask |
                 KeymapStateMask |
                 ButtonPressMask | ButtonReleaseMask |
                 EnterWindowMask | LeaveWindowMask /*|PointerMotionMask*/
                 );

    return;
  }

  Fl::add_handler(flwm_event_handler);

  // setting attributes on root window makes me the window manager:
  initializing = 1;
  XSelectInput(d, fl_xid(Root),
	       SubstructureRedirectMask | SubstructureNotifyMask |
	       ColormapChangeMask | PropertyChangeMask |
	       ButtonPressMask | ButtonReleaseMask | 
	       EnterWindowMask | LeaveWindowMask |
	       KeyPressMask | KeyReleaseMask | KeymapStateMask);
#if FL_MAJOR_VERSION>1
  Root->cursor(cursor);
#else
  Root->cursor((Fl_Cursor)cursor, CURSOR_FG_SLOT, CURSOR_BG_SLOT);
#endif
  Fl::visible_focus(0);

#ifdef TITLE_FONT
  Fl::set_font(TITLE_FONT_SLOT, TITLE_FONT);
#endif
#ifdef MENU_FONT
  Fl::set_font(MENU_FONT_SLOT, MENU_FONT);
#endif
#ifdef ACTIVE_COLOR
  Fl::set_color(FL_SELECTION_COLOR, ACTIVE_COLOR<<8);
#endif

  // Gnome crap:
  // First create a window that can be watched to see if wm dies:
  Atom a = XInternAtom(d, "_WIN_SUPPORTING_WM_CHECK", False);
  XWindow win = XCreateSimpleWindow(d, fl_xid(Root), -200, -200, 5, 5, 0, 0, 0);
  CARD32 val = win;
  XChangeProperty(d, fl_xid(Root), a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1);
  XChangeProperty(d, win, a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1);
  // Next send a list of Gnome stuff we understand:
  a = XInternAtom(d, "_WIN_PROTOCOLS", 0);
  Atom list[10]; unsigned int i = 0;
//list[i++] = XInternAtom(d, "_WIN_LAYER", 0);
  list[i++] = _win_state = XInternAtom(d, "_WIN_STATE", 0);
  list[i++] = _win_hints = XInternAtom(d, "_WIN_HINTS", 0);
//list[i++] = XInternAtom(d, "_WIN_APP_STATE", 0);
//list[i++] = XInternAtom(d, "_WIN_EXPANDED_SIZE", 0);
//list[i++] = XInternAtom(d, "_WIN_ICONS", 0);
#if DESKTOPS
  list[i++] = _win_workspace = XInternAtom(d, "_WIN_WORKSPACE", 0);
  list[i++] = _win_workspace_count = XInternAtom(d, "_WIN_WORKSPACE_COUNT", 0);
  list[i++] = _win_workspace_names = XInternAtom(d, "_WIN_WORKSPACE_NAMES", 0);
#endif
//list[i++] = XInternAtom(d, "_WIN_FRAME_LIST", 0);
  XChangeProperty(d, fl_xid(Root), a, XA_ATOM, 32, PropModeReplace, (uchar*)list, i);

  Grab_Hotkeys();

#ifdef SHOW_CLOCK
  Fl::add_timeout(clock_period, flwm_update_clock);
  flwm_clock_alarm_start.sa_handler = &flwm_clock_alarm_on;
  flwm_clock_alarm_stop.sa_handler = &flwm_clock_alarm_off;
  sigaction(SIGALRM, &flwm_clock_alarm_start, NULL);
  sigaction(SIGCONT, &flwm_clock_alarm_stop, NULL);
#endif

  XSync(d, 0);
  initializing = 0;

#if DESKTOPS
  init_desktops();
#endif

  // find all the windows and create a Frame for each:
  unsigned int n;
  XWindow w1, w2, *wins;
  XWindowAttributes attr;
  XQueryTree(d, fl_xid(Root), &w1, &w2, &wins, &n);
  for (i = 0; i < n; ++i) {
    XGetWindowAttributes(d, wins[i], &attr);
    if (attr.override_redirect || !attr.map_state) continue;
    (void)new Frame(wins[i],&attr);
  }
  XFree((void *)wins);
}

////////////////////////////////////////////////////////////////

extern int exit_flag;
extern int max_w_switch;
extern int max_h_switch;

// consume a switch from argv.  Returns number of words eaten, 0 on error:
int arg(int argc, char **argv, int &i) {
  const char *s = argv[i];
  if (s[0] != '-') return 0;
  s++;

  // do single-word switches:
  if (!strcmp(s,"x")) {
    exit_flag = 1;
    i++;
    return 1;
  } else if (!strcmp(s, "test")) {
    test_mode = true;
    i++;
    return 1;
  }

  // do switches with a value:
  const char *v = argv[i+1];
  if (i >= argc-1 || !v)
    return 0;	// all the rest need an argument, so if missing it is an error

  if (!strcmp(s, "cfg")) {
    cfg = v;
  } else if (!strcmp(s, "cbg")) {
    cbg = v;
#if FL_MAJOR_VERSION < 2
  } else if (*s == 'c') {
    cursor = atoi(v);
#endif
  } else if (*s == 'v') {
    int visid = atoi(v);
    fl_open_display();
    XVisualInfo templt; int num;
    templt.visualid = visid;
    fl_visual = XGetVisualInfo(fl_display, VisualIDMask, &templt, &num);
    if (!fl_visual) Fl::fatal("No visual with id %d",visid);
    fl_colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen),
				  fl_visual->visual, AllocNone);
  } else if (*s == 'm') {
    max_w_switch = atoi(v);
    while (*v && *v++ != 'x');
    max_h_switch = atoi(v);
  } else
    return 0; // unrecognized
  // return the fact that we consumed 2 switches:
  i += 2;
  return 2;
}

#if FL_MAJOR_VERSION<2
static void color_setup(Fl_Color slot, const char* arg, ulong value) {
  if (arg) {
    XColor x;
    if (XParseColor(fl_display, fl_colormap, arg, &x))
      value = ((x.red>>8)<<24)|((x.green>>8)<<16)|((x.blue));
  }
  Fl::set_color(slot, value);
}
#endif

int main(int argc, char** argv) {
  program_name = fl_filename_name(argv[0]);
  int i; if (Fl::args(argc, argv, i, arg) < argc) Fl::error(
"options are:\n"
" -d[isplay] host:#.#\tX display & screen to use\n"
" -v[isual] #\t\tvisual to use\n"
" -g[eometry] WxH+X+Y\tlimits windows to this area\n"
" -m[aximum] WxH\t\tsize of maximized windows\n"
" -x\t\t\tmenu says Exit instead of logout\n"
" -bg color\t\tFrame color\n"
" -fg color\t\tLabel color\n"
" -bg2 color\t\tText field color\n"
" -c[ursor] #\t\tCursor number for root\n"
" -cfg color\t\tCursor color\n"
" -cbg color\t\tCursor outline color"
" -test\t\t\tTest the window graphics"
);
#ifndef FL_NORMAL_SIZE // detect new versions of fltk where this is a variable
  FL_NORMAL_SIZE = 12;
#endif
#if FL_MAJOR_VERSION>1
  if (cfg) fl_cursor_fg = fltk::color(cfg);
  if (cbg) fl_cursor_bg = fltk::color(cbg);
#else
  fl_open_display();
  color_setup(CURSOR_FG_SLOT, cfg, CURSOR_FG_COLOR<<8);
  color_setup(CURSOR_BG_SLOT, cbg, CURSOR_BG_COLOR<<8);
  Fl::set_color(FL_SELECTION_COLOR,0,0,128);
#endif
  Fl_Root root;
  Root = &root;
#if FL_MAJOR_VERSION>1
  // show() is not a virtual function in fltk2.0, this fools it:
  fltk::load_theme();
  root.show();
#endif
  Root->show(argc,argv); // fools fltk into using -geometry to set the size
  XSetErrorHandler(xerror_handler);
  initialize();
  return Fl::run();
}
