#include <stdlib.h>

#include <qlayout.h>
#include <qlabel.h>
#include <qcombobox.h>
#include <qtabwidget.h>
#include <qframe.h>
#include <qvgroupbox.h>
#include <qpushbutton.h>
#include <qlistview.h>
#include <qheader.h>
#include <qwhatsthis.h>
#include <qcheckbox.h>
#include <qradiobutton.h>
#include <qbuttongroup.h>
#include <qregexp.h>

#include <kkeydialog.h>
#include <kglobal.h>
#include <kconfig.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kdebug.h>
#include <kapplication.h>

#include "rules.h"
#include "kcmlayout.h"
#include "kcmlayout.moc"
#include "pixmap.h"
#include "kcmmisc.h"

#include <X11/Xlib.h>


static const char* switchModes[] = {
  "Global", "WinClass", "Window"
};

// This class helps to catch state change for CheckListItem

class QCheckListItem1:
    public QCheckListItem
{

private:
    LayoutConfig* lc;

public:
    QCheckListItem1(QListView * parent, const QString & text, Type tt, LayoutConfig* lc_):
	QCheckListItem(parent, text, tt),
	lc(lc_)
	{}
    virtual ~QCheckListItem1()
	{}

protected:
    virtual void stateChange(bool)
	{ lc->itemStateChanged(); }
};

static QString lookupLocalized(const QDict<char> &dict, const QString& text)
{

  QDictIterator<char> it(dict);
  while (it.current())
    {
      if ( i18n(it.current()) == text )
        return it.currentKey();
      ++it;
    }

  return QString::null;
}

void LayoutConfig::itemStateChanged()
{
    addLayoutSelChanged();
    emit changed();
}

