// ImGui iOS+OpenGL+Synergy binding
// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp.
// Providing a standalone iOS application with Synergy integration makes this sample more verbose than others. It also hasn't been tested as much.
// Refer to other examples to get an easier understanding of how to integrate ImGui into your existing application.

// TODO:
// - Clipboard is not supported.

#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include "imgui_impl_ios.h"
#include "imgui.h"

#include "uSynergy.h"

// From Carbon HIToolbox/Events.h
// FIXME: Keyboard mapping is hacked in because Synergy doesn't give us character but only keycode which aren't really portable if you consider keyboard locale. See https://github.com/ocornut/imgui/pull/247
enum {
    kVK_ANSI_A                    = 0x00,
    kVK_ANSI_S                    = 0x01,
    kVK_ANSI_D                    = 0x02,
    kVK_ANSI_F                    = 0x03,
    kVK_ANSI_H                    = 0x04,
    kVK_ANSI_G                    = 0x05,
    kVK_ANSI_Z                    = 0x06,
    kVK_ANSI_X                    = 0x07,
    kVK_ANSI_C                    = 0x08,
    kVK_ANSI_V                    = 0x09,
    kVK_ANSI_B                    = 0x0B,
    kVK_ANSI_Q                    = 0x0C,
    kVK_ANSI_W                    = 0x0D,
    kVK_ANSI_E                    = 0x0E,
    kVK_ANSI_R                    = 0x0F,
    kVK_ANSI_Y                    = 0x10,
    kVK_ANSI_T                    = 0x11,
    kVK_ANSI_1                    = 0x12,
    kVK_ANSI_2                    = 0x13,
    kVK_ANSI_3                    = 0x14,
    kVK_ANSI_4                    = 0x15,
    kVK_ANSI_6                    = 0x16,
    kVK_ANSI_5                    = 0x17,
    kVK_ANSI_Equal                = 0x18,
    kVK_ANSI_9                    = 0x19,
    kVK_ANSI_7                    = 0x1A,
    kVK_ANSI_Minus                = 0x1B,
    kVK_ANSI_8                    = 0x1C,
    kVK_ANSI_0                    = 0x1D,
    kVK_ANSI_RightBracket         = 0x1E,
    kVK_ANSI_O                    = 0x1F,
    kVK_ANSI_U                    = 0x20,
    kVK_ANSI_LeftBracket          = 0x21,
    kVK_ANSI_I                    = 0x22,
    kVK_ANSI_P                    = 0x23,
    kVK_ANSI_L                    = 0x25,
    kVK_ANSI_J                    = 0x26,
    kVK_ANSI_Quote                = 0x27,
    kVK_ANSI_K                    = 0x28,
    kVK_ANSI_Semicolon            = 0x29,
    kVK_ANSI_Backslash            = 0x2A,
    kVK_ANSI_Comma                = 0x2B,
    kVK_ANSI_Slash                = 0x2C,
    kVK_ANSI_N                    = 0x2D,
    kVK_ANSI_M                    = 0x2E,
    kVK_ANSI_Period               = 0x2F,
    kVK_ANSI_Grave                = 0x32,
    kVK_ANSI_KeypadDecimal        = 0x41,
    kVK_ANSI_KeypadMultiply       = 0x43,
    kVK_ANSI_KeypadPlus           = 0x45,
    kVK_ANSI_KeypadClear          = 0x47,
    kVK_ANSI_KeypadDivide         = 0x4B,
    kVK_ANSI_KeypadEnter          = 0x4C,
    kVK_ANSI_KeypadMinus          = 0x4E,
    kVK_ANSI_KeypadEquals         = 0x51,
    kVK_ANSI_Keypad0              = 0x52,
    kVK_ANSI_Keypad1              = 0x53,
    kVK_ANSI_Keypad2              = 0x54,
    kVK_ANSI_Keypad3              = 0x55,
    kVK_ANSI_Keypad4              = 0x56,
    kVK_ANSI_Keypad5              = 0x57,
    kVK_ANSI_Keypad6              = 0x58,
    kVK_ANSI_Keypad7              = 0x59,
    kVK_ANSI_Keypad8              = 0x5B,
    kVK_ANSI_Keypad9              = 0x5C
};

