#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#include <errno.h>

#include <qwindowdefs.h>
#include <qglobal.h>
#include <qfile.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kprocess.h>

#include <X11/Xatom.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBfile.h>
#include <X11/extensions/XKBgeom.h>
#include <X11/extensions/XKM.h>

#include "extension.h"

XKBExtension::XKBExtension(Display *d)
{
  // determine display to use
  if (!d)
    d = qt_xdisplay();
  m_dpy = d;

  qt_input_encoding = XInternAtom( d, "_QT_INPUT_ENCODING", false);

    // Verify the Xlib has matching XKB extension.

    int major = XkbMajorVersion;
    int minor = XkbMinorVersion;
    if (!XkbLibraryVersion(&major, &minor))
    {
        kdError() << "Xlib XKB extension " << major << '.' << minor <<
            " != " << XkbMajorVersion << '.' << XkbMinorVersion << endl;
        return;
    }

    // Verify the X server has matching XKB extension.

    int opcode_rtrn;
    int error_rtrn;
    if (!XkbQueryExtension(qt_xdisplay(), &opcode_rtrn, &m_xkb_opcode, &error_rtrn,
                         &major, &minor))
    {
        kdError() << "X server XKB extension " << major << '.' << minor <<
            " != " << XkbMajorVersion << '.' << XkbMinorVersion << endl;
        return;
    }
    
    // Do it, or face horrible memory corrupting bugs
    ::XkbInitAtoms(NULL);
}

XKBExtension::~XKBExtension()
{
    // clear the property. Makes Qt use the locale again for input mapping
    Atom type;
    int format;
    unsigned long  nitems, after = 1;
    unsigned char * data;
    XGetWindowProperty( m_dpy, qt_xrootwin(), qt_input_encoding, 0, 1024,
				true, XA_STRING, &type, &format, &nitems,
				&after,  &data );
    if( data )
	delete data;
}

void XKBExtension::setXkbOptions(const QString& options, bool resetOld)
{
  if (options.isEmpty())
    return;

  QString exe = KGlobal::dirs()->findExe("setxkbmap");
  if (exe.isEmpty())
    return;

  KProcess p;
  p << exe;
  if( resetOld )
    p << "-option";
  p << "-option" << options;
  
  p.start(KProcess::Block);
}

void XKBExtension::setLayout(const QString& rule, const QString& model, 
		const QString& layout, const char* variant, const QString &encoding, unsigned int group)
{
  if (rule.isEmpty() || model.isEmpty() || layout.isEmpty())
    return;

  QString exe = KGlobal::dirs()->findExe("setxkbmap");
  if (exe.isEmpty())
    return;

  KProcess p;
  p << exe;
  p << "-rules" << rule;
  p << "-model" << model;
  p << "-layout" << layout;
  if( variant && *variant )
    p << "-variant" << variant;

  if (p.start(KProcess::Block) && p.normalExit() && (p.exitStatus() == 0))
  {
    // The following is done only if the operation succeded.
    XkbLockGroup( m_dpy, XkbUseCoreKbd, group );
  }
}

void XKBExtension::setGroup(int group)
{
  XkbLockGroup( m_dpy, XkbUseCoreKbd, group );
}

int XKBExtension::getGroup()
{
  XkbStateRec xkbState;
  XkbGetState( m_dpy, XkbUseCoreKbd, &xkbState);
  return xkbState.group;
}

// Get the current layout in its binary compiled form.
// and write it to the file specified by 'fileName'
bool XKBExtension::getCompiledLayout(const QString &fileName)
{
  XkbFileInfo result;
  memset(&result, 0, sizeof(result));
  result.type = XkmKeymapFile;
  XkbReadFromServer(m_dpy, XkbAllMapComponentsMask, XkbAllMapComponentsMask, &result);

  FILE *output = fopen(QFile::encodeName(fileName), "w");
  if (!output)
  {
    XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True);
    return false;
  }

  XkbWriteXKMFile(output, &result);

  fclose(output);
  XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True);
  return true;
}

// Sets the current layout to the given binary compiled layout
// from the file specified by 'fileName'
bool XKBExtension::setCompiledLayout(const QString &fileName)
{
  FILE *input = fopen(QFile::encodeName(fileName), "r");
  if (!input)
  {
    kdWarning() << "Unable to open " << fileName << ": " << strerror(errno) << endl;
    return false;
  }

  XkbFileInfo result;
  memset(&result, 0, sizeof(result));
  if ((result.xkb = XkbAllocKeyboard())==NULL)
  {
     kdWarning() << "Unable to allocate memory for keyboard description." << endl;
     fclose(input);
     return false;
  }
  unsigned retVal = XkmReadFile(input, 0, XkmKeymapLegal, &result);
  if (retVal == XkmKeymapLegal)
  {
    // this means reading the Xkm didn't manage to read any section
    kdWarning() << "Unable to load map from file." << endl;
    XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True);
    fclose(input);
    return false;
  }

  fclose(input);

  if (XkbChangeKbdDisplay(m_dpy, &result) == Success)
  {
    if (!XkbWriteToServer(&result))
    {
      kdWarning() << "Unable to write the keyboard layout to X display." << endl;
    }
  }
  else
  {
    kdWarning() << "Unable prepare the keyboard layout for X display." << endl;
  }
  
  XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True);
  return true;
}