LayoutConfig::LayoutConfig(QWidget *parent, const char *name)
  : KCModule(parent, name), rules(0)
{
  QVBoxLayout *main = new QVBoxLayout(this, 0,0);
  QTabWidget *tab = new QTabWidget(this);
  main->addWidget(tab);

  QWidget *layout = new QWidget(this);
  QVBoxLayout *vvbox = new QVBoxLayout(layout, KDialog::marginHint(), KDialog::spacingHint());

  enableCheckbox = new QCheckBox( i18n( "&Enable keyboard layouts" ), layout );
  QString wtstr = i18n("Here you can completely disable this module, for example if you use"
    " other tools for switching keyboard layouts." );
  connect( enableCheckbox, SIGNAL( toggled( bool )), this, SLOT(changed()));
  vvbox->addWidget( enableCheckbox );
  QWhatsThis::add( enableCheckbox, wtstr );

  QGroupBox *grp = new QGroupBox(i18n("Configuration"), layout);
  grpPrimary = grp;
  connect( enableCheckbox, SIGNAL( toggled( bool )), grp, SLOT( setEnabled( bool )));
  vvbox->addWidget(grp);

  QGridLayout *grid = new QGridLayout(grp, 4, 2, 6, 6);
  grid->addRowSpacing(0, grp->fontMetrics().height());

  modelCombo = new QComboBox(grp);
  QLabel *l = new QLabel(modelCombo, i18n("Keyboard &model:"), grp);
  grid->addWidget(l, 1, 0);
  grid->addWidget(modelCombo, 1, 1);
  wtstr = i18n("Here you can choose a keyboard model. This setting is independent of your"
    " keyboard layout and refers to the \"hardware\" model, i.e. the way your keyboard is manufactured."
    " Modern keyboards that come with your computer usually have two extra keys and are referred to as"
    " \"104-key\" models, which is probably what you want if you don't know what kind of keyboard you have.");
  QWhatsThis::add( modelCombo, wtstr );
  QWhatsThis::add( l, wtstr );

  connect(modelCombo, SIGNAL(activated(int)), this, SLOT(changed()));

  layoutCombo = new QComboBox(grp);
  l = new QLabel(layoutCombo, i18n("Pr&imary layout:"), grp);
  grid->addWidget(l, 2, 0);
  grid->addWidget(layoutCombo, 2, 1);
  wtstr = i18n("Here you can choose your primary keyboard layout, i.e. the keyboard layout used as default."
    " The keyboard layout defines \"which keys do what\". For example German keyboards have"
    " the letter 'Y' where US keyboards have the letter 'Z'.");
  QWhatsThis::add( layoutCombo, wtstr );
  QWhatsThis::add( l, wtstr );

  connect(layoutCombo, SIGNAL(activated(int)), this, SLOT(changed()));
  connect(layoutCombo, SIGNAL(activated(int)), this, SLOT(primLayoutChanged()));

  variantCombo = new QComboBox(grp);
  l = new QLabel(variantCombo, i18n("Prim&ary variant:"), grp);
  grid->addWidget(l, 3, 0);
  grid->addWidget(variantCombo, 3, 1);
  wtstr = i18n("Here you can choose a variant of your primary keyboard layout."
    " Layout variants usually represent different key maps for the same language."
    " For example, Ukrainian layout might have four variants:"
    " basic, winkeys (as in Windows), typewriter (as in typewriters)"
    " and phonetic (each Ukrainian letter is placed on a transliterated latin one).");
  QWhatsThis::add( variantCombo, wtstr );
  QWhatsThis::add( l, wtstr );

  connect(variantCombo, SIGNAL(activated(int)), this, SLOT(changed()));
  connect(variantCombo, SIGNAL(activated(int)), this, SLOT(primVariantChanged()));

  grp = new QGroupBox(i18n("Additional Layouts"), layout);
  grpAdditional = grp;
  connect( enableCheckbox, SIGNAL( toggled( bool )), grp, SLOT( setEnabled( bool )));
  vvbox->addWidget(grp);
  QWhatsThis::add( grp, i18n("You can select an arbitrary number of additional keyboard layouts."
    " If one or more additional layouts have been selected, the KDE panel will offer a docked flag."
    " By clicking on this flag you can easily switch between layouts.") );

  modeBtnGroup = new QButtonGroup(3, Horizontal, i18n("Switching Policy"), layout);
  modeBtnGroup->setExclusive(true);

  QRadioButton* radio = new QRadioButton( i18n("&Global"), modeBtnGroup);
  radio = new QRadioButton( i18n("Application"), modeBtnGroup);
  radio = new QRadioButton( i18n("&Window"), modeBtnGroup);
  vvbox->addWidget(modeBtnGroup);
  connect( modeBtnGroup, SIGNAL( clicked( int ) ), this, SLOT(changed()));
  connect( enableCheckbox, SIGNAL( toggled( bool )), modeBtnGroup, SLOT( setEnabled( bool )));


  QVBoxLayout *vbox = new QVBoxLayout(grp, 6,6);
  vbox->addSpacing(grp->fontMetrics().height());

  additional = new QListView(grp);
  vbox->addWidget(additional);
//  additional->header()->hide();
  additional->addColumn(""/*, 22*/);
  additional->addColumn(i18n("Flag")/*, 28*/);
  additional->addColumn(i18n("Layout"));
  additional->addColumn(i18n("Keymap"));
  additional->setAllColumnsShowFocus(true);
  additional->setResizeMode(QListView::LastColumn);

//  connect(additional, SIGNAL(clicked(QListViewItem*)), this, SLOT(changed()));
  connect(additional, SIGNAL(selectionChanged()), this, SLOT(addLayoutSelChanged()));

//WABA (parent was grp)
  QHBoxLayout* hhbox = new QHBoxLayout();
  vbox->addLayout(hhbox);
  addVariantCombo = new QComboBox(grp);
  l = new QLabel(addVariantCombo, i18n("&Variant:"), grp);
  l->setAlignment(AlignRight);
  hhbox->addWidget(l);
  hhbox->addWidget(addVariantCombo);
  wtstr = i18n("Here you can choose a variant of the selected additional keyboard layout."
    " Layout variants usually represent different key maps for the same language."
    " For example, Ukrainian layout might have four variants:"
    " basic, winkeys (as in Windows), typewriter (as in typewriters)"
    " and phonetic (each Ukrainian letter is placed on a transliterated latin one).");
  QWhatsThis::add( addVariantCombo, wtstr );
  QWhatsThis::add( l, wtstr );

  connect(addVariantCombo, SIGNAL(activated(int)), this, SLOT(changed()));
  connect(addVariantCombo, SIGNAL(activated(int)), this, SLOT(addVariantChanged()));


  //Read rules - we _must_ read _before_ creating xkb-options comboboxes
  ruleChanged("xfree86");

  QWidget *options_tab = makeOptionsTab(this);

  tab->addTab(layout, i18n("&Layout"));
  tab->addTab(options_tab, i18n("&Options"));

  load();
}


