#import <stdio.h>
#import "unarchive.h"
@import Foundation;
#import "uicache.h"
#import <sys/stat.h>
#import <dlfcn.h>
#import <spawn.h>
#import "path.h"
#import "CoreServices.h"
#import <objc/runtime.h>

#define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer")

typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
typedef void (*_CFPreferencesSetValueWithContainerType)(CFStringRef key, CFPropertyListRef value, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
typedef Boolean (*_CFPreferencesSynchronizeWithContainerType)(CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
typedef CFArrayRef (*_CFPreferencesCopyKeyListWithContainerType)(CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
typedef CFDictionaryRef (*_CFPreferencesCopyMultipleWithContainerType)(CFArrayRef keysToFetch, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);

extern char*** _NSGetArgv();
NSString* safe_getExecutablePath()
{
	char* executablePathC = **_NSGetArgv();
	return [NSString stringWithUTF8String:executablePathC];
}

NSDictionary* infoDictionaryForAppPath(NSString* appPath)
{
	NSString* infoPlistPath = [appPath stringByAppendingPathComponent:@"Info.plist"];
    return [NSDictionary dictionaryWithContentsOfFile:infoPlistPath];
}

NSString* appIdForAppPath(NSString* appPath)
{
	return infoDictionaryForAppPath(appPath)[@"CFBundleIdentifier"];
}

NSString* appPathForAppId(NSString* appId, NSError** error)
{
	NSString* appPath = [TROLLSTORE_APPLICATIONS_PATH stringByAppendingPathComponent:appId];
    
    NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appPath error:error];
    if(!items) return nil;
    
    for(NSString* item in items)
    {
        if([item.pathExtension isEqualToString:@"app"])
        {
            return [appPath stringByAppendingPathComponent:item];
        }
    }
    
    return nil;
}

static void dump_file_content(int fd)
{
    ssize_t num_read;
    char line_buf[256];
    int cur_pos = 0;

    for (;;)
    {
        char c;
        num_read = read(fd, &c, sizeof(c));
        if(num_read <= 0)

        if(c == '\n' || cur_pos >= 255 || num_read <= 0)
        {
            line_buf[cur_pos] = '\n';
            NSLog(@"%s", (char*)line_buf);
            if(c == '\n') cur_pos++;

            if(num_read > 0)
            {
                continue;
            }
            else
            {
                break;
            }
        }

        line_buf[cur_pos++] = c;
    }
}

BOOL signApp(NSString* appPath, NSError** error)
{
    NSString* ldidPath = [[safe_getExecutablePath() stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"ldid"];
    if(![[NSFileManager defaultManager] fileExistsAtPath:ldidPath])
    {
        NSLog(@"WARNING: ldid not found, not signing application");
        return NO;
    }

    NSString* certPath = [TROLLSTORE_MAIN_PATH stringByAppendingPathComponent:@"TrollStore.app/cert.p12"];
    NSString* certArg = [@"-K" stringByAppendingPathComponent:certPath];

    int out[2];
    posix_spawn_file_actions_t action;
    posix_spawn_file_actions_init(&action);
    pipe(out);
    posix_spawn_file_actions_adddup2(&action, out[1], STDERR_FILENO);
    posix_spawn_file_actions_addclose(&action, out[0]);

    char* args[] = { "ldid", "-S", "-M", (char*)certArg.UTF8String, (char*)appPath.UTF8String, NULL };

    NSLog(@"%@ ldid -S -M %@ %@", ldidPath, certArg, appPath);

    pid_t task_pid;
    int status = -200;
    int spawnError = posix_spawn(&task_pid, [ldidPath UTF8String], &action, NULL, args, NULL);
    
    if(spawnError != 0)
    {
        NSLog(@"posix_spawn error %d\n", spawnError);
        return spawnError;
    }
    
    waitpid(task_pid, &status, WEXITED);

    NSLog(@"ldid exited with status %d", status);
    
    waitpid(task_pid, &status, 0);

    NSLog(@"ldid exited with status %d", status);

    NSLog(@"ldid output:");

    close(out[1]);
    dump_file_content(out[0]);

    NSLog(@"end ldid output:");

	return status == 0;
}

BOOL installApp(NSString* appPath, NSString* appId, BOOL sign, NSError** error)
{
    if(sign)
    {
        // if it fails to sign, we don't care
	    signApp(appPath, error);
    }

    BOOL existed;
    NSError* mcmError;
    MCMAppContainer* appContainer = [objc_getClass("MCMAppContainer") containerWithIdentifier:appId createIfNecessary:YES existed:&existed error:&mcmError];
    NSLog(@"installApp appContainer: %@, mcmError: %@", appContainer, mcmError);
    if(!appContainer || mcmError)
    {
        if(error) *error = mcmError;
        return NO;
    }

    //TODO: if TrollStore, preserve by moving it into appPath if needed ldid if needed

    NSURL* trollStoreMarkURL = [appContainer.url URLByAppendingPathComponent:@"_TrollStore"];
    if(existed)
    {
        // trying to update an app not installed by TrollStore... bailing out
        if(![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil])
        {
            NSLog(@"installApp already installed and not a TrollStore app... bailing out");
            return NO;
        }
        else
        {
            // update existing app... clean old app directory
            NSLog(@"installApp found existing TrollStore app, cleaning directory");
            NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:appContainer.url includingPropertiesForKeys:nil options:0 errorHandler:nil];
            NSURL* fileURL;
            while(fileURL = [enumerator nextObject])
            {
                [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
            }
        }
    }

    [[NSFileManager defaultManager] createFileAtPath:trollStoreMarkURL.path contents:[NSData data] attributes:nil];
    
    NSString* newAppPath = [appContainer.url.path stringByAppendingPathComponent:appPath.lastPathComponent];

    NSLog(@"installApp new app path: %@", newAppPath);
    
    BOOL suc = [[NSFileManager defaultManager] copyItemAtPath:appPath toPath:newAppPath error:error];

    NSLog(@"installApp copied app? %d, adding to uicache now...", suc);

	registerPath(newAppPath, NO);

    return YES;
}

BOOL uninstallApp(NSString* appId, NSError** error)
{
	NSString* appPath = appPathForAppId(appId, error);
    if(!appPath) return NO;

    registerPath(appPath, YES);

    return [[NSFileManager defaultManager] removeItemAtPath:[appPath stringByDeletingLastPathComponent] error:error];
}

BOOL installIpa(NSString* ipaPath, NSError** error)
{
	BOOL suc = NO;
    NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
    
    suc = [[NSFileManager defaultManager] createDirectoryAtPath:tmpPath withIntermediateDirectories:NO attributes:nil error:error];
    if(!suc) return NO;

    extract(ipaPath, tmpPath);

    NSString* tmpPayloadPath = [tmpPath stringByAppendingPathComponent:@"Payload"];
    
    NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpPayloadPath error:error];
    if(!items) return NO;
    
    NSString* tmpAppPath;
    for(NSString* item in items)
    {
        if([item.pathExtension isEqualToString:@"app"])
        {
            tmpAppPath = [tmpPayloadPath stringByAppendingPathComponent:item];
            break;
        }
    }
    if(!tmpAppPath) return NO;
    
    NSString* appId = appIdForAppPath(tmpAppPath);
    
	suc = installApp(tmpAppPath, appId, YES, error);
	
	[[NSFileManager defaultManager] removeItemAtPath:tmpAppPath error:nil];

	return suc;
}

void uninstallAllApps(void)
{
    NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:TROLLSTORE_APPLICATIONS_PATH error:nil];
    for(NSString* appId in items)
    {
        NSString* appPath = appPathForAppId(appId, nil);
        registerPath(appPath, YES);
    }

    [[NSFileManager defaultManager] removeItemAtPath:TROLLSTORE_ROOT_PATH error:nil];
}

BOOL uninstallTrollStore(void)
{
    NSString* trollStore = [TROLLSTORE_MAIN_PATH stringByAppendingPathComponent:@"TrollStore.app"];
    if(![[NSFileManager defaultManager] fileExistsAtPath:trollStore]) return NO;

    registerPath(trollStore, YES);
    return [[NSFileManager defaultManager] removeItemAtPath:trollStore error:nil];
}

BOOL installTrollStore(NSString* pathToTar)
{
    _CFPreferencesSetValueWithContainerType _CFPreferencesSetValueWithContainer = (_CFPreferencesSetValueWithContainerType)dlsym(RTLD_DEFAULT, "_CFPreferencesSetValueWithContainer");
    _CFPreferencesSynchronizeWithContainerType _CFPreferencesSynchronizeWithContainer = (_CFPreferencesSynchronizeWithContainerType)dlsym(RTLD_DEFAULT, "_CFPreferencesSynchronizeWithContainer");
    _CFPreferencesSetValueWithContainer(CFSTR("SBShowNonDefaultSystemApps"), kCFBooleanTrue, CFSTR("com.apple.springboard"), CFSTR("mobile"), kCFPreferencesAnyHost, kCFPreferencesNoContainer);
    _CFPreferencesSynchronizeWithContainer(CFSTR("com.apple.springboard"), CFSTR("mobile"), kCFPreferencesAnyHost, kCFPreferencesNoContainer);

    if(![[NSFileManager defaultManager] fileExistsAtPath:pathToTar]) return NO;
    if(![pathToTar.pathExtension isEqualToString:@"tar"]) return NO;

    NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
    BOOL suc = [[NSFileManager defaultManager] createDirectoryAtPath:tmpPath withIntermediateDirectories:NO attributes:nil error:nil];
    if(!suc) return NO;

    extract(pathToTar, tmpPath);

    NSLog(@"installTrollStore extracted %@ to %@", pathToTar, tmpPath);

    NSString* tmpTrollStore = [tmpPath stringByAppendingPathComponent:@"TrollStore.app"];
    if(![[NSFileManager defaultManager] fileExistsAtPath:tmpTrollStore]) return NO;

    NSLog(@"installTrollStore temp TrollStore path: %@", tmpTrollStore);

    NSString* tmpTrollStoreMain = [tmpTrollStore stringByAppendingPathComponent:@"TrollStore"];
    NSString* tmpTrollStoreRootHelper = [tmpTrollStore stringByAppendingPathComponent:@"trollstorehelper"];
    NSString* tmpTrollStoreLdid = [tmpTrollStore stringByAppendingPathComponent:@"ldid"];

    // make executable
    chmod(tmpTrollStoreMain.UTF8String, 0755); 
    chmod(tmpTrollStoreRootHelper.UTF8String, 0755);
    chmod(tmpTrollStoreLdid.UTF8String, 0755);

    // set owners
    chown(tmpTrollStoreMain.UTF8String, 33, 33);
    chown(tmpTrollStoreRootHelper.UTF8String, 0, 0); // set root helper binary owner to root
    chown(tmpTrollStoreLdid.UTF8String, 0, 0);

    NSLog(@"installTrollStore extracted and prepared TrollStore app, now installing...");

    installApp(tmpTrollStore, @"com.apple.TrollStore", NO, nil);

    [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil];

    return YES;
}

int main(int argc, char *argv[], char *envp[]) {
	@autoreleasepool {
		if(argc <= 1) return -1;

        NSLog(@"trollstore helper go, uid: %d, gid: %d", getuid(), getgid());
        NSLog(@"ok %d", argc);

        NSBundle* mcmBundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"];
        [mcmBundle load];

		BOOL suc = NO;
		NSError* error;

		NSString* cmd = [NSString stringWithUTF8String:argv[1]];
		if([cmd isEqualToString:@"install"])
		{
			if(argc <= 2) return -2;
			NSString* ipaPath = [NSString stringWithUTF8String:argv[2]];
			suc = installIpa(ipaPath, &error);
		} else if([cmd isEqualToString:@"uninstall"])
		{
			if(argc <= 2) return -2;
			NSString* appId = [NSString stringWithUTF8String:argv[2]];
			suc = uninstallApp(appId, &error);
		} else if([cmd isEqualToString:@"install-trollstore"])
		{
            if(argc <= 2) return -2;
            NSString* tsTar = [NSString stringWithUTF8String:argv[2]];
			suc = installTrollStore(tsTar);
            NSLog(@"installed troll store? %d", suc);
		} else if([cmd isEqualToString:@"uninstall-trollstore"])
        {
            uninstallTrollStore();
            uninstallAllApps();
        }

		if(!suc)
		{
			NSLog(@"error: %@", error);
		}

		return !suc;
	}
}