/*
 *  Login.app
 * 
 *  Copyright (c) 1997-2000 Per Liden
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xmu/WinUtil.h>
#include <X11/extensions/Xdbe.h>
#include <wraster.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include "preferences.h"
#include "util.h"
#include "graphics.h"
#include "resources.h"
#include "switchuser.h"
#include "config.h"


typedef enum {
   Wait,
   Login,
   Fail,
   Console,
   Suspend,
   Restart,
   PowerOff,
   Exit
} Action;


/* global data */
Display*            dpy = 0;
Window              root = 0;

static Window       panel = 0;
static Window       userField = 0;
static Window       passwordField = 0;
static Window       powerButton = 0;
static Window       restartButton = 0;
static Preferences* preferences = 0;
static Resources*   resources = 0;

static XdbeBackBuffer userFieldBuffer, passwordFieldBuffer;
static XdbeSwapInfo userFieldSwap, passwordFieldSwap;


#define             INPUT_MAXLENGTH 256
static char         username[INPUT_MAXLENGTH];
static char         hiddenPassword[INPUT_MAXLENGTH];
static char         password[INPUT_MAXLENGTH];
static Window       activeField;

/* forward declarations */
static void updateInputField(Window field);
static void resetInputFields();

/*
BOOL runAlertPanel(char* message) {
    int    x;
    int    y;
    int    width;
    int    height;
    BOOL   result;
    Window win;
    XEvent event;

    result = NO;

    width = resources->panelImage->width;
    height = resources->panelImage->height;
    x = (XWidthOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - width + preferences->panelPosition[0]) / 2;
    y = (XHeightOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - height + preferences->panelPosition[1]) / 2;

    win = XCreateSimpleWindow(dpy, root, x, y, width, height, 0, 0, 0);
    XSelectInput(dpy, win, KeyPressMask);

    while(1) {
	Window buttonDown = 0;
	BOOL   inside = NO;
	Action action = Wait;
	
	XNextEvent(dpy, &event);
	
	switch (event.type) {
	case ButtonPress:
	    if (event.xbutton.button == Button1) {
		if (event.xbutton.window == restartButton || event.xbutton.window == powerButton) {
		    buttonDown = event.xbutton.window;
		    inside = YES;
		    if (buttonDown == restartButton) {
			setWindowImage(restartButton, resources->restartButtonDownImage);
		    } else if (buttonDown == powerButton) {
			setWindowImage(powerButton, resources->powerButtonDownImage);
		    }	
		}
	    }
	    break;
	    
	case ButtonRelease:
	    if (event.xbutton.button == Button1) {
		if (buttonDown == event.xbutton.window && inside == YES) {
		    if (buttonDown == restartButton) {
			setWindowImage(restartButton, resources->restartButtonUpImage);
			action = Restart;
		    } else if (buttonDown == powerButton) {
			setWindowImage(powerButton, resources->powerButtonUpImage);
			action = PowerOff;
		    }
		}
		buttonDown = 0;
		inside = NO;
	    }
	    break;
	    
	case EnterNotify:
	    if (buttonDown == event.xcrossing.window) {
		if (buttonDown == restartButton) {
		    setWindowImage(restartButton, resources->restartButtonDownImage);
		} else if (buttonDown == powerButton) {
		    setWindowImage(powerButton, resources->powerButtonDownImage);
		}
		inside = YES;
	    }
	    break;
	    
	case LeaveNotify:
	    if (buttonDown == event.xcrossing.window) {
		if (buttonDown == restartButton) {
		    setWindowImage(restartButton, resources->restartButtonUpImage);
		} else if (buttonDown == powerButton) {
		    setWindowImage(powerButton, resources->powerButtonUpImage);
		}
		inside = NO;
	    }
	    break;
	}
    }
    
    return result;
}
*/