LayoutConfig::~LayoutConfig()
{
  delete rules;
}


void LayoutConfig::addLayoutSelChanged()
{
    QCheckListItem* chkItem = dynamic_cast<QCheckListItem*>( additional->selectedItem() );

    if( !chkItem )
	return;	//disaster!

    if( !chkItem->isOn() )
    {
      if( addVariantCombo->isEnabled() )
      {
	addVariantCombo->clear();
	addVariantCombo->setEnabled(false);
      }
      return;
    }

    addVariantCombo->setEnabled(true);
    addVariantCombo->clear();

    QString addKbdLayout = lookupLocalized( rules->layouts(), chkItem->text(2) );
    QStringList vars = rules->getVariants(addKbdLayout);

    if( vars.count() == 0 )	// cowardly running away
	return;

    addVariantCombo->insertStringList(vars);

    char* variant = variants[addKbdLayout];
    if( variant )
    {
	addVariantCombo->setCurrentText(variant);
    }
    else
    {
      addVariantCombo->setCurrentItem(0);
      variants.insert(addKbdLayout, addVariantCombo->currentText().latin1());
    }
}

void LayoutConfig::primLayoutChanged()
{
    QString kbdLayout = lookupLocalized( rules->layouts(), layoutCombo->currentText() );
    QStringList vars = rules->getVariants(kbdLayout);
    variantCombo->clear();
    variantCombo->insertStringList(vars);

    char* variant = variants[kbdLayout];
    if( variant )
	variantCombo->setCurrentText(variant);
}

void LayoutConfig::primVariantChanged()
{
    QString primKbdLayout = lookupLocalized( rules->layouts(), layoutCombo->currentText() );
    if( variants.find(primKbdLayout) )
    {
	variants.replace(primKbdLayout, variantCombo->currentText().latin1());

	QListViewItem* item = additional->selectedItem();
	if( item && primKbdLayout == lookupLocalized(rules->layouts(), item->text(2)) )
	    addVariantCombo->setCurrentItem( variantCombo->currentItem() );
    }
    else
	variants.insert(primKbdLayout, variantCombo->currentText().latin1());
}

void LayoutConfig::addVariantChanged()
{
    QString addKbdLayout = lookupLocalized( rules->layouts(), additional->selectedItem()->text(2) );
    if( variants.find(addKbdLayout) ) {
	variants.replace(addKbdLayout, addVariantCombo->currentText().latin1());
	QString primKbdLayout = lookupLocalized( rules->layouts(), layoutCombo->currentText() );
	if( addKbdLayout == primKbdLayout )
	    variantCombo->setCurrentItem( addVariantCombo->currentItem() );
    }
    else
	variants.insert(addKbdLayout, addVariantCombo->currentText().latin1());
}

QWidget* LayoutConfig::makeOptionsTab(QWidget* parent)
{
  QWidget* tab = new QWidget(parent);

  QVBoxLayout* vbox = new QVBoxLayout(tab, 6, 6);

  QGroupBox *grp = new QVGroupBox(i18n("Xkb Options"), tab);
  vbox->add(grp);

  resetOldCheckbox = new QCheckBox( i18n("&Reset old options"), grp);
  connect(resetOldCheckbox, SIGNAL(toggled(bool)), this, SLOT(changed()));

  //Calculate number of options
  numberOfOptions = 0;
  QDictIterator<char> it(rules->options());

// this code for reading variants contributed by Volodymyr M. Lisivka
  //Create comboboxes for all options
  it.toFirst();
  for (int i=0; it.current(); ++it)
  {
    if (!it.currentKey().contains(':'))
    {
      QLabel *label = new QLabel(i18n(it.current())+":", grp);
      QComboBox *combobox = new QComboBox(grp);
      label->setBuddy(combobox);

      optionsComboboxes.insert(i18n(it.currentKey().local8Bit()), combobox);
      connect(combobox, SIGNAL(activated(const QString&)), this, SLOT(changed()));
      combobox->insertItem(QString(""));

      i++;
    }
  }

  //Add content to comboboxes
  it.toFirst();
  for( ; it.current(); ++it)
  {
    QString key = it.currentKey();
    int pos = key.find(':');
    if (pos >= 0)
    {
      QComboBox *combobox = optionsComboboxes[key.left(pos)];
      if (combobox != NULL) {
      // workaroung for mistake in rules file for xkb options in XFree 4.2.0
        QString text(it.current());
        text = text.replace( QRegExp("Cap\\$"), "Caps." );
        combobox->insertItem(i18n(text.latin1()));
      }
    }
  }

  vbox->addStretch();

  return tab;
}

