//===========================================
//  Lumina-DE source code
//  Copyright (c) 2016, Ken Moore
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include "LInputDevice.h"

//Qt Library includes
#include <QString>
#include <QX11Info>
#include <QDebug>

//XCB Library includes
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xinput.h>
#include <xcb/xproto.h>

#include <LUtils.h>

//===================
//    LInputDevice Class
//===================
// === PUBLIC ===
LInputDevice::LInputDevice(unsigned int id, unsigned int type){
  devID = id;
  devType = type;
  //ATOM_FLOAT = 0; //init this when needed later
  //devName = name;
  getProperties(); //need to populate the name/atom correlations for properties
  readProperties(); //populate the hash with the current values of the properties
}

LInputDevice::~LInputDevice(){

}

unsigned int LInputDevice::devNumber(){
  return devID;
}

bool LInputDevice::isPointer(){
  return (devType==XCB_INPUT_DEVICE_USE_IS_X_POINTER \
	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_POINTER);
}

bool LInputDevice::isKeyboard(){
  return (devType==XCB_INPUT_DEVICE_USE_IS_X_KEYBOARD \
	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_KEYBOARD);
}

bool LInputDevice::isExtension(){
  return (devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_DEVICE \
	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_KEYBOARD \
	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_POINTER);
}

// Property Management
QList<int> LInputDevice::listProperties(){
  return devProps.keys();
}

QString LInputDevice::propertyName(int prop){
  if(devProps.contains(prop)){ return devProps[prop].name; }
  else{ return ""; }
}

QVariant LInputDevice::getPropertyValue(int prop){
  if(devProps.contains(prop)){ return devProps[prop].value; }
  else{ return QVariant(); }
}

bool LInputDevice::setPropertyValue(int prop, QVariant value){
  if(!devProps.contains(prop)){ return false; }
  //Need the float atom for some properties - make sure we have that first
  /*if(ATOM_FLOAT==0){
    xcb_intern_atom_reply_t *ar = xcb_intern_atom_reply(QX11Info::connection(), \
			xcb_intern_atom(QX11Info::connection(), 0, 1, "FLOAT"), NULL);
    if(ar!=0){
      ATOM_FLOAT = ar->atom;
      free(ar);
    }
  }*/
  //Now setup the argument
  bool ok = false;
  QStringList args;
   args << "--set-prop";
   args << QString::number(devID);
   args << QString::number(prop); //prop ID
   args << variantToString(value);
  ok = (0 == LUtils::runCmd("xinput", args) );
  if(ok){ 
    //Need to update the value in the hash as well
    propData dat = devProps[prop];
      dat.value = value;
    devProps.insert(prop, dat);
  }
  return ok;
}

// === PRIVATE ===
void LInputDevice::getProperties(){
  devProps.clear();
  xcb_input_list_device_properties_cookie_t cookie = xcb_input_list_device_properties_unchecked(QX11Info::connection(), devID);
  xcb_input_list_device_properties_reply_t *reply = xcb_input_list_device_properties_reply(QX11Info::connection(), cookie, NULL);
  //Get the atoms
  xcb_atom_t *atoms = xcb_input_list_device_properties_atoms(reply);
  //qDebug() << "Property Response Type:" << reply->response_type; //Always seems to be "1"
  QList<xcb_get_atom_name_cookie_t> cookies;
  for(int i=0; i<reply->num_atoms; i++){  cookies <<  xcb_get_atom_name(QX11Info::connection(), atoms[i]);  }
  for(int i=0; i<reply->num_atoms; i++){
    xcb_get_atom_name_reply_t *nr = xcb_get_atom_name_reply(QX11Info::connection(), cookies[i], NULL);
    propData DATA;
      DATA.name = QString::fromUtf8( xcb_get_atom_name_name(nr), xcb_get_atom_name_name_length(nr) );
      DATA.atom = atoms[i];
      DATA.id = (int)(atoms[i]);
    devProps.insert(DATA.id,DATA);
    ::free(nr);
  }
  //Done with data structure
  ::free(reply);
}