static void createPanel(BOOL showWelcomeMsg) {
   int x;
   int y;
   int width;
   int height;

   /* Create panel window */
   width = resources->panelImage->width;
   height = resources->panelImage->height;
   x = (XWidthOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - width + preferences->panelPosition[0]) / 2;
   y = (XHeightOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - height + preferences->panelPosition[1]) / 2;

   panel = XCreateSimpleWindow(dpy, root, x, y, width, height, 0, 0, 0);
   XSelectInput(dpy, panel, KeyPressMask);

   /* Create input fields */
   userField = XCreateSimpleWindow(dpy, panel, preferences->inputUserPosition[0], 
                                   preferences->inputUserPosition[1], 10, 10, 0, 0, 0);
   setWindowImage(userField, resources->inputUserImage);

   passwordField = XCreateSimpleWindow(dpy, panel, preferences->inputPasswordPosition[0], 
                                       preferences->inputPasswordPosition[1], 10, 10, 0, 0, 0);
   setWindowImage(passwordField, resources->inputPasswordImage);

   /* Allocate double buffers */
   userFieldBuffer = XdbeAllocateBackBufferName(dpy, userField, XdbeBackground);
   userFieldSwap.swap_window = userField;
   userFieldSwap.swap_action = XdbeBackground;
   passwordFieldBuffer = XdbeAllocateBackBufferName(dpy, passwordField, XdbeBackground);
   passwordFieldSwap.swap_window = passwordField;
   passwordFieldSwap.swap_action = XdbeBackground;

   /* Create buttons */
   if (preferences->disableUserShutdown == NO) {
      restartButton = XCreateSimpleWindow(dpy, panel, preferences->restartButtonPosition[0], 
                                          preferences->restartButtonPosition[1], 10, 10, 0, 0, 0);
      XSelectInput(dpy, restartButton, ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask);
      setWindowImage(restartButton, resources->restartButtonUpImage);

      powerButton = XCreateSimpleWindow(dpy, panel, preferences->powerButtonPosition[0], 
                                        preferences->powerButtonPosition[1], 10, 10, 0, 0, 0);
      XSelectInput(dpy, powerButton, ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask);
      setWindowImage(powerButton, resources->powerButtonUpImage);
   }

   if (showWelcomeMsg == YES && preferences->disableWelcomeMessage == NO) {
      setWindowImage(panel, resources->panelImage);
   } else {
      setCombinedWindowImage(panel, resources->panelImage, resources->fieldLabelsImage);
   }

   XMapRaised(dpy, panel); 

   /* Grab keyboard */
   XGrabKeyboard(dpy, panel, False, GrabModeAsync, GrabModeAsync, CurrentTime);

   XSync(dpy, False);

   /* Draw welcome message */
   if (showWelcomeMsg == YES && preferences->disableWelcomeMessage == NO) {
      drawText(panel, resources->messageFont, preferences->messageJustification, 
               preferences->messagePosition[0], preferences->messagePosition[1],
               resources->messageColor, preferences->welcomeMessage);

      XSync(dpy, False);
      sleep(2);
      setCombinedWindowImage(panel, resources->panelImage, resources->fieldLabelsImage);
   }

   /* Show input fields */
   XMapRaised(dpy, userField);
   XMapRaised(dpy, passwordField);

   /* Show buttons */
   if (preferences->disableUserShutdown == NO) {
      XMapWindow(dpy, restartButton);
      XMapWindow(dpy, powerButton);
   }

   /* Show hostname */
   if (preferences->disableHostname == NO) {
      drawText(panel, resources->hostnameFont, preferences->hostnameJustification, 
               preferences->hostnamePosition[0], preferences->hostnamePosition[1],
               resources->hostnameColor, resources->hostname);
   }
   XSync(dpy, False);

   resetInputFields();
}

static void shakePanel() {
    int i, j, x, y;
    int width, height;

   if (preferences->disableAnimations == NO) {
      width = resources->panelImage->width;
      height = resources->panelImage->height;
      x = (XWidthOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - width + preferences->panelPosition[0]) / 2;
      y = (XHeightOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - height + preferences->panelPosition[1]) / 2;

      for(i = 0; i < 3; i++) {
         for(j = 3; j <= 21; j += 3) {
            XMoveWindow(dpy, panel, x + j, y);
            XFlush(dpy);
            usleep(10);
         }
         for(j = 21; j >= -21; j -= 3) {
            XMoveWindow(dpy, panel, x + j, y);
            XFlush(dpy);
            usleep(10);
         }
         for(j = -18; j <= 0; j += 3) {
            XMoveWindow(dpy, panel, x + j, y);
            XFlush(dpy);
            usleep(10);
         }
         /*XFlush(dpy);*/
      }
   }
}