void LayoutConfig::changed()
{
	emit KCModule::changed(true);
}


void LayoutConfig::load()
{
  // open the config file
  KConfig *config = new KConfig("kxkbrc", true);
  config->setGroup("Layout");

  bool use = config->readBoolEntry( "Use", false );

  // find out which rule applies
  QString rule = config->readEntry("Rule", "xfree86");
  //  setCurrent(ruleCombo, rule);

  // update the other files
  ruleChanged(rule);

  // find out about the layout and the model
  QString model = config->readEntry("Model", "pc104");
  QString layout = config->readEntry("Layout", "us");

  // TODO: When no model and no layout are known (1st start), we really
  // should ask the X-server about it's settings!

  QString m_name = rules->models()[model];
  modelCombo->setCurrentText(i18n(m_name.local8Bit()));

  QString l_name = rules->layouts()[layout];
  layoutCombo->setCurrentText(i18n(l_name.local8Bit()));

  QStringList otherLayouts = config->readListEntry("Additional");

  QListViewItemIterator it( additional );
  for ( ; it.current(); ++it )
  {
    if ( otherLayouts.contains(lookupLocalized(rules->layouts(), it.current()->text(2))) )
        dynamic_cast<QCheckListItem*>(it.current())->setOn(true);
    else
        dynamic_cast<QCheckListItem*>(it.current())->setOn(false);
  }

// reading variants
  QStringList vars = config->readListEntry("Variants");

  rules->parseVariants(vars, variants);

  primLayoutChanged();

  bool resetOld = config->readBoolEntry("ResetOldOptions", false);
  resetOldCheckbox->setChecked(resetOld);
  QStringList options = config->readListEntry("Options");

  for (QStringList::Iterator it = options.begin(); it != options.end(); ++it)
    {
      QString option = *it;
      QString optionKey = option.mid(0, option.find(':'));
      QString optionName = rules->options()[option];
      QComboBox *combobox = optionsComboboxes[optionKey];
      if (combobox != NULL)
        combobox->setCurrentText(i18n(optionName.local8Bit()));
    }


  QString swMode = config->readEntry("SwitchMode", "Global");
  modeBtnGroup->setButton(0);

   for(int ii=1; ii<3; ii++)
    if( swMode == switchModes[ii] )
	modeBtnGroup->setButton(ii);

  delete config;

  enableCheckbox->setChecked( use );
  grpPrimary->setEnabled(use);
  grpAdditional->setEnabled(use);
  modeBtnGroup->setEnabled(use);
}

void LayoutConfig::ruleChanged(const QString &rule)
{
  if( rule == m_rule )
    return;

  m_rule = rule;

  QString model, layout;
  if (rules)
    {
      model = lookupLocalized(rules->models(), modelCombo->currentText());
      layout = lookupLocalized(rules->layouts(), layoutCombo->currentText());
// we have to save additional as well
    }

  delete rules;
  rules = new KeyRules(rule);

  QStringList tmp;
  modelCombo->clear();
  QDictIterator<char> it(rules->models());
  while (it.current())
    {
      tmp.append(i18n(it.current()));
      ++it;
    }
  tmp.sort();
  modelCombo->insertStringList(tmp);


  // fill in the additional layouts  -- moved from load() by A. Rysin
  additional->clear();
  QDictIterator<char> it2(rules->layouts());
  while (it2.current())
    {
      QCheckListItem *item = new QCheckListItem1(additional, "", QCheckListItem::CheckBox, this);
      QString addLayout = it2.currentKey();
      item->setPixmap(1, findPixmap(addLayout));
      item->setText(2, i18n(it2.current()) );
      item->setText(3, "(" + addLayout + ")" );
      ++it2;
    }
  additional->setSorting(2);	// from Qt3 QListView sorts by language


  layoutCombo->clear();

// filling layout combo from additional ListView
// because it's easiest way to get it sorted by language
  QListViewItemIterator item( additional );
  for ( ; item.current(); ++item )
    layoutCombo->insertItem(*item.current()->pixmap(1), item.current()->text(2));


  if (!model.isEmpty())
  {
    QString m_name = rules->models()[model];
    modelCombo->setCurrentText(m_name);
  }
  if (!layout.isEmpty())
  {
    QString l_name = rules->layouts()[layout];
    layoutCombo->setCurrentText(l_name);
  }
  primLayoutChanged();
}


