/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2002 Chris Schoeneman
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 * 
 * This package 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, see <http://www.gnu.org/licenses/>.
 */

#include "synergy/unix/AppUtilUnix.h"
#include "synergy/ArgsBase.h"
#include <thread>

#if WINAPI_XWINDOWS
#include "synergy/unix/X11LayoutsParser.h"
#include <X11/XKBlib.h>
#elif WINAPI_CARBON
#include <Carbon/Carbon.h>
#else
#error Platform not supported.
#endif
#include "base/Log.h"
#include "base/log_outputters.h"

#if WINAPI_XWINDOWS
#include <libnotify/notify.h>
#endif

AppUtilUnix::AppUtilUnix(IEventQueue* events)
{
}

AppUtilUnix::~AppUtilUnix()
{
}

int
standardStartupStatic(int argc, char** argv)
{
    return AppUtil::instance().app().standardStartup(argc, argv);
}

int
AppUtilUnix::run(int argc, char** argv)
{
    return app().runInner(argc, argv, NULL, &standardStartupStatic);
}

void
AppUtilUnix::startNode()
{
    app().startNode();
}

std::vector<String>
AppUtilUnix::getKeyboardLayoutList()
{
    std::vector<String> layoutLangCodes;

#if WINAPI_XWINDOWS
    layoutLangCodes = X11LayoutsParser::getX11LanguageList("/usr/share/X11/xkb/rules/evdev.xml");
#elif WINAPI_CARBON
    CFStringRef keys[] = { kTISPropertyInputSourceCategory };
    CFStringRef values[] = { kTISCategoryKeyboardInputSource };
    CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 1, NULL, NULL);
    CFArrayRef kbds = TISCreateInputSourceList(dict, false);

    for (CFIndex i = 0; i < CFArrayGetCount(kbds); ++i) {
        TISInputSourceRef keyboardLayout = (TISInputSourceRef)CFArrayGetValueAtIndex(kbds, i);
        auto layoutLanguages = (CFArrayRef)TISGetInputSourceProperty(keyboardLayout, kTISPropertyInputSourceLanguages);
        char temporaryCString[128] = {0};
        for(CFIndex index = 0; index < CFArrayGetCount(layoutLanguages) && layoutLanguages; index++) {
            auto languageCode = (CFStringRef)CFArrayGetValueAtIndex(layoutLanguages, index);
            if(!languageCode ||
               !CFStringGetCString(languageCode, temporaryCString, 128, kCFStringEncodingUTF8)) {
                continue;
            }

            std::string langCode(temporaryCString);
            if(langCode.size() == 2 &&
               std::find(layoutLangCodes.begin(), layoutLangCodes.end(), langCode) == layoutLangCodes.end()){
                layoutLangCodes.push_back(langCode);
            }

            //Save only first language code
            break;
        }
    }
#endif

    return layoutLangCodes;
}

String
AppUtilUnix::getCurrentLanguageCode()
{
    String result = "";
#if WINAPI_XWINDOWS

    auto display = XOpenDisplay(nullptr);
      if (!display) {
          LOG((CLOG_WARN "Failed to open x11 default display"));
          return result;
      }

      auto kbdDescr= XkbAllocKeyboard();
      if (!kbdDescr) {
          LOG((CLOG_WARN "Failed to get x11 keyboard description"));
          return result;
      }
      XkbGetNames(display, XkbSymbolsNameMask, kbdDescr);

      Atom symNameAtom = kbdDescr->names->symbols;
      auto rawLayouts = std::string(XGetAtomName(display, symNameAtom));

      XkbStateRec state;
      XkbGetState(display, XkbUseCoreKbd, &state);
      auto nedeedGroupIndex = static_cast<int>(state.group);

      size_t groupIdx = 0;
      size_t groupStartI = 0;
      for(size_t strI = 0; strI < rawLayouts.size(); strI++) {
          if(rawLayouts[strI] != '+') {
              continue;
          }

          auto group = rawLayouts.substr(groupStartI, strI - groupStartI);
          if(group.find("group", 0, 5) == std::string::npos &&
             group.find("inet", 0, 4)  == std::string::npos &&
             group.find("pc", 0, 2)    == std::string::npos) {
              if(nedeedGroupIndex == groupIdx) {
                  result = group.substr(0, std::min(group.find('(', 0), group.find(':', 0)));
                  break;
              }
              groupIdx++;
          }

          groupStartI = strI + 1;
      }

      XFree(kbdDescr);
      XkbFreeNames(kbdDescr, XkbSymbolsNameMask, true);
      XCloseDisplay(display);

      result = X11LayoutsParser::convertLayotToISO("/usr/share/X11/xkb/rules/evdev.xml", result);

#elif WINAPI_CARBON
    auto layoutLanguages = (CFArrayRef)TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceLanguages);
    char temporaryCString[128] = {0};
    for(CFIndex index = 0; index < CFArrayGetCount(layoutLanguages) && layoutLanguages; index++) {
        auto languageCode = (CFStringRef)CFArrayGetValueAtIndex(layoutLanguages, index);
        if(!languageCode || !CFStringGetCString(languageCode, temporaryCString, 128, kCFStringEncodingUTF8)) {
            continue;
        }

        result = std::string(temporaryCString);
        break;
    }
#endif
    return result;
}
	
void
AppUtilUnix::showNotification(const String & title, const String & text) const
{
#if WINAPI_XWINDOWS
    LOG((CLOG_INFO "Showing notification. Title: \"%s\". Text: \"%s\"", title.c_str(), text.c_str()));
    if (!notify_init("Synergy"))
    {
        LOG((CLOG_INFO "Failed to initialize libnotify"));
        return;
    }

    auto notification = notify_notification_new (title.c_str(), text.c_str(), nullptr);
    if (notification == nullptr)
    {
        LOG((CLOG_INFO "Failed to create notification"));
        return;
    }
    notify_notification_set_timeout(notification, 10000);

    if (!notify_notification_show(notification, nullptr))
    {
        LOG((CLOG_INFO "Failed to show notification"));
    }

    g_object_unref(G_OBJECT(notification));
    notify_uninit();

#elif WINAPI_CARBON
    // synergys and synergyc are not allowed to send native notifications on MacOS
    // instead ask main synergy process to show them instead
    LOG((CLOG_INFO "OSX Notification: %s|%s", title.c_str(), text.c_str()));
#endif
}