static void destroyPanel(BOOL shrink) {
   RImage* image;
   Pixmap  shrinkPixmap;
   int     x, y;
   int     width, height;

   if (shrink == YES && preferences->disableAnimations == NO) {
      width = resources->panelImage->width;
      height = resources->panelImage->height;
      x = (XWidthOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - width + preferences->panelPosition[0]) / 2;
      y = (XHeightOfScreen(ScreenOfDisplay(dpy, DefaultScreen(dpy))) - height + preferences->panelPosition[1]) / 2;
	
      image = RCreateImageFromDrawable(ctx, panel, 0);
      RConvertImage(ctx, image, &shrinkPixmap);
      RDestroyImage(image);
	
      XSetWindowBackgroundPixmap(dpy, panel, shrinkPixmap);
      XUnmapSubwindows(dpy, panel);
	
      for(; width > 0; width -= 20, x += 10) {
         XMoveResizeWindow(dpy, panel, x, y, width, height);
         XCopyArea(dpy, shrinkPixmap, shrinkPixmap, ctx->copy_gc, 10, 0, width, height, 0, 0);
         XSetWindowBackgroundPixmap(dpy, panel, shrinkPixmap);
         XSync(dpy, False);
      }
	
      XFreePixmap(dpy, shrinkPixmap);    
   } else {
      XUnmapSubwindows(dpy, panel);
   }
    
   XUngrabKeyboard(dpy, CurrentTime);
   XUnmapWindow(dpy, panel);

   if (preferences->disableUserShutdown == NO) {
      XDestroyWindow(dpy, restartButton);
      XDestroyWindow(dpy, powerButton);
   }
   XDestroyWindow(dpy, userField);
   XDestroyWindow(dpy, passwordField);
   XDestroyWindow(dpy, panel);
   XFlush(dpy);
}

static void initRootWindow() {
   if (strcasecmp(preferences->backgroundColor, "None")) {
      XSetWindowBackground(dpy, root, resources->backgroundColor);
   }
   XClearWindow(dpy, root);
}

static void resetInputFields() {
   strcpy(username, "");
   strcpy(password, "");
   strcpy(hiddenPassword, "");
   XClearWindow(dpy, userField);
   XClearWindow(dpy, passwordField);
   activeField = userField;
   updateInputField(userField);
}

static void updateInputField(Window field) {
   GC           gc;
   char*        text;
   int          xText;
   int          xCursor;
   int          yCursor1;
   int          yCursor2;
   int          textWidth;
   int          justification;
   unsigned int width;
   unsigned int height;
   Window       dummyRoot;
   int          dummyInt;
   unsigned int dummyUInt;
   XdbeBackBuffer backbuffer;
   XdbeSwapInfo   swapinfo;

   if (field == userField) {
      text = username;
      backbuffer = userFieldBuffer;
      swapinfo = userFieldSwap;
   } else { /* field == passwordField */
      text = hiddenPassword;
      backbuffer = passwordFieldBuffer;
      swapinfo = passwordFieldSwap;
   }

   XGetGeometry(dpy, field, &dummyRoot, &dummyInt, &dummyInt, &width, &height, &dummyUInt, &dummyUInt);

   textWidth = XTextWidth(resources->inputFont, text, strlen(text));
   if (textWidth + 5 > width) {
      justification = JUSTIFICATION_RIGHT;
      xText = width - 5;
      xCursor = width - 5;
   } else {
      justification = JUSTIFICATION_LEFT;
      xText = 2;
      xCursor = textWidth + 3;
   }

   XdbeBeginIdiom(dpy);
   
   drawText(backbuffer, resources->inputFont, justification,
            xText, height / 2 + height / 4 + 1, resources->inputFontColor, text);

   if (field == activeField) {	
      yCursor1 = (height / 2) - (preferences->inputCursorHeight / 2);
      yCursor2 = (height / 2) + (preferences->inputCursorHeight / 2);
	
      gc = XCreateGC(dpy, backbuffer, 0, 0);
      XSetForeground(dpy, gc, BlackPixel(dpy, DefaultScreen(dpy)));
      XDrawLine(dpy, backbuffer, gc, xCursor, yCursor1, xCursor, yCursor2);
      XDrawLine(dpy, backbuffer, gc, xCursor + 1, yCursor1, xCursor + 1, yCursor2);
      XFreeGC(dpy, gc);
   }

   XdbeSwapBuffers(dpy, &swapinfo, 1);
   XdbeEndIdiom(dpy);
}

static Action getCommand(char* text) {
   if(!strcmp(text, "console"))
      return Console;
   else if(!strcmp(text, "halt"))
      return PowerOff;
   else if(!strcmp(text, "reboot"))
      return Restart;
   else if(!strcmp(text, "suspend"))
      return Suspend;
   else if(!strcmp(text, "exit"))
      return Exit;
   else
      return Wait;
}