void LayoutConfig::save()
{
  KConfig *config = new KConfig("kxkbrc", false);
  config->setGroup("Layout");
  //  config->writeEntry("Rule", ruleCombo->currentText());

  QString layout = lookupLocalized(rules->layouts(), layoutCombo->currentText());
  config->writeEntry("Model", lookupLocalized(rules->models(), modelCombo->currentText()));
  config->writeEntry("Layout", layout );

  config->writeEntry("Encoding", rules->encodings().find( layout ) );

  QString options;
  for (QDictIterator<char> it(rules->options()); it.current(); ++it)
  {
    if (!it.currentKey().contains(':'))
    {
      QComboBox *combobox = optionsComboboxes[it.currentKey()];
      QString selectedString = lookupLocalized(rules->options(), combobox->currentText());
      if (!selectedString.isEmpty())
      {
       if (!options.isEmpty())
         options.append(',');
       options.append(selectedString);
      }
    }
  }
  config->writeEntry("ResetOldOptions", resetOldCheckbox->isChecked());
  config->writeEntry("Options", options);

  QStringList otherLayouts;
  QStringList encodings;
  QListViewItem *item = additional->firstChild();
  while (item)
    {
      QCheckListItem *cli = dynamic_cast<QCheckListItem*>(item);
      if (cli->isOn()) {
	  QString layout = lookupLocalized(rules->layouts(), cli->text(2));
	otherLayouts.append(layout);
	encodings.append(rules->encodings().find( layout ) );
      }
      item = item->nextSibling();
    }
  config->writeEntry("Additional", otherLayouts);
  config->writeEntry("AdditionalEncodings", encodings);

  QStringList varList;
  for(QDictIterator<char> it(variants); it.current(); ++it )
  {
    QString var = it.currentKey();
    if( var != layout && !otherLayouts.contains(var) ) // this layout was deleted
      continue;

    var += "(";
    var += it.current();
    var += ")";
    varList.append( var );
  }
  config->writeEntry("Variants", varList);


  config->writeEntry("Use", enableCheckbox->isChecked());

  int modeId = modeBtnGroup->id(modeBtnGroup->selected());
  if( modeId < 1 || modeId > 2 )
    modeId = 0;

  config->writeEntry("SwitchMode", switchModes[modeId]);

  config->sync();

  delete config;

  kapp->kdeinitExec("kxkb");
}


void LayoutConfig::defaults()
{
  enableCheckbox->setChecked(false);
  ruleChanged("xfree86");

  modelCombo->setCurrentText("pc104");
  layoutCombo->setCurrentText("us");

  QListViewItem *item = additional->firstChild();
  while (item)
    {
      QCheckListItem *cli = dynamic_cast<QCheckListItem*>(item);
      cli->setOn(false);
      item = item->nextSibling();
    }
}

QString LayoutConfig::quickHelp() const
{
  return i18n("<h1>Keyboard Layout & Model</h1> Here you can choose your keyboard layout and model."
    " The 'model' refers to the type of keyboard that is connected to your computer, while the keyboard"
    " layout defines \"which key does what\" and may be different for different countries.<p>"
    " In addition to the 'Primary Layout', which will be used as the default, you can specify additional"
    " layouts, which you can easily switch between using the KDE panel.");
}