void LInputDevice::readProperties(){
  QList<int> props = devProps.keys();
  //XINPUT UTILITY USAGE (alternative to XCB which actually works right now)
  QStringList info = LUtils::getCmdOutput("xinput list-props "+QString::number(devID));
  for(int i=0; i<props.length(); i++){
    propData PROP = devProps[props[i]];
    QStringList filter = info.filter(" ("+QString::number(PROP.id)+"):");
    if(filter.length()==1){
      QString val = filter.first().section("):",1,-1).simplified();
      //Now figure out what type of value this is and save it into the QVariant
      QVariant variant;
      if(val.split(", ").length()>1){
        //some kind of array
        QList<QVariant> list;
        QStringList valList = val.split(", ");
        for(int j=0; j<valList.length(); j++){ list << valueToVariant(valList[j]); }
        variant = QVariant(list);
      }else{
	variant = valueToVariant(val);
      }
      PROP.value = variant;
    }
    devProps.insert(props[i], PROP);
  }

//XCB Code (non-functional - issue with library itself? 12/6/16 - Ken Moore)
  /*QVariant result;
  if(!devProps.contains(prop)){qDebug() << "Invalid Property"; return result; }
  //Now generate the property request
  xcb_input_get_device_property_cookie_t cookie = xcb_input_get_device_property_unchecked( QX11Info::connection(), devProps.value(prop).atom, \
		XCB_ATOM_ATOM, 0, 1000, devID, 0);
  xcb_input_get_device_property_reply_t *reply = xcb_input_get_device_property_reply(QX11Info::connection(), cookie, NULL);
  if(reply==0){ qDebug() << "Could not get reply!"; return result; }
  //Now read off the value of the property
  if(ATOM_FLOAT==0){
    xcb_intern_atom_reply_t *ar = xcb_intern_atom_reply(QX11Info::connection(), \
			xcb_intern_atom(QX11Info::connection(), 0, 1, "FLOAT"), NULL);
    if(ar!=0){
      ATOM_FLOAT = ar->atom;
      free(ar);
    }
  }
  //Turn the reply into the proper items array (depends on format of the return data)
  xcb_input_get_device_property_items_t items;
  qDebug() <<QByteArray::fromRawData( (char*)(xcb_input_get_device_property_items(reply) ) , reply->num_items);
  void *buffer = xcb_input_get_device_property_items(reply);
  xcb_input_get_device_property_items_serialize( &buffer, reply->num_items, reply->format, &items);

  //if(reply->num_items > 0){
  //qDebug() << "Format:" << reply->format << "Length:" << length;
  //qDebug() << "Response Type:" << reply->response_type << "Pads:" << reply->pad0 << reply->pad1;
    switch(reply->type){
	case XCB_ATOM_INTEGER:
	  //qDebug() << "Got Integer";

	  break;
	case XCB_ATOM_CARDINAL:
	  //qDebug() << "Got Cardinal";

	  break;
	case XCB_ATOM_STRING:
	  qDebug() << "Got String:";
	  if(reply->format==8){
	    result.setValue( QByteArray::fromRawData( (char*) xcb_input_get_device_property_items_data_8(&items), sizeof(xcb_input_get_device_property_items_data_8(&items))/sizeof(char)) );
	  }
	  break;
	case XCB_ATOM_ATOM:
	  //qDebug() << "Got Atom";

	  break;
	default:
	    qDebug() << "Other Type:" << reply->type;
    }
  //}
  free(reply); //done with this structure
  return result;*/
}

QVariant LInputDevice::valueToVariant(QString value){
  //Read through the string and see what type of value it is
  if(value.count("\"")==2){
    //String value or atom
    if(value.endsWith(")")){
      //ATOM (name string +(atomID))
      return QVariant(value); //don't strip off the atom number -- keep that within the parenthesis
    }else{
      //String
      value = value.section("\"",1,-2); //everything between the quotes
      return QVariant(value);
    }
  }else if(value.contains(".")){
    //float/double number
    return QVariant( value.toDouble() );
  }else{
    //integer or boolian (no way to tell right now - assume all int)
    bool ok = false;
    int intval = value.toInt(&ok);
    if(ok){ return QVariant(intval); }
  }
  return QVariant();
}

QString LInputDevice::variantToString(QVariant value){
  if( value.canConvert< QList<QVariant> >() ){
    //List of variants
    QStringList out;
    QList<QVariant> list = value.toList();
    for(int i=0; i<list.length(); i++){ out << variantToString(list[i]); }
    return out.join(", ");
  }else{
    //Single value
    if(value.canConvert<double>() ){
      return QString::number(value.toDouble());
    }else if(value.canConvert<int>() ){
     return QString::number(value.toInt());
    }else if( value.canConvert<QString>() ){
      //See if this is an atom first
      QString val = value.toString();
      if(val.contains("(")){ val = val.section("(",1,-1).section(")",0,0); }
      return val;
    }
  }
  return ""; //nothing to return
}

//======================
//  LInput Static Functions
//======================
QList<LInputDevice*> LInput::listDevices(){
  QList<LInputDevice*> devices;
  xcb_input_list_input_devices_cookie_t cookie = xcb_input_list_input_devices_unchecked(QX11Info::connection());
  xcb_input_list_input_devices_reply_t *reply = xcb_input_list_input_devices_reply(QX11Info::connection(), cookie, NULL);
  if(reply==0){ return devices; } //error - nothing returned
  //Use the iterator for going through the reply
  //qDebug() << "Create iterator";
  xcb_input_device_info_iterator_t iter = xcb_input_list_input_devices_devices_iterator(reply);
  //xcb_str_iterator_t nameiter = xcb_input_list_input_devices_names_iterator(reply);

  //Now step through the reply
  while(iter.data != 0 ){
    devices << new LInputDevice(iter.data->device_id, iter.data->device_use);
    //qDebug() << "Found Input Device:" << iter.data->device_id;
    //qDebug() << "  - num_class_info:" << iter.data->num_class_info;
    if(iter.rem>0){ xcb_input_device_info_next(&iter); }
    else{ break; }
  }
  //Free the reply (done with it)
  ::free(reply);
  //return the information
  return devices;
}