Action handleKeyPress(XEvent* event) {
   static Window  lastActiveField;
   char           ascii;    
   KeySym         keysym;
   XComposeStatus compstatus;
   int            length;
   Action         action;
   Action         command;

   XLookupString(&event->xkey, &ascii, 1, &keysym, &compstatus);

   lastActiveField = activeField;
   action = Wait;

   /* Enter */
   if (keysym == XK_Return || keysym == XK_Tab) {
      if (activeField == userField) {
         if (strcmp(username, "")) {
            command = getCommand(username);
	    
            if (command == Suspend || command == Console || command == Exit) {
               action = command;
            } else {
               action = Wait;
               activeField = passwordField;
            }
         }
      } else {
         if (keysym == XK_Tab) {
            activeField = userField;
            action = Wait;
         } else {
            command = getCommand(username);

            if (command == PowerOff || command == Restart) {
               if (isPasswordCorrect("root", password) == YES) {
                  action = command;
               } else {
                  action = Fail;
               }		    
            } else {
               if (isPasswordCorrect(username, password) == YES) {
                  action = Login;
               } else {
                  action = Fail;
               }
            }
         }
      }
   }
   /* Delete/Backspace */
   else if (keysym == XK_Delete || keysym == XK_BackSpace) {
      if (activeField == userField) {
         length = strlen(username) - 1;
         username[length] = '\0';
      } else {
         length = strlen(password) - 1;
         password[length] = '\0';
         hiddenPassword[length] = '\0';
      }

      action = Wait;
   }
   /* Normal key */
   else if (isprint(ascii) && (keysym < XK_Shift_L || keysym > XK_Hyper_R)) {
      char buffer[2];
      buffer[0] = ascii;
      buffer[1] = '\0';

      action = Wait;

      if (activeField == userField) {
         if (strlen(username) < INPUT_MAXLENGTH - 1) {
            strcat(username, buffer);
         } else {
            return action;
         }
      } else {
         if (strlen(password) < INPUT_MAXLENGTH - 1) {
            strcat(password, buffer);
            buffer[0] = '*';
            strcat(hiddenPassword, buffer);
         } else {
            return action;
         }
      }
   }

   updateInputField(activeField);

   if (lastActiveField != activeField) {
      updateInputField(lastActiveField);
   }

   if (action == Fail) {
      activeField = userField;
   }

   return action;
}

static Action eventHandler(XEvent* event) {
   static Window buttonDown = 0;
   static BOOL   inside = NO;
   Action        action = Wait;

   switch (event->type) {
   case ButtonPress:
      if (event->xbutton.button == Button1) {
         if (event->xbutton.window == restartButton || event->xbutton.window == powerButton) {
            buttonDown = event->xbutton.window;
            inside = YES;
            if (buttonDown == restartButton) {
               setWindowImage(restartButton, resources->restartButtonDownImage);
            } else if (buttonDown == powerButton) {
               setWindowImage(powerButton, resources->powerButtonDownImage);
            }	
         }
      }
      break;

   case ButtonRelease:
      if (event->xbutton.button == Button1) {
         if (buttonDown == event->xbutton.window && inside == YES) {
            if (buttonDown == restartButton) {
               setWindowImage(restartButton, resources->restartButtonUpImage);
               action = Restart;
            } else if (buttonDown == powerButton) {
               setWindowImage(powerButton, resources->powerButtonUpImage);
               action = PowerOff;
            }
         }
         buttonDown = 0;
         inside = NO;
      }
      break;

   case EnterNotify:
      if (buttonDown == event->xcrossing.window) {
         if (buttonDown == restartButton) {
            setWindowImage(restartButton, resources->restartButtonDownImage);
         } else if (buttonDown == powerButton) {
            setWindowImage(powerButton, resources->powerButtonDownImage);
         }
         inside = YES;
      }
      break;

   case LeaveNotify:
      if (buttonDown == event->xcrossing.window) {
         if (buttonDown == restartButton) {
            setWindowImage(restartButton, resources->restartButtonUpImage);
         } else if (buttonDown == powerButton) {
            setWindowImage(powerButton, resources->powerButtonUpImage);
         }
         inside = NO;
      }
      break;

   case KeyPress:
      action = handleKeyPress(event);
      break;

   default:
      action = Wait;
      break;
   }

   return action;
}

static int catchXErrors(Display* dpy, XErrorEvent* event) {
   return 0;
}