extern "C"
{
  KCModule *create_keyboard_layout(QWidget *parent, const char *)
  {
    return new LayoutConfig(parent, "kcmlayout");
  }

  KCModule *create_keyboard(QWidget *parent, const char *)
  {
    return new KeyboardConfig(parent, "kcmlayout");
  }

  void init_keyboard()
  {
    KConfig *config = new KConfig("kcminputrc", true); // Read-only, no globals
    config->setGroup("Keyboard");

    XKeyboardState   kbd;
    XKeyboardControl kbdc;

    XGetKeyboardControl(kapp->getDisplay(), &kbd);
    bool key = config->readBoolEntry("KeyboardRepeating", true);
    kbdc.key_click_percent = config->readNumEntry("ClickVolume", kbd.key_click_percent);
    kbdc.auto_repeat_mode = (key ? AutoRepeatModeOn : AutoRepeatModeOff);
    XChangeKeyboardControl(kapp->getDisplay(),
                           KBKeyClickPercent | KBAutoRepeatMode,
                           &kbdc);
    int numlockState = config->readNumEntry( "NumLock", 2 );
    if( numlockState != 2 )
        numlockx_change_numlock_state( numlockState == 0 );

    delete config;

    config = new KConfig("kxkbrc", true, false);
    config->setGroup("Layout");
    if (config->readBoolEntry("Use", false) == true)
        kapp->startServiceByDesktopName("kxkb");
    delete config;
  }
}

#if 0// do not remove!
// These options are from XFree 4.1.0
 I18N_NOOP("Group Shift/Lock behavior");
 I18N_NOOP("R-Alt switches group while pressed");
 I18N_NOOP("Right Alt key changes group");
 I18N_NOOP("Caps Lock key changes group");
 I18N_NOOP("Menu key changes group");
 I18N_NOOP("Both Shift keys together change group");
 I18N_NOOP("Control+Shift changes group");
 I18N_NOOP("Alt+Control changes group");
 I18N_NOOP("Alt+Shift changes group");
 I18N_NOOP("Control Key Position");
 I18N_NOOP("Make Caps Lock an additional Control");
 I18N_NOOP("Swap Control and Caps Lock");
 I18N_NOOP("Control key at left of 'A'");
 I18N_NOOP("Control key at bottom left");
 I18N_NOOP("Use keyboard LED to show alternative group");
 I18N_NOOP("Num_Lock LED shows alternative group");
 I18N_NOOP("Caps_Lock LED shows alternative group");
 I18N_NOOP("Scroll_Lock LED shows alternative group");

//these seem to be new in XFree86 4.2.0
 I18N_NOOP("Left Win-key switches group while pressed");
 I18N_NOOP("Right Win-key switches group while pressed");
 I18N_NOOP("Both Win-keys switch group while pressed");
 I18N_NOOP("Left Win-key changes group");
 I18N_NOOP("Right Win-key changes group");
 I18N_NOOP("Third level choosers");
 I18N_NOOP("Press Right Control to choose 3rd level");
 I18N_NOOP("Press Menu key to choose 3rd level");
 I18N_NOOP("Press any of Win-keys to choose 3rd level");
 I18N_NOOP("Press Left Win-key to choose 3rd level");
 I18N_NOOP("Press Right Win-key to choose 3rd level");
 I18N_NOOP("CapsLock key behavior");
 I18N_NOOP("uses internal capitalization. Shift cancels Caps.");
 I18N_NOOP("uses internal capitalization. Shift doesn't cancel Cap$");
 I18N_NOOP("acts as Shift with locking. Shift cancels Caps.");
 I18N_NOOP("acts as Shift with locking. Shift doesn't cancel Caps.");
 I18N_NOOP("Alt/Win key behavior");
 I18N_NOOP("Add the standard behavior to Menu key.");
 I18N_NOOP("Alt and Meta on the Alt keys (default).");
 I18N_NOOP("Meta is mapped to the Win-keys.");
 I18N_NOOP("Meta is mapped to the left Win-key.");
 I18N_NOOP("Super is mapped to the Win-keys (default).");
 I18N_NOOP("Hyper is mapped to the Win-keys.");
 I18N_NOOP("Right Alt is Compose");
 I18N_NOOP("Right Win-key is Compose");
 I18N_NOOP("Menu is Compose");
#endif