/* keycodes for keys that are independent of keyboard layout*/
enum {
    kVK_Return                    = 0x24,
    kVK_Tab                       = 0x30,
    kVK_Space                     = 0x31,
    kVK_Delete                    = 0x33,
    kVK_Escape                    = 0x35,
    kVK_Command                   = 0x37,
    kVK_Shift                     = 0x38,
    kVK_CapsLock                  = 0x39,
    kVK_Option                    = 0x3A,
    kVK_Control                   = 0x3B,
    kVK_RightShift                = 0x3C,
    kVK_RightOption               = 0x3D,
    kVK_RightControl              = 0x3E,
    kVK_Function                  = 0x3F,
    kVK_F17                       = 0x40,
    kVK_VolumeUp                  = 0x48,
    kVK_VolumeDown                = 0x49,
    kVK_Mute                      = 0x4A,
    kVK_F18                       = 0x4F,
    kVK_F19                       = 0x50,
    kVK_F20                       = 0x5A,
    kVK_F5                        = 0x60,
    kVK_F6                        = 0x61,
    kVK_F7                        = 0x62,
    kVK_F3                        = 0x63,
    kVK_F8                        = 0x64,
    kVK_F9                        = 0x65,
    kVK_F11                       = 0x67,
    kVK_F13                       = 0x69,
    kVK_F16                       = 0x6A,
    kVK_F14                       = 0x6B,
    kVK_F10                       = 0x6D,
    kVK_F12                       = 0x6F,
    kVK_F15                       = 0x71,
    kVK_Help                      = 0x72,
    kVK_Home                      = 0x73,
    kVK_PageUp                    = 0x74,
    kVK_ForwardDelete             = 0x75,
    kVK_F4                        = 0x76,
    kVK_End                       = 0x77,
    kVK_F2                        = 0x78,
    kVK_PageDown                  = 0x79,
    kVK_F1                        = 0x7A,
    kVK_LeftArrow                 = 0x7B,
    kVK_RightArrow                = 0x7C,
    kVK_DownArrow                 = 0x7D,
    kVK_UpArrow                   = 0x7E
};

static char g_keycodeCharUnshifted[256] = {};
static char g_keycodeCharShifted[256] = {};

//static double       g_Time = 0.0f;
static bool         g_MousePressed[3] = { false, false, false };
static float        g_mouseWheelX = 0.0f;
static float        g_mouseWheelY = 0.0f;

static GLuint       g_FontTexture = 0;
static int          g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
static int          g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
static int          g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
static size_t       g_VboSize = 0;
static unsigned int g_VboHandle = 0, g_VaoHandle = 0;
static float        g_displayScale;

static int usynergy_sockfd;
static bool g_synergyPtrActive = false;
static uint16_t g_mousePosX = 0;
static uint16_t g_mousePosY = 0;

static void ImGui_ImplIOS_RenderDrawLists (ImDrawData *draw_data);
bool ImGui_ImplIOS_CreateDeviceObjects();

static NSString *g_serverName;

uSynergyBool ImGui_ConnectFunc(uSynergyCookie cookie)
{
    // NOTE: You need to turn off "Use SSL Encryption" in Synergy preferences, since
    // uSynergy does not support SSL.
    
    NSLog( @"Connect Func!");
    struct addrinfo hints, *res;
    
    // first, load up address structs with getaddrinfo():
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    
    // get server address
    getaddrinfo([g_serverName UTF8String], "24800", &hints, &res);
    
    if (!res)
    {
        NSLog( @"Could not find server: %@", g_serverName );
        return USYNERGY_FALSE;
    }
    
    // make a socket:
    usynergy_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    
    // connect it to the address and port we passed in to getaddrinfo():
    int ret = connect(usynergy_sockfd, res->ai_addr, res->ai_addrlen);
    if (!ret) {
        NSLog( @"Connect succeeded...");
    } else {
        NSLog( @"Connect failed, %d", ret );
    }
    
    
    return USYNERGY_TRUE;
}

uSynergyBool ImGui_SendFunc(uSynergyCookie cookie, const uint8_t *buffer, int length)
{
//    NSLog( @"Send Func" );
    send( usynergy_sockfd, buffer, length, 0 );
    
    return USYNERGY_TRUE;
}

uSynergyBool ImGui_RecvFunc(uSynergyCookie cookie, uint8_t *buffer, int maxLength, int* outLength)
{
    *outLength = (int)recv( usynergy_sockfd, buffer, maxLength, 0 );
    
    return USYNERGY_TRUE;
}

void ImGui_SleepFunc(uSynergyCookie cookie, int timeMs)
{
    usleep( timeMs * 1000 );
}

uint32_t ImGui_GetTimeFunc()
{
    struct timeval  tv;
    gettimeofday(&tv, NULL);
    
    return (int32_t)((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000);
}

void ImGui_TraceFunc(uSynergyCookie cookie, const char *text)
{
    puts(text);
}

void ImGui_ScreenActiveCallback(uSynergyCookie cookie, uSynergyBool active)
{
    g_synergyPtrActive = active;
//    printf( "Synergy: screen activate %s\n", active?"YES":"NO" );
}

void ImGui_MouseCallback(uSynergyCookie cookie, uint16_t x, uint16_t y, int16_t wheelX, int16_t wheelY,
                         uSynergyBool buttonLeft, uSynergyBool buttonRight, uSynergyBool buttonMiddle)
{
//    printf("Synergy: mouse callback %d %d -- wheel %d %d\n", x, y,  wheelX, wheelY );
    uSynergyContext *ctx = (uSynergyContext*)cookie;
    g_mousePosX = x;
    g_mousePosY = y;
    g_mouseWheelX = wheelX;
    g_mouseWheelY = wheelY;
    g_MousePressed[0] = buttonLeft;
    g_MousePressed[1] = buttonMiddle;
    g_MousePressed[2] = buttonRight;
    
    ctx->m_mouseWheelX = 0;
    ctx->m_mouseWheelY = 0;
}

void ImGui_KeyboardCallback(uSynergyCookie cookie, uint16_t key,
                            uint16_t modifiers, uSynergyBool down, uSynergyBool repeat)
{
    int scanCode = key-1;
    // printf("Synergy: keyboard callback: 0x%02X (%s)", scanCode, down?"true":"false");
    ImGuiIO& io = ImGui::GetIO();
    io.KeysDown[key] = down;
    io.KeyShift = (modifiers & USYNERGY_MODIFIER_SHIFT);
    io.KeyCtrl = (modifiers & USYNERGY_MODIFIER_CTRL);
    io.KeyAlt = (modifiers & USYNERGY_MODIFIER_ALT);
    io.KeySuper = (modifiers & USYNERGY_MODIFIER_WIN);
    
    // Add this as keyboard input
    if ((down) && (key) && (scanCode<256) && !(modifiers & USYNERGY_MODIFIER_CTRL)) 
	{
        // If this key maps to a character input, apply it
        int charForKeycode = (modifiers & USYNERGY_MODIFIER_SHIFT) ? g_keycodeCharShifted[scanCode] : g_keycodeCharUnshifted[scanCode];
        io.AddInputCharacter((unsigned short)charForKeycode);
    }   
}

void ImGui_JoystickCallback(uSynergyCookie cookie, uint8_t joyNum, uint16_t buttons, int8_t leftStickX, int8_t leftStickY, int8_t rightStickX, int8_t rightStickY)
{
    printf("Synergy: joystick callback TODO\n");
}

void ImGui_ClipboardCallback(uSynergyCookie cookie, enum uSynergyClipboardFormat format, const uint8_t *data, uint32_t size)
{
    printf("Synergy: clipboard callback TODO\n" );
}

@interface ImGuiHelper ()
{
    BOOL _mouseDown;
    BOOL _mouseTapped;
    CGPoint _touchPos;

    uSynergyContext _synergyCtx;
    dispatch_queue_t _synergyQueue;
}
@property (nonatomic, weak) UIView *view;
@property (nonatomic, strong) NSString *serverName;

@end

@implementation ImGuiHelper

- (id) initWithView: (UIView *)view
{
    self = [super init];
    if (self)
    {
        self.view = view;

        [self setupImGuiHooks];
    }
    return self;
}

- (void)setupKeymaps
{
    // The keyboard mapping is a big headache. I tried for a while to find a better way to do this,
    // but this was the best I could come up with. There are some device independent API's available
    // to convert scan codes to unicode characters, but these are only available on mac and not
    // on iOS as far as I can tell (it's part of Carbon). I didn't see any better way to do
    // this or  any way to get the character codes out of usynergy.
    g_keycodeCharUnshifted[ kVK_ANSI_A ]='a';
    g_keycodeCharUnshifted[ kVK_ANSI_S ]='s';
    g_keycodeCharUnshifted[ kVK_ANSI_D ]='d';
    g_keycodeCharUnshifted[ kVK_ANSI_F ]='f';
    g_keycodeCharUnshifted[ kVK_ANSI_H ]='h';
    g_keycodeCharUnshifted[ kVK_ANSI_G ]='g';
    g_keycodeCharUnshifted[ kVK_ANSI_Z ]='z';
    g_keycodeCharUnshifted[ kVK_ANSI_X ]='x';
    g_keycodeCharUnshifted[ kVK_ANSI_C ]='c';
    g_keycodeCharUnshifted[ kVK_ANSI_V ]='v';
    g_keycodeCharUnshifted[ kVK_ANSI_B ]='b';
    g_keycodeCharUnshifted[ kVK_ANSI_Q ]='q';
    g_keycodeCharUnshifted[ kVK_ANSI_W ]='w';
    g_keycodeCharUnshifted[ kVK_ANSI_E ]='e';
    g_keycodeCharUnshifted[ kVK_ANSI_R ]='r';
    g_keycodeCharUnshifted[ kVK_ANSI_Y ]='y';
    g_keycodeCharUnshifted[ kVK_ANSI_T ]='t';
    g_keycodeCharUnshifted[ kVK_ANSI_1 ]='1';
    g_keycodeCharUnshifted[ kVK_ANSI_2 ]='2';
    g_keycodeCharUnshifted[ kVK_ANSI_3 ]='3';
    g_keycodeCharUnshifted[ kVK_ANSI_4 ]='4';
    g_keycodeCharUnshifted[ kVK_ANSI_6 ]='6';
    g_keycodeCharUnshifted[ kVK_ANSI_5 ]='5';
    g_keycodeCharUnshifted[ kVK_ANSI_Equal ]='=';
    g_keycodeCharUnshifted[ kVK_ANSI_9 ]='9';
    g_keycodeCharUnshifted[ kVK_ANSI_7 ]='7';
    g_keycodeCharUnshifted[ kVK_ANSI_Minus ]='-';
    g_keycodeCharUnshifted[ kVK_ANSI_8 ]='8';
    g_keycodeCharUnshifted[ kVK_ANSI_0 ]='0';
    g_keycodeCharUnshifted[ kVK_ANSI_RightBracket ]=']';
    g_keycodeCharUnshifted[ kVK_ANSI_O ]='o';
    g_keycodeCharUnshifted[ kVK_ANSI_U ]='u';
    g_keycodeCharUnshifted[ kVK_ANSI_LeftBracket ]='[';
    g_keycodeCharUnshifted[ kVK_ANSI_I ]='i';
    g_keycodeCharUnshifted[ kVK_ANSI_P ]='p';
    g_keycodeCharUnshifted[ kVK_ANSI_L ]='l';
    g_keycodeCharUnshifted[ kVK_ANSI_J ]='j';
    g_keycodeCharUnshifted[ kVK_ANSI_Quote ]='\'';
    g_keycodeCharUnshifted[ kVK_ANSI_K ]='k';
    g_keycodeCharUnshifted[ kVK_ANSI_Semicolon ]=';';
    g_keycodeCharUnshifted[ kVK_ANSI_Backslash ]='\\';
    g_keycodeCharUnshifted[ kVK_ANSI_Comma ]=',';
    g_keycodeCharUnshifted[ kVK_ANSI_Slash ]='/';
    g_keycodeCharUnshifted[ kVK_ANSI_N ]='n';
    g_keycodeCharUnshifted[ kVK_ANSI_M ]='m';
    g_keycodeCharUnshifted[ kVK_ANSI_Period ]='.';
    g_keycodeCharUnshifted[ kVK_ANSI_Grave ]='`';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadDecimal ]='.';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadMultiply ]='*';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadPlus ]='+';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadDivide ]='/';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadEnter ]='\n';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadMinus ]='-';
    g_keycodeCharUnshifted[ kVK_ANSI_KeypadEquals ]='=';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad0 ]='0';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad1 ]='1';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad2 ]='2';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad3 ]='3';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad4 ]='4';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad5 ]='5';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad6 ]='6';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad7 ]='7';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad8 ]='8';
    g_keycodeCharUnshifted[ kVK_ANSI_Keypad9 ]='9';
    g_keycodeCharUnshifted[ kVK_Space ]=' ';
    
    g_keycodeCharShifted[ kVK_ANSI_A ]='A';
    g_keycodeCharShifted[ kVK_ANSI_S ]='S';
    g_keycodeCharShifted[ kVK_ANSI_D ]='D';
    g_keycodeCharShifted[ kVK_ANSI_F ]='F';
    g_keycodeCharShifted[ kVK_ANSI_H ]='H';
    g_keycodeCharShifted[ kVK_ANSI_G ]='G';
    g_keycodeCharShifted[ kVK_ANSI_Z ]='Z';
    g_keycodeCharShifted[ kVK_ANSI_X ]='X';
    g_keycodeCharShifted[ kVK_ANSI_C ]='C';
    g_keycodeCharShifted[ kVK_ANSI_V ]='V';
    g_keycodeCharShifted[ kVK_ANSI_B ]='B';
    g_keycodeCharShifted[ kVK_ANSI_Q ]='Q';
    g_keycodeCharShifted[ kVK_ANSI_W ]='W';
    g_keycodeCharShifted[ kVK_ANSI_E ]='E';
    g_keycodeCharShifted[ kVK_ANSI_R ]='R';
    g_keycodeCharShifted[ kVK_ANSI_Y ]='Y';
    g_keycodeCharShifted[ kVK_ANSI_T ]='T';
    g_keycodeCharShifted[ kVK_ANSI_1 ]='!';
    g_keycodeCharShifted[ kVK_ANSI_2 ]='@';
    g_keycodeCharShifted[ kVK_ANSI_3 ]='#';
    g_keycodeCharShifted[ kVK_ANSI_4 ]='$';
    g_keycodeCharShifted[ kVK_ANSI_6 ]='^';
    g_keycodeCharShifted[ kVK_ANSI_5 ]='%';
    g_keycodeCharShifted[ kVK_ANSI_Equal ]='+';
    g_keycodeCharShifted[ kVK_ANSI_9 ]='(';
    g_keycodeCharShifted[ kVK_ANSI_7 ]='&';
    g_keycodeCharShifted[ kVK_ANSI_Minus ]='_';
    g_keycodeCharShifted[ kVK_ANSI_8 ]='*';
    g_keycodeCharShifted[ kVK_ANSI_0 ]=')';
    g_keycodeCharShifted[ kVK_ANSI_RightBracket ]='}';
    g_keycodeCharShifted[ kVK_ANSI_O ]='O';
    g_keycodeCharShifted[ kVK_ANSI_U ]='U';
    g_keycodeCharShifted[ kVK_ANSI_LeftBracket ]='{';
    g_keycodeCharShifted[ kVK_ANSI_I ]='I';
    g_keycodeCharShifted[ kVK_ANSI_P ]='P';
    g_keycodeCharShifted[ kVK_ANSI_L ]='L';
    g_keycodeCharShifted[ kVK_ANSI_J ]='J';
    g_keycodeCharShifted[ kVK_ANSI_Quote ]='\"';
    g_keycodeCharShifted[ kVK_ANSI_K ]='K';
    g_keycodeCharShifted[ kVK_ANSI_Semicolon ]=':';
    g_keycodeCharShifted[ kVK_ANSI_Backslash ]='|';
    g_keycodeCharShifted[ kVK_ANSI_Comma ]='<';
    g_keycodeCharShifted[ kVK_ANSI_Slash ]='?';
    g_keycodeCharShifted[ kVK_ANSI_N ]='N';
    g_keycodeCharShifted[ kVK_ANSI_M ]='M';
    g_keycodeCharShifted[ kVK_ANSI_Period ]='>';
    g_keycodeCharShifted[ kVK_ANSI_Grave ]='~';
    g_keycodeCharShifted[ kVK_ANSI_KeypadDecimal ]='.';
    g_keycodeCharShifted[ kVK_ANSI_KeypadMultiply ]='*';
    g_keycodeCharShifted[ kVK_ANSI_KeypadPlus ]='+';
    g_keycodeCharShifted[ kVK_ANSI_KeypadDivide ]='/';
    g_keycodeCharShifted[ kVK_ANSI_KeypadEnter ]='\n';
    g_keycodeCharShifted[ kVK_ANSI_KeypadMinus ]='-';
    g_keycodeCharShifted[ kVK_ANSI_KeypadEquals ]='=';
    g_keycodeCharShifted[ kVK_ANSI_Keypad0 ]='0';
    g_keycodeCharShifted[ kVK_ANSI_Keypad1 ]='1';
    g_keycodeCharShifted[ kVK_ANSI_Keypad2 ]='2';
    g_keycodeCharShifted[ kVK_ANSI_Keypad3 ]='3';
    g_keycodeCharShifted[ kVK_ANSI_Keypad4 ]='4';
    g_keycodeCharShifted[ kVK_ANSI_Keypad5 ]='5';
    g_keycodeCharShifted[ kVK_ANSI_Keypad6 ]='6';
    g_keycodeCharShifted[ kVK_ANSI_Keypad7 ]='7';
    g_keycodeCharShifted[ kVK_ANSI_Keypad8 ]='8';
    g_keycodeCharShifted[ kVK_ANSI_Keypad9 ]='9';
    g_keycodeCharShifted[ kVK_Space ]=' ';
}

- (void)setupImGuiHooks
{
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    
    [self setupKeymaps];
    
    // Account for retina display for glScissor
    g_displayScale = [[UIScreen mainScreen] scale];
    
    ImGuiStyle& style = ImGui::GetStyle();
    style.TouchExtraPadding = ImVec2( 4.0, 4.0 );
    
    io.RenderDrawListsFn = ImGui_ImplIOS_RenderDrawLists;
    
    UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidPan:) ];
    [self.view addGestureRecognizer:panRecognizer];
    
    UITapGestureRecognizer *tapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector( viewDidTap:)];
    [self.view addGestureRecognizer:tapRecoginzer];
    
    // Fill out the Synergy key map
    // (for some reason synergy scan codes are off by 1)
    io.KeyMap[ImGuiKey_Tab] = kVK_Tab+1;
    io.KeyMap[ImGuiKey_LeftArrow] = kVK_LeftArrow+1;
    io.KeyMap[ImGuiKey_RightArrow] = kVK_RightArrow+1;
    io.KeyMap[ImGuiKey_UpArrow] = kVK_UpArrow+1;
    io.KeyMap[ImGuiKey_DownArrow] = kVK_DownArrow+1;
    io.KeyMap[ImGuiKey_Home] = kVK_Home+1;
    io.KeyMap[ImGuiKey_End] = kVK_End+1;
    io.KeyMap[ImGuiKey_Insert] = kVK_Help+1;
    io.KeyMap[ImGuiKey_Delete] = kVK_ForwardDelete+1;
    io.KeyMap[ImGuiKey_Backspace] = kVK_Delete+1;
    io.KeyMap[ImGuiKey_Space] = kVK_Space+1;
    io.KeyMap[ImGuiKey_Enter] = kVK_Return+1;
    io.KeyMap[ImGuiKey_Escape] = kVK_Escape+1;
    io.KeyMap[ImGuiKey_A] = kVK_ANSI_A+1;
    io.KeyMap[ImGuiKey_C] = kVK_ANSI_C+1;
    io.KeyMap[ImGuiKey_V] = kVK_ANSI_V+1;
    io.KeyMap[ImGuiKey_X] = kVK_ANSI_X+1;
    io.KeyMap[ImGuiKey_Y] = kVK_ANSI_Y+1;
    io.KeyMap[ImGuiKey_Z] = kVK_ANSI_Z+1;
}

- (void)connectServer: (NSString*)serverName
{
    self.serverName = serverName;
    g_serverName = serverName;
    
    // Init synergy
    NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
    
    uSynergyInit( &_synergyCtx );
    _synergyCtx.m_clientName = strdup( [bundleName UTF8String] );
    _synergyCtx.m_clientWidth = self.view.bounds.size.width;
    _synergyCtx.m_clientHeight = self.view.bounds.size.height;
    
    _synergyCtx.m_connectFunc = ImGui_ConnectFunc;
    _synergyCtx.m_sendFunc = ImGui_SendFunc;
    _synergyCtx.m_receiveFunc = ImGui_RecvFunc;
    _synergyCtx.m_sleepFunc = ImGui_SleepFunc;
    _synergyCtx.m_traceFunc = ImGui_TraceFunc;
    _synergyCtx.m_getTimeFunc = ImGui_GetTimeFunc;
    
    _synergyCtx.m_traceFunc = ImGui_TraceFunc;
    _synergyCtx.m_screenActiveCallback = ImGui_ScreenActiveCallback;
    _synergyCtx.m_mouseCallback = ImGui_MouseCallback;
    _synergyCtx.m_keyboardCallback = ImGui_KeyboardCallback;
    
    _synergyCtx.m_cookie = (uSynergyCookie)&_synergyCtx;
    
    // Create a background thread for synergy
    _synergyQueue = dispatch_queue_create( "imgui-usynergy", NULL );
    dispatch_async( _synergyQueue, ^{
        while (1)
            uSynergyUpdate(&_synergyCtx);
    });
}


- (void)viewDidPan: (UIPanGestureRecognizer *)recognizer
{
    
    if ((recognizer.state == UIGestureRecognizerStateBegan) ||
        (recognizer.state == UIGestureRecognizerStateChanged))
    {
        _mouseDown = YES;
        _touchPos = [recognizer locationInView:self.view];
    }
    else
    {
        _mouseDown = NO;
        _touchPos = CGPointMake( -1, -1 );
    }
}

- (void)viewDidTap: (UITapGestureRecognizer*)recognizer
{
    _touchPos = [recognizer locationInView:self.view];
    _mouseTapped = YES;
}
    
- (void)render
{
    ImGui::Render();
}

- (void)newFrame
{
    ImGuiIO& io = ImGui::GetIO();
    ImGuiStyle& style = ImGui::GetStyle();
    
    if (!g_FontTexture)
        ImGui_ImplIOS_CreateDeviceObjects();
    
    io.DisplaySize = ImVec2(_view.bounds.size.width, _view.bounds.size.height);

    io.MouseDrawCursor = g_synergyPtrActive;
    if (g_synergyPtrActive)
    {
        style.TouchExtraPadding = ImVec2(0.0, 0.0);
        io.MousePos = ImVec2(g_mousePosX, g_mousePosY);
        for (int i=0; i < 3; i++)
            io.MouseDown[i] = g_MousePressed[i];

        // This is an arbitrary scaling factor that works for me. Not sure what units these mousewheel values from synergy are supposed to be in.
        io.MouseWheel = g_mouseWheelY / 500.0;
        io.MouseWheelH = g_mouseWheelX / 500.0;
    }
    else
    {
        // Synergy not active, use touch events
        style.TouchExtraPadding = ImVec2(4.0, 4.0);
        io.MousePos = ImVec2(_touchPos.x, _touchPos.y);
        if ((_mouseDown) || (_mouseTapped))
        {
            io.MouseDown[0] = true;
            _mouseTapped = NO;
        }
        else
        {
            io.MouseDown[0] = false;
        }
    }
    
    ImGui::NewFrame();
}
@end

// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure)
// If text or lines are blurry when integrating ImGui in your engine:
// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f)
// NOTE: this is copied pretty much entirely from the example_glfw_opengl3, with only minor changes for ES
static void ImGui_ImplIOS_RenderDrawLists (ImDrawData *draw_data)
{
    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
	// FIXME: Backport changes from imgui_impl_glfw_gl3.cpp
    GLint last_program, last_texture;
    glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_SCISSOR_TEST);
    glActiveTexture(GL_TEXTURE0);
    
    // Setup orthographic projection matrix
    const float width = ImGui::GetIO().DisplaySize.x;
    const float height = ImGui::GetIO().DisplaySize.y;
    const float ortho_projection[4][4] =
    {
        { 2.0f/width,	0.0f,			0.0f,		0.0f },
        { 0.0f,			2.0f/-height,	0.0f,		0.0f },
        { 0.0f,			0.0f,			-1.0f,		0.0f },
        { -1.0f,		1.0f,			0.0f,		1.0f },
    };
    glUseProgram(g_ShaderHandle);
    glUniform1i(g_AttribLocationTex, 0);
    glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
    glBindVertexArray(g_VaoHandle);
    
    for (int n = 0; n < draw_data->CmdListsCount; n++)
    {
        ImDrawList* cmd_list = draw_data->CmdLists[n];
        ImDrawIdx* idx_buffer = &cmd_list->IdxBuffer.front();
        
        glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
        const int needed_vtx_size = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
        if (g_VboSize < needed_vtx_size)
        {
            // Grow our buffer if needed
            g_VboSize = needed_vtx_size + 2000 * sizeof(ImDrawVert);
            glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)g_VboSize, NULL, GL_STREAM_DRAW);
        }

        unsigned char* vtx_data = (unsigned char*)glMapBufferRange(GL_ARRAY_BUFFER, 0, needed_vtx_size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
        if (!vtx_data)
            continue;
        memcpy(vtx_data, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
        glUnmapBuffer(GL_ARRAY_BUFFER);
        
        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
        {
            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
            if (pcmd->UserCallback)
            {
                pcmd->UserCallback(cmd_list, pcmd);
            }
            else
            {
                glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId);
                glScissor((int)(pcmd->ClipRect.x * g_displayScale),
                          (int)((height - pcmd->ClipRect.w) * g_displayScale),
                          (int)((pcmd->ClipRect.z - pcmd->ClipRect.x) * g_displayScale),
                          (int)((pcmd->ClipRect.w - pcmd->ClipRect.y) * g_displayScale));
                glDrawElements( GL_TRIANGLES, (GLsizei)pcmd->ElemCount, GL_UNSIGNED_SHORT, idx_buffer );
            }
            idx_buffer += pcmd->ElemCount;
        }
    }
    
    // Restore modified state
    glBindVertexArray(0);
    glBindBuffer( GL_ARRAY_BUFFER, 0);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(last_program);
    glDisable(GL_SCISSOR_TEST);
    glBindTexture(GL_TEXTURE_2D, last_texture);
}