static void closeAllClients(BOOL top) {
   Window            dummywindow;
   Window*           children;
   unsigned int      nchildren;
   unsigned int      i;
   XWindowAttributes attr;

   XSync(dpy, 0);
   XSetErrorHandler(catchXErrors);

   nchildren = 0;
   XQueryTree(dpy, root, &dummywindow, &dummywindow, &children, &nchildren);
   if (top == NO) {
      for (i = 0; i < nchildren; i++) {
         if (XGetWindowAttributes(dpy, children[i], &attr) && (attr.map_state == IsViewable)) {
            children[i] = XmuClientWindow(dpy, children[i]);
         } else {
            children[i] = 0;
         }
      }
   }

   for (i = 0; i < nchildren; i++) {
      if (children[i]) {
         XKillClient(dpy, children[i]);
      }
   }
   XFree((char *)children);
    
   XSync(dpy, 0);
   XSetErrorHandler(NULL);    
}

void startPanel(char* appPath, Preferences* pref) {
   XEvent event;
   char*  display = ":0.0";
   pid_t  pid = -1;
   pid_t  wpid = -1;

   preferences = pref;

   if (!(dpy = XOpenDisplay(display))) {
      printError("could not open display %s", display);
      exit(0);
   }

   root = RootWindow(dpy, DefaultScreen(dpy));

   if (initGraphicsContext() == NO) {
      exit(0);
   }

   if (!(resources = allocResources(dpy, appPath, preferences))) {
      exit(0);
   }

   initRootWindow();
   executeScript(appPath, STARTUP_SCRIPT);
   createPanel(YES);
    
   while(1) {
      XNextEvent(dpy, &event);
      switch (eventHandler(&event)) {
      case Login:
         destroyPanel(YES);

         pid = fork();
         switch (pid) {
         case 0:
            setEnvironment(username);
            executeScript(appPath, LOGIN_ROOT_SCRIPT);
            switchUser(username, appPath, LOGIN_USER_SCRIPT);

         case -1:
            printError("fork failed, could not execute login scripts");
            break;

         default:
            /* Wait until user is logging out (login process terminates) */
            while (wpid != pid) {
               wpid = wait(NULL);
            }

            /* Close all clients */
            closeAllClients(NO);
            closeAllClients(YES);
		
            /* Send HUP signal to clientgroup */
            killpg(pid, SIGHUP);
		
            /* Send TERM signal to clientgroup, if error send KILL */
            if (killpg(pid, SIGTERM)) {
               killpg(pid, SIGKILL);
            }
         }

         wpid = -1;
         pid = fork();
         switch (pid) {
         case 0:
            setEnvironment(username);
            executeScript(appPath, LOGOUT_ROOT_SCRIPT);
            exit(0);
            break;

         case -1:
            printError("fork failed, could not execute logout script");
            break;

         default:
            while (wpid != pid) {
               wpid = wait(NULL);
            }
         }

         initRootWindow();
         executeScript(appPath, STARTUP_SCRIPT);
         createPanel(NO);
         break;

      case Console:
         destroyPanel(YES);
         // execute console command
         createPanel(NO);
         break;

      case Fail:
         resetInputFields();
         shakePanel();
         break;
	  
      case Restart:
         XUnmapWindow(dpy, userField);
         XUnmapWindow(dpy, passwordField);
         setWindowImage(panel, resources->panelImage);
         drawText(panel, resources->messageFont, preferences->messageJustification, 
                  preferences->messagePosition[0], preferences->messagePosition[1],
                  resources->messageColor, preferences->restartMessage);
         XSync(dpy, False);
         sleep(2);
         executeScript(appPath, RESTART_SCRIPT);
         sleep(60);
         exit(0);
         break;

      case PowerOff:
         XUnmapWindow(dpy, userField);
         XUnmapWindow(dpy, passwordField);
         setWindowImage(panel, resources->panelImage);
         drawText(panel, resources->messageFont, preferences->messageJustification, 
                  preferences->messagePosition[0], preferences->messagePosition[1],
                  resources->messageColor, preferences->powerOffMessage);
         XSync(dpy, False);
         sleep(2);
         executeScript(appPath, POWEROFF_SCRIPT);
         sleep(60);
         exit(0);
         break;
	  
      case Suspend:
         resetInputFields();
         executeScript(appPath, SUSPEND_SCRIPT);
         break;

      case Exit:
         destroyPanel(NO);
         exit(0);
         break;

      case Wait:
      default:
         break;
      }
   }
}

