//
//  PreferencesWindowController.m
//  Ample
//
//  Created by Kelvin Sherlock on 8/31/2020.
//  Copyright © 2020 Kelvin Sherlock. All rights reserved.
//

#import "Ample.h"
#import "PreferencesWindowController.h"

#import <Security/Security.h>


@interface PreferencesWindowController ()
@property (weak) IBOutlet NSTextField *pathField;
@property (weak) IBOutlet NSTextField *wdField;
@property (weak) IBOutlet NSButton *fixButton;

@end

@implementation PreferencesWindowController

-(NSString *)windowNibName {
    return @"Preferences";
}

- (void)windowDidLoad {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [super windowDidLoad];
    
    // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
    
    [self validateMamePath: [defaults stringForKey: kMamePath]];
    [self validateMameWD: [defaults stringForKey: kMameWorkingDirectory]];

    /* check vmnet_helper permissions */
    
    int needs_fixin = [self checkHelperPermissions: nil];
    [_fixButton setEnabled: needs_fixin > 0];
}

-(void)validateMamePath: (NSString *)path {
    NSFileManager * fm = [NSFileManager defaultManager];

    if ([path length] == 0 || [fm isExecutableFileAtPath: path]) {
        [_pathField setTextColor: nil];
    } else {
        [_pathField setTextColor: [NSColor systemRedColor]];
    }
}

-(void)validateMameWD: (NSString *)path {
    NSFileManager * fm = [NSFileManager defaultManager];
    BOOL directory = YES;
    
    if ([path length] == 0) {
        [_wdField setTextColor: nil];
        return;
    }
    
    if ([fm fileExistsAtPath: path isDirectory: &directory] && directory) {
        [_wdField setTextColor: nil];
        return;

    }
    [_wdField setTextColor: [NSColor systemRedColor]];
}

- (IBAction)pathChanged:(id)sender {

    NSString *path = [sender stringValue];
    
    [self validateMamePath: path];

}
- (IBAction)wdChanged:(id)sender {

    NSString *path = [sender stringValue];
    
    [self validateMameWD: path];
}

// -1 - error
// 1 - needs help
// 0 - a-ok
-(int)checkHelperPermissions: (NSString *)path {

    static const unsigned Mask = S_ISUID | S_ISGID;
    if (!path) {
        NSBundle *bundle = [NSBundle mainBundle];
        path = [bundle pathForAuxiliaryExecutable: @"vmnet_helper"];
    }
    if (!path) return -1;

    NSFileManager *fm = [NSFileManager defaultManager];
    NSError *error = nil;
    NSDictionary *attr = [fm attributesOfItemAtPath: path error: &error];
    
    if (error) return -1;

    NSNumber *owner = [attr objectForKey: NSFileOwnerAccountID];
    NSNumber *perm = [attr objectForKey: NSFilePosixPermissions];
    if ([owner longValue] == 0 && ([perm unsignedIntValue] & Mask) == Mask) return 0;
    return 1;
}

- (IBAction)fixPerms:(id)sender {

    NSBundle *bundle = [NSBundle mainBundle];
    NSString *path = [bundle pathForAuxiliaryExecutable: @"vmnet_helper"];
    if (!path) return;
    
    
#if 0
    // this requires an entitlement and sanboxing and Apple's permission.
    NSWorkspace *ws = [NSWorkspace sharedWorkspace];
    
    [ws requestAuthorizationOfType:NSWorkspaceAuthorizationTypeSetAttributes
                 completionHandler: ^(NSWorkspaceAuthorization *a, NSError *e){
        if (e || !a) return;

        NSError *error = nil;
        NSDictionary *attr = @{
            NSFileOwnerAccountID: @0, /* root */
            NSFileGroupOwnerAccountID: @20, /* staff */
//            NSFilePosixPermissions: @0106755 /* 755 + setuid + setgid */
        };


        
        NSFileManager *fm = [NSFileManager fileManagerWithAuthorization: a];
        [fm setAttributes: attr ofItemAtPath: path error: &error];
        if (error) {
            NSLog(@"%@", error);
//            NSAlert *a = [NSAlert alertWithError: error];
//            [a runModal];
        }
        else {
            [self->_fixButton setEnabled: NO];
        }
        
    }];
#endif

    // AuthorizationExecuteWithPrivileges - deprecated in 10.7
    // https://github.com/sveinbjornt/STPrivilegedTask
    // XMJobBless + launchd stuff - the preferred way to do it...
    // https://developer.apple.com/library/archive/samplecode/BetterAuthorizationSample/Introduction/Intro.html
    // https://developer.apple.com/library/archive/samplecode/SMJobBless/Listings/ReadMe_txt.html#//apple_ref/doc/uid/DTS40010071-ReadMe_txt-DontLinkElementID_3
    //
    // really should be a launchd service but that's for another time...
   
    AuthorizationRef myAuthorizationRef = 0;
    OSStatus myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef);
    if (myStatus) return;

    AuthorizationItem myItems[1] = {{0}};
    myItems[0].name = kAuthorizationRightExecute;
    myItems[0].valueLength = 0;
    myItems[0].value = NULL;
    myItems[0].flags = 0;
    AuthorizationRights myRights = {0};
    myRights.count = sizeof(myItems) / sizeof(myItems[0]);
    myRights.items = myItems;
    AuthorizationFlags myFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed |
                                 kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize;
    myStatus = AuthorizationCopyRights(myAuthorizationRef, &myRights,
                                                             kAuthorizationEmptyEnvironment, myFlags, NULL);

    if (!myStatus) {
        FILE *fp = NULL;
        static char buffer[4096];
        const char *cp = [path fileSystemRepresentation];
        const char* args_chown[] = {"root", cp , NULL};
        const char* args_chmod[] = {"+s", cp, NULL};
        
        // well ... the second command executes a lot more consistently when the (optional) fp is provided and the we fgets the buffer.
        // textmate does a wait() to wait for a child to exit. Using the fp (which is popened()?) and reading until EOF
        // accomplishes the same thing.
        myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/usr/sbin/chown", kAuthorizationFlagDefaults, (char**)args_chown, &fp);
        while (fgets(buffer, sizeof(buffer), fp));
        fclose(fp);
//        fprintf(stderr, "myStatus = %d\ndata: %s\n", myStatus, buffer);

        myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/chmod", kAuthorizationFlagDefaults, (char**)args_chmod, &fp);
        while (fgets(buffer, sizeof(buffer), fp));
        fclose(fp);
//        fprintf(stderr, "myStatus = %d\ndata: %s\n", myStatus, buffer);

    }
    AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDestroyRights);
    
    int needs_fixin = [self checkHelperPermissions: path];
    [_fixButton setEnabled: needs_fixin > 0];
}


@end