void ImGui_ImplIOS_CreateFontsTexture()
{
    // Build texture atlas
    ImGuiIO& io = ImGui::GetIO();
    unsigned char* pixels;
    int width, height;
    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader.
    
    // Upload texture to graphics system
    GLint last_texture;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
    glGenTextures(1, &g_FontTexture);
    glBindTexture(GL_TEXTURE_2D, g_FontTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    
    // Store our identifier
    io.Fonts->TexID = (void *)(intptr_t)g_FontTexture;

    // Restore state
    glBindTexture(GL_TEXTURE_2D, last_texture);
}

bool ImGui_ImplIOS_CreateDeviceObjects()
{
    const GLchar *vertex_shader =
    "uniform mat4 ProjMtx;\n"
    "attribute highp vec2 Position;\n"
    "attribute highp vec2 UV;\n"
    "attribute highp vec4 Color;\n"
    "varying vec2 Frag_UV;\n"
    "varying vec4 Frag_Color;\n"
    "void main()\n"
    "{\n"
    "	Frag_UV = UV;\n"
    "	Frag_Color = Color;\n"
    "	gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
    "}\n";
    
    const GLchar* fragment_shader =
    "uniform sampler2D Texture;\n"
    "varying highp vec2 Frag_UV;\n"
    "varying highp vec4 Frag_Color;\n"
    "void main()\n"
    "{\n"
    "	gl_FragColor = Frag_Color * texture2D( Texture, Frag_UV.st);\n"
    "}\n";
    
    g_ShaderHandle = glCreateProgram();
    g_VertHandle = glCreateShader(GL_VERTEX_SHADER);
    g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(g_VertHandle, 1, &vertex_shader, 0);
    glShaderSource(g_FragHandle, 1, &fragment_shader, 0);
    glCompileShader(g_VertHandle);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv( g_VertHandle, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(g_VertHandle, logLength, &logLength, log);
        NSLog(@"VERTEX Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glCompileShader(g_FragHandle);
    
#if defined(DEBUG)
    glGetShaderiv( g_FragHandle, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(g_FragHandle, logLength, &logLength, log);
        NSLog(@"FRAGMENT Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glAttachShader(g_ShaderHandle, g_VertHandle);
    glAttachShader(g_ShaderHandle, g_FragHandle);
    glLinkProgram(g_ShaderHandle);
    
    g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
    g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
    g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
    g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
    g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
    
    glGenBuffers(1, &g_VboHandle);
    
    glGenVertexArrays(1, &g_VaoHandle);
    glBindVertexArray(g_VaoHandle);
    glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
    glEnableVertexAttribArray(g_AttribLocationPosition);
    glEnableVertexAttribArray(g_AttribLocationUV);
    glEnableVertexAttribArray(g_AttribLocationColor);
    
    glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos));
    glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv));
    glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col));

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    ImGui_ImplIOS_CreateFontsTexture();
    
    return true;
}