#include "MacUtils.h"
#include "UBPlatformUtils.h"
#include "core/UBApplication.h"
#include "core/UBSettings.h"
#include "softwareupdate/UBSoftwareUpdate.h"
#include "softwareupdate/UBSoftwareUpdateController.h"
#include "frameworks/UBFileSystemUtils.h"

#include <QWidget>

#import <Foundation/NSAutoreleasePool.h>
#import <Carbon/Carbon.h>
#import <APELite.h>


NSString* bundleShortVersion(NSBundle *bundle)
{
    return [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}

OSStatus emptySetSystemUIMode (
   SystemUIMode inMode,
   SystemUIOptions inOptions)
{
    Q_UNUSED(inMode);
    Q_UNUSED(inOptions);
    // NOOP
    return noErr;
}

void *originalSetSystemUIMode = 0;

void UBPlatformUtils::init()
{
	initializeKeyboardLayouts();

    // qwidget_mac.mm qt_mac_set_fullscreen_mode uses kUIModeAllSuppressed which is unfortunate in our case
    //
    // http://developer.apple.com/mac/library/documentation/Carbon/Reference/Dock_Manager/Reference/reference.html#//apple_ref/c/func/SetSystemUIMode
    //

    originalSetSystemUIMode = APEPatchCreate((const void *)SetSystemUIMode, (const void *)emptySetSystemUIMode);

    setDesktopMode(false);

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *currentPath      = [[NSBundle mainBundle] pathForResource:@"Save PDF to Sankore" ofType:@"workflow"];
    NSString *installedPath    = [[[@"~/Library/PDF Services" stringByExpandingTildeInPath] stringByAppendingPathComponent:@"Save PDF to Sankore"] stringByAppendingPathExtension:@"workflow"];
    NSString *currentVersion   = bundleShortVersion([NSBundle bundleWithPath:currentPath]);
    NSString *installedVersion = bundleShortVersion([NSBundle bundleWithPath:installedPath]);

    if (![installedVersion isEqualToString:currentVersion])
    {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        [fileManager removeFileAtPath:installedPath handler:nil];
        [fileManager createDirectoryAtPath:[installedPath stringByDeletingLastPathComponent] attributes:nil];
        BOOL copyOK = [fileManager copyPath:currentPath toPath:installedPath handler:nil];
        if (!copyOK)
        {
            qWarning("Could not install the 'Save PDF to Sankore' workflow");
        }
    }

    [pool drain];

}


void UBPlatformUtils::setDesktopMode(bool desktop)
{
    OSStatus (*functor)(SystemUIMode, SystemUIOptions) = (OSStatus (*)(SystemUIMode, SystemUIOptions))originalSetSystemUIMode;

    if (desktop)
    {
        functor(kUIModeNormal, 0);
    }
    else
    {
        functor(kUIModeAllHidden, 0);
    }

}


QString UBPlatformUtils::applicationResourcesDirectory()
{
    QString path;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *resourcePath = [[NSBundle mainBundle] resourcePath];

    path = QString::fromUtf8([resourcePath fileSystemRepresentation], strlen([resourcePath fileSystemRepresentation]));
    [pool drain];

    return path;
}

void UBPlatformUtils::hideFile(const QString &filePath)
{
    FSRef ref;
    CFStringRef path = CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(filePath.unicode()), filePath.length());
    const CFIndex maxSize = CFStringGetMaximumSizeOfFileSystemRepresentation(path);
    UInt8 fileSystemRepresentation[maxSize];
    CFRelease(path);
    if (!CFStringGetFileSystemRepresentation(path, (char*)fileSystemRepresentation, maxSize))
    {
        return;
    }

    OSStatus status = FSPathMakeRefWithOptions(fileSystemRepresentation, kFSPathMakeRefDefaultOptions, &ref, NULL);
    if (status != noErr)
    {
        return;
    }

    FSCatalogInfo catalogInfo;
    FSCatalogInfoBitmap whichInfo = kFSCatInfoFinderInfo;

    OSErr err = FSGetCatalogInfo(&ref, whichInfo, &catalogInfo, NULL, NULL, NULL);
    if (err != noErr)
    {
        return;
    }

    ((FileInfo*)(catalogInfo.finderInfo))->finderFlags |= kIsInvisible;

    FSSetCatalogInfo(&ref, whichInfo, &catalogInfo);
}

void UBPlatformUtils::setFileType(const QString &filePath, unsigned long fileType)
{
    FSRef ref;
    CFStringRef path = CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(filePath.unicode()), filePath.length());
    const CFIndex maxSize = CFStringGetMaximumSizeOfFileSystemRepresentation(path);
    UInt8 fileSystemRepresentation[maxSize];
    CFRelease(path);
    if (!CFStringGetFileSystemRepresentation(path, (char*)fileSystemRepresentation, maxSize))
    {
        return;
    }

    OSStatus status = FSPathMakeRefWithOptions(fileSystemRepresentation, kFSPathMakeRefDefaultOptions, &ref, NULL);
    if (status != noErr)
    {
        return;
    }

    FSCatalogInfo catalogInfo;
    FSCatalogInfoBitmap whichInfo = kFSCatInfoFinderInfo;

    OSErr err = FSGetCatalogInfo(&ref, whichInfo, &catalogInfo, NULL, NULL, NULL);
    if (err != noErr)
    {
        return;
    }

    ((FileInfo*)(catalogInfo.finderInfo))->fileType = fileType;
    ((FileInfo*)(catalogInfo.finderInfo))->fileCreator = 'UniB';

    FSSetCatalogInfo(&ref, whichInfo, &catalogInfo);
}

static CGDisplayFadeReservationToken token = NULL;

void UBPlatformUtils::fadeDisplayOut()
{
    if (CGAcquireDisplayFadeReservation(1.2, &token) == kCGErrorSuccess)
    {
        CGDisplayFade(token, 1.0, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, true);
    }
}

void UBPlatformUtils::fadeDisplayIn()
{
    if (CGAcquireDisplayFadeReservation(0.6, &token) == kCGErrorSuccess)
    {
        CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor,  kCGDisplayBlendNormal, 0.0, 0.0, 0.0, true);
    }
}

QString UBPlatformUtils::preferredTranslation()
{
    QString qmPath;

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *lprojPath = [[[NSBundle mainBundle] pathForResource:@"Localizable" ofType:@"strings"] stringByDeletingLastPathComponent];
    if (lprojPath)
    {
        NSString *lang = [[lprojPath lastPathComponent] stringByDeletingPathExtension];
        NSString *translationFilePath = [lprojPath stringByAppendingPathComponent:[[@"sankore_" stringByAppendingString:lang] stringByAppendingPathExtension:@"qm"]];
        qmPath = QString::fromUtf8([translationFilePath UTF8String], strlen([translationFilePath UTF8String]));
    }

    [pool drain];
    return qmPath;
}

QString UBPlatformUtils::preferredLanguage()
{
    QFileInfo qmFileInfo = QFileInfo(preferredTranslation());
    QDir lprojPath = qmFileInfo.dir();
    QFileInfo lprojFileInfo = QFileInfo(lprojPath.absolutePath());
    return lprojFileInfo.baseName();
}

void UBPlatformUtils::runInstaller(const QString &installerFilePath)
{
    UBApplication::setDisabled(true);

    // Save app config file to temp directory (will be restored at launch)
    QString appSettings = UBPlatformUtils::applicationResourcesDirectory() + "/etc/Uniboard.config";
    QString tmpSettings = QDir::tempPath() + "/Uniboard.config";
    QFile::remove(tmpSettings);
    QFile::copy(appSettings, tmpSettings);

    QString updateFilePath = QDir::tempPath() + "/upgrade.sh";

    QFile file(":/macx/upgrade.sh");
    QFile updateFile(updateFilePath);
    if (file.open(QIODevice::ReadOnly) && updateFile.open(QIODevice::WriteOnly))
    {
        QByteArray payload = file.readAll();

        updateFile.write(payload);
        updateFile.close();

        QString uniboardAndVersion = QApplication::applicationName()  + QString(" ") + QApplication::applicationVersion();

        QFileInfo fi(installerFilePath);
        uniboardAndVersion = fi.fileName().remove(".dmg");

        QString bundlePath;
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSString *nssBundlePath = [[NSBundle mainBundle] bundlePath];

        bundlePath = QString::fromUtf8([nssBundlePath fileSystemRepresentation], strlen([nssBundlePath fileSystemRepresentation]));
        [pool drain];

        QString escaped = QString("/bin/sh \"%1\" \"%2\" \"%3\" \"%4\"")
                .arg(updateFilePath)
                .arg(uniboardAndVersion)
                .arg(installerFilePath)
                .arg(bundlePath);

        qDebug() << "Installing New Version" << escaped;

        QProcess process;
        bool success = process.startDetached(escaped);

        if(success)
           return;
    }

    // did not work .. lets load the dmg ...
    QDesktopServices::openUrl(QUrl::fromLocalFile(installerFilePath));

}


void UBPlatformUtils::bringPreviousProcessToFront()
{
    ProcessSerialNumber previousProcessPSN = {0, kNoProcess};

    CFArrayRef apps = CopyLaunchedApplicationsInFrontToBackOrder();
    if (apps != NULL)
    {
        if (CFArrayGetCount(apps) > 1)
        {
            SInt64 psn64;
            CFDictionaryRef appInfo = (CFDictionaryRef)CFArrayGetValueAtIndex(apps, 1);
            CFNumberRef psn = (CFNumberRef)CFDictionaryGetValue(appInfo, CFSTR("PSN"));
            if (psn != NULL)
            {
                CFNumberGetValue(psn, kCFNumberSInt64Type, &psn64);
                previousProcessPSN.highLongOfPSN = psn64 >> 32;
                previousProcessPSN.lowLongOfPSN  = psn64 & 0xFFFFFFFF;
            }
        }
        CFRelease(apps);
    }
    else
    {
        // On 10.4, we can't get the apps in front to back order, so we default to Finder
        OSStatus status;
        ProcessSerialNumber psn = {0, kNoProcess};
        while ((status = GetNextProcess(&psn)) == noErr)
        {
            CFDictionaryRef processInfo = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
            if (processInfo != NULL)
            {
                CFStringRef bundleIdentifier = (CFStringRef)CFDictionaryGetValue(processInfo, kCFBundleIdentifierKey);
                if (bundleIdentifier && CFStringCompare(CFSTR("com.apple.finder"), bundleIdentifier, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
                {
                    previousProcessPSN.highLongOfPSN = psn.highLongOfPSN;
                    previousProcessPSN.lowLongOfPSN  = psn.lowLongOfPSN;
                }
                CFRelease(processInfo);
            }
        }
    }

    SetFrontProcess(&previousProcessPSN);
}


QString UBPlatformUtils::osUserLoginName()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *nsUserName = NSUserName();
    QString userName = QString::fromUtf8([nsUserName UTF8String], strlen([nsUserName UTF8String]));

    [pool drain];
    return userName;
}

QString UBPlatformUtils::computerName()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *nsComputerName = [[NSHost currentHost] name];
    QString computerName = QString::fromUtf8([nsComputerName UTF8String], strlen([nsComputerName UTF8String]));

    [pool drain];
    return computerName;
}

void UBPlatformUtils::setWindowNonActivableFlag(QWidget* widget, bool nonAcivable)
{
    Q_UNUSED(widget);
    Q_UNUSED(nonAcivable);
}


QString QStringFromStringRef(CFStringRef stringRef)
{
	if (stringRef!=NULL)
	{
		char tmp[1024];
		CFStringGetCString(stringRef, tmp, 1024, 0);
		return QString(tmp);
	}
	else
		return QString();
}


KEYBT* createKeyBt(const UCKeyboardLayout* keyLayout, int vkk)
{
	UInt32 deadKeyState = 0L;
	UInt32 kbdType = kKeyboardISO;

	UniCharCount cnt1, cnt2;
	UniChar unicodeString1[100], unicodeString2[100];

	UCKeyTranslate(keyLayout, vkk, kUCKeyActionDisplay, 0, kbdType,  kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 100, &cnt1, unicodeString1);
	UCKeyTranslate(keyLayout, vkk, kUCKeyActionDisplay, (shiftKey >> 8) & 0xff, kbdType,  kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 100, &cnt2, unicodeString2);

	return new KEYBT(unicodeString1[0], vkk, unicodeString2[0], vkk);
}


void UBPlatformUtils::initializeKeyboardLayouts()
{
	CFStringRef keys[] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsEnableCapable, kTISPropertyInputSourceIsSelectCapable };
	const void* values[] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue, kCFBooleanTrue };
	CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 3, NULL, NULL);
	CFArrayRef kbds = TISCreateInputSourceList(dict, false);

	int count = CFArrayGetCount(kbds);
	QList<UBKeyboardLocale*> result;

	for(int i=0; i<count; i++)
	{
		TISInputSourceRef keyLayoutRef =  (TISInputSourceRef)CFArrayGetValueAtIndex(kbds, i);
		if (keyLayoutRef==NULL)
			continue;

		CFDataRef ref = (CFDataRef) TISGetInputSourceProperty(keyLayoutRef,
					 kTISPropertyUnicodeKeyLayoutData);
		if (ref==NULL)
			continue;
		const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*) CFDataGetBytePtr(ref);
		if (keyLayoutRef==NULL)
			continue;

		KEYBT** keybt = new KEYBT*[SYMBOL_KEYS_COUNT];

		keybt[0] = createKeyBt(keyLayout, 10);
		keybt[1] = createKeyBt(keyLayout, 18);
		keybt[2] = createKeyBt(keyLayout, 19);
		keybt[3] = createKeyBt(keyLayout, 20);
		keybt[4] = createKeyBt(keyLayout, 21);
		keybt[5] = createKeyBt(keyLayout, 23);
		keybt[6] = createKeyBt(keyLayout, 22);
		keybt[7] = createKeyBt(keyLayout, 26);
		keybt[8] = createKeyBt(keyLayout, 28);
		keybt[9] = createKeyBt(keyLayout, 25);
		keybt[10] = createKeyBt(keyLayout, 29);
		keybt[11] = createKeyBt(keyLayout, 27);
		keybt[12] = createKeyBt(keyLayout, 24);

		keybt[13] = createKeyBt(keyLayout, 12);
		keybt[14] = createKeyBt(keyLayout, 13);
		keybt[15] = createKeyBt(keyLayout, 14);
		keybt[16] = createKeyBt(keyLayout, 15);
		keybt[17] = createKeyBt(keyLayout, 17);
		keybt[18] = createKeyBt(keyLayout, 16);
		keybt[19] = createKeyBt(keyLayout, 32);
		keybt[20] = createKeyBt(keyLayout, 34);
		keybt[21] = createKeyBt(keyLayout, 31);
		keybt[22] = createKeyBt(keyLayout, 35);
		keybt[23] = createKeyBt(keyLayout, 33);
		keybt[24] = createKeyBt(keyLayout, 30);

		keybt[25] = createKeyBt(keyLayout, 0);
		keybt[26] = createKeyBt(keyLayout, 1);
		keybt[27] = createKeyBt(keyLayout, 2);
		keybt[28] = createKeyBt(keyLayout, 3);
		keybt[29] = createKeyBt(keyLayout, 5);
		keybt[30] = createKeyBt(keyLayout, 4);
		keybt[31] = createKeyBt(keyLayout, 38);
		keybt[32] = createKeyBt(keyLayout, 40);
		keybt[33] = createKeyBt(keyLayout, 37);
		keybt[34] = createKeyBt(keyLayout, 41);
		keybt[35] = createKeyBt(keyLayout, 39);
		keybt[36] = createKeyBt(keyLayout, 42);

		keybt[37] = createKeyBt(keyLayout, 6);
		keybt[38] = createKeyBt(keyLayout, 7);
		keybt[39] = createKeyBt(keyLayout, 8);
		keybt[40] = createKeyBt(keyLayout, 9);
		keybt[41] = createKeyBt(keyLayout, 11);
		keybt[42] = createKeyBt(keyLayout, 45);
		keybt[43] = createKeyBt(keyLayout, 46);
		keybt[44] = createKeyBt(keyLayout, 43);
		keybt[45] = createKeyBt(keyLayout, 47);
		keybt[46] = createKeyBt(keyLayout, 44);


		CFStringRef sr = (CFStringRef) TISGetInputSourceProperty(keyLayoutRef, kTISPropertyInputSourceID);
		QString ID = QStringFromStringRef(sr);

		sr = (CFStringRef) TISGetInputSourceProperty(keyLayoutRef, kTISPropertyLocalizedName);
		QString fullName = QStringFromStringRef(sr);

		CFArrayRef langs = (CFArrayRef) TISGetInputSourceProperty(keyLayoutRef,
													  kTISPropertyInputSourceLanguages);

		QString name = "??";
		if (CFArrayGetCount(langs)>0)
		{
			CFStringRef langRef = (CFStringRef)CFArrayGetValueAtIndex(langs, 0);
			name = QStringFromStringRef(langRef);
		}

		//IconRef iconRef = (IconRef)TISGetInputSourceProperty(kTISPropertyIconRef,
		//											  kTISPropertyInputSourceLanguages);

		result.append(new UBKeyboardLocale(fullName, name, ID, NULL, keybt));
	}

	if (result.size()==0)
	{
		nKeyboardLayouts = 0;
		keyboardLayouts = NULL;
	}
	else
	{
		nKeyboardLayouts = result.size();
		keyboardLayouts = new UBKeyboardLocale*[nKeyboardLayouts];
		for(int i=0; i<nKeyboardLayouts; i++)
			keyboardLayouts[i] = result[i];
	}

}

void UBPlatformUtils::destroyKeyboardLayouts()
{}