//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// 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 3 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

//#define USE_XTEST

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


#ifdef USE_XTEST
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
#endif

// some of X11/XF86keysym.h does not contains this key
#ifndef XF86XK_AudioForward
#define XF86XK_AudioForward   0x1008FF97
#endif

#ifndef XF86XK_MonBrightnessUp
#define XF86XK_MonBrightnessUp   0x1008FF02
#endif

#ifndef XF86XK_MonBrightnessDown
#define XF86XK_MonBrightnessDown 0x1008FF03
#endif

#include "common.h"
#include "utils.h"

extern char tmp[MAXMAXLEN];

#ifdef USE_XTEST
static Display* disp = NULL;
#endif

static int modifierCtrl = 0;
static int modifierAlt  = 0;

void freeDisplay()
{
#ifdef USE_XTEST
    if (disp) {
        XCloseDisplay(disp);
        disp = NULL;
    }
#endif
}

#ifdef USE_XTEST

static void fakeKeyProcess(KeyCode fakeKey, int shift)
{
    DEBUG2("fakeKeyProcess Key=%d Shift=%d Ctrl=%d Alt=%d", fakeKey, shift, modifierCtrl, modifierAlt);
    
    KeyCode shiftKey = XKeysymToKeycode(disp,XK_Shift_L);
    KeyCode altKey   = XKeysymToKeycode(disp,XK_Alt_L);
    KeyCode ctrlKey  = XKeysymToKeycode(disp,XK_Control_L);
   
    if (shift) {
        XTestFakeKeyEvent(disp, shiftKey, True, CurrentTime);
    }
    if (modifierCtrl) {
        XTestFakeKeyEvent(disp, ctrlKey, True, CurrentTime);
    }
    if (modifierAlt) {
        XTestFakeKeyEvent(disp, altKey, True, CurrentTime);
    }

    XTestFakeKeyEvent(disp, fakeKey, True, CurrentTime);
    XTestFakeKeyEvent(disp, fakeKey, False, CurrentTime);
     
    if (modifierAlt) {
        XTestFakeKeyEvent(disp, altKey, False, CurrentTime);
    }
    if (modifierCtrl) {
        XTestFakeKeyEvent(disp, ctrlKey, False, CurrentTime);
    }
    if (shift) {
        XTestFakeKeyEvent(disp, shiftKey, False, CurrentTime);
    }
}

static void mouseClick(int button)
{
    XTestFakeButtonEvent(disp, button, True, CurrentTime);
    XTestFakeButtonEvent(disp, button, False, CurrentTime);
}

static void mouseDoubleClick()
{
    mouseClick(1);
    usleep(1000);
    mouseClick(1);
}

static void setModifier(const char *btn, int on)
{
    if (strcmp("Ctrl", btn) == 0) {
         modifierCtrl = on;
    } else if (strcmp("Alt", btn) == 0) { 
        modifierAlt   = on;  
    } else {
        DEBUG2("emulateCommands: improper modifier %s", btn);
    }
}

static struct 
{ 
    const char* ksStr;
    int         shift;
    KeySym      ks;
} 
ksTable[] = {
    {"!",  1, XK_exclam},
    {"\"", 0, XK_quotedbl},
    {"#",  1, XK_numbersign},
    {"$",  1, XK_dollar},
    {"%",  1, XK_percent},
    {"&",  1, XK_ampersand},
    {"`",  1, XK_apostrophe},
    {"*",  1, XK_asterisk},
    {"+",  1, XK_plus},
    {"-",  0, XK_minus},
    {".",  0, XK_period},
    {"/",  0, XK_slash} ,
    {":",  1, XK_colon},
    {"<",  1, XK_less},
    {"=",  0, XK_equal},
    {">",  1, XK_greater},
    {"?",  1, XK_question},
    {"[",  0, XK_bracketleft},
    {"\\", 0, XK_backslash},
    {"]",  0, XK_bracketright},
    {"^",  1, XK_asciicircum},
    {"_",  1, XK_underscore},
    {"@",  1, XK_at},
     
    {"parenleft",  1, XK_parenleft},
    {"parenright", 1, XK_parenright},
    {"comma",      0, XK_comma},
    {"semicolon",  0, XK_semicolon},
    {"space",      0, XK_space},
   
    {"Back",             0, XF86XK_Back},
    {"Close",            0, XF86XK_Close},
    {"Eject",            0, XF86XK_Eject},
    {"Forward",          0, XF86XK_Forward},
    
    {"AudioForward",     0, XF86XK_AudioForward},
    {"AudioLowerVolume", 0, XF86XK_AudioLowerVolume},
    {"AudioMute",        0, XF86XK_AudioMute},
    {"AudioNext",        0, XF86XK_AudioNext},
    {"AudioPause",       0, XF86XK_AudioPause}, 
    {"AudioPlay",        0, XF86XK_AudioPlay}, 
    {"AudioPrev",        0, XF86XK_AudioPrev},
    {"AudioRaiseVolume", 0, XF86XK_AudioRaiseVolume},
    {"AudioRewind",      0, XF86XK_AudioRewind},
    
    {"MonBrightnessDown",0, XF86XK_MonBrightnessDown},
    {"MonBrightnessUp",  0, XF86XK_MonBrightnessUp},
    
    {"PowerOff",         0, XF86XK_PowerOff},
    {"WakeUp",           0, XF86XK_WakeUp},
    { NULL,              0, NoSymbol}
};

static KeyCode keysymStr2keycode(Display* disp, const char* keysymStr, int* shift)
{
    (*shift) = 0;
    KeySym ks = NoSymbol;
    
    int i=0;
    while (ksTable[i].ksStr) {
        if(strcmp(ksTable[i].ksStr,keysymStr) == 0) {
            ks = ksTable[i].ks;
            (*shift) = ksTable[i].shift;
            DEBUG2("match keysymbol %s %ld %d", keysymStr,ks,(*shift));
            break;
        }
        i++;
    }
    
    if (ks == NoSymbol) {
       ks = XStringToKeysym(keysymStr);
       
       if (ks != NoSymbol) {  // ok, got it
            int i=0;
            while (ksTable[i].ksStr) {  // now detect shift
                if(ksTable[i].ks == ks) {
                    (*shift) = ksTable[i].shift;
                    break;
                }
                i++;
            }
        }
    }
    
    DEBUG2("matched %s %ld %d", keysymStr,ks,(*shift));
    
    if (ks == NoSymbol) {
        DEBUG2("couldn't map keysymbol %s", keysymStr);
        ks = XStringToKeysym("space");
        (*shift) = 0;
    }
    
    KeyCode kk = XKeysymToKeycode(disp, ks);
    if (kk == 0) {
        kk = XKeysymToKeycode(disp,XStringToKeysym("space"));
    }
    
    // check shifted
   
    //KeySym ks2 = XKeycodeToKeysym(disp, kk, 0); // deprecated
    int keysyms_per_keycode_return;
    KeySym *ks2 = XGetKeyboardMapping(disp, kk, 1, &keysyms_per_keycode_return);
    
    if (*ks2 != NoSymbol) {
        if (*ks2 != ks) {  // possibly shifted
            KeySym lower_return;
            KeySym upper_return;
            XConvertCase(*ks2, &lower_return, &upper_return);
            
            if (upper_return == ks) {
                (*shift) = 1;
            }
        }
    }
    XFree(ks2);
    
    return kk;
}

#endif

// Input can be something like:
// key,a,key,by_value,40,keyup,b,keydown,c,mouse,1,mouseup,1,mousedown,1,mousemove,100,100,mousermove,10,10,mousedblclick,sleep,1000
//
int emulateCommands(int subtype, const char *descr, const char* cmdString, cmdParams* params)
{
    logger(L_INF, "Command: Emulate");
    
    #ifdef USE_XTEST
    if (descr == NULL) {
        logger(L_ERR, "[EX]: emulateCommands(): no command");
        return EXIT_NOK;
    }
    if (disp == NULL) {
        disp = XOpenDisplay(NULL);
    }
    if (disp == NULL) {
        return EXIT_NOK;
    }

    char * cmd = strdup(descr);

    KeyCode fakeKey;
    int shift = 0;

    char* token = strtok(cmd,",");
    while (token) {

        if (strcmp(token,"keyup") == 0) {
            char * key = strtok(NULL,",");
            if (key) {
                if (strcmp(key,"by_value") == 0) {
                    char * keyval = strtok(NULL,",");
                    if (keyval) {
                        fakeKey = XKeysymToKeycode(disp,strtol(keyval, (char**) NULL, 0));
                        if (fakeKey == 0) {
                            logger(L_DBG, "emulateCommands: improper keyup value");
                            continue;
                        }
                    } else {
                        logger(L_DBG, "emulateCommands: keyup value absent");
                        continue;
                    }
                } else {
                    fakeKey = keysymStr2keycode(disp,key,&shift);
                }
                XTestFakeKeyEvent(disp, fakeKey, False, CurrentTime);
            } else {
                logger(L_DBG, "emulateCommands: improper keyup");
            }
        } else if (strcmp(token,"keydown") == 0) {
            char * key = strtok(NULL,",");
            if (key) {
                if (strcmp(key,"by_value") == 0) {
                    char * keyval = strtok(NULL,",");
                    if (keyval) {
                        fakeKey = XKeysymToKeycode(disp,strtol(keyval, (char**) NULL, 0));
                        if (fakeKey == 0) {
                            logger(L_DBG, "emulateCommands: improper keydown value");
                            continue;
                        }
                    } else {
                        logger(L_DBG, "emulateCommands: keydown value absent");
                        continue;
                    }
                } else {
                    fakeKey = keysymStr2keycode(disp,key,&shift);
                }
                XTestFakeKeyEvent(disp, fakeKey, True, CurrentTime );
            } else {
                logger(L_DBG, "emulateCommands: improper keydown");
            }

        } else if (strcmp(token,"key") == 0) {

            char * key = strtok(NULL,",");
            
            //DEBUG2("emulateCommands: PARSE %s %c >%s<", token, *(token+4), key ? key : "NULL");
            
            if (key) {
                if (strcmp(key,"by_value") == 0) {
                    char * keyval = strtok(NULL,",");
                    if (keyval) {
                        //printf("Emulate key %s\n",keyval);
                        
                        DEBUG2("emulateCommands: key value is %s", keyval);
                        
                        fakeKey = XKeysymToKeycode(disp,strtol(keyval, (char**) NULL, 0));
                        if (fakeKey == 0) {
                            logger(L_DBG, "emulateCommands: improper key value");
                            continue;
                        }
                    } else {
                        logger(L_DBG, "emulateCommands: key value absent");
                        continue;
                    }
                } else {
                
                    DEBUG2("emulateCommands: key is %s", key);
                    fakeKey = keysymStr2keycode(disp,key,&shift);
                }
                
                fakeKeyProcess(fakeKey, shift);
                
            } else {
                logger(L_DBG, "emulateCommands: improper key");
            }
            
        } else if (strcmp(token,"modifier") == 0) {
            
            char * on = strtok(NULL,",");
            if (on) {
                int v = 0;
                if (strcmp("1", on) == 0) {
                    v = 1;
                }
                
                char * mval = strtok(NULL,",");
                if (mval) {
                    setModifier(mval, v);
                } else {
                    logger(L_DBG, "emulateCommands: modifier absent");
                }
            } else {
                logger(L_DBG, "emulateCommands: improper modifier value");
            }


        } else if (strcmp(token,"mousemove") == 0) {
            char * x = strtok(NULL,",");
            char * y = (x ? strtok(NULL,",") : NULL);
            if (x && y) {
                XTestFakeMotionEvent(disp, -1, atoi(x), atoi(y), CurrentTime );
            } else {
                logger(L_DBG, "emulateCommands: improper mousemove");
            }

        } else if (strcmp(token,"mousermove") == 0) {
            char * x = strtok(NULL,",");
            char * y = (x ? strtok(NULL,",") : NULL);
            if (x && y) {
                XTestFakeRelativeMotionEvent(disp,atoi(x), atoi(y), CurrentTime );
            } else {
                logger(L_DBG, "emulateCommands: improper mousermove");
            }

        } else if (strcmp(token,"mousedblclick") == 0) {

            mouseDoubleClick();

        } else if (strcmp(token,"mouseup") == 0) {
            char * x = strtok(NULL,",");
            if (x) {
                XTestFakeButtonEvent(disp, atoi(x), False, CurrentTime);
            } else {
                logger(L_DBG, "emulateCommands: improper mouseup");
            }

        } else if (strcmp(token,"mousedown") == 0) {
            char * x = strtok(NULL,",");
            if (x) {
                XTestFakeButtonEvent(disp, atoi(x), True, CurrentTime);
            } else {
                logger(L_DBG, "emulateCommands: improper mousedown");
            }

        } else if (strcmp(token,"mouse") == 0) {
            char * x = strtok(NULL,",");
            if (x) {
                mouseClick(atoi(x));
            } else {
                logger(L_DBG, "emulateCommands: improper mouse");
            }

        } else if (strcmp(token,"sleep") == 0) {

            char * x = strtok(NULL,",");
            if (x) {
                usleep(atoi(x));
            } else {
                logger(L_DBG, "emulateCommands: improper sleep");
            }

        } else {
            logger(L_DBG, "emulateCommands: unknown operation");
        }
        token = strtok(NULL,",");

        XFlush(disp);
    }
    XFlush(disp);

    free(cmd);
    #endif

    return EXIT_OK;
}

