//
//  SettingsMenuController.m
//  mkxp-z
//
//  Created by ゾロアーク on 1/15/21.
//

// This is a pretty rudimentary keybinding menu, and it replaces the normal one
// for macOS. The normal one basically just doesn't seem to work with ANGLE,
// so I cooked this one up in a hurry despite knowing next to zero about Xcode's
// interface builder in general.

// Yes, it is still a mess, but it is working.

#import <GameController/GameController.h>

#import <SDL_scancode.h>
#import <SDL_keyboard.h>
#import <SDL_video.h>

#import "sdl_codes.h"

#import "SettingsMenuController.h"

#import "input/input.h"
#import "input/keybindings.h"

#import "eventthread.h"
#import "sharedstate.h"
#import "config.h"

#import "assert.h"

static const int inputMapRowToCode[] {
    Input::Down, Input::Left, Input::Right, Input::Up,
    Input::A, Input:: B, Input::ZL, Input::X, Input::Y, Input::ZR,
    Input::L, Input::R
};

typedef NSMutableArray<NSNumber*> BindingIndexArray;

@implementation SettingsMenu {
    __weak IBOutlet NSWindow *_window;
    __weak IBOutlet NSTableView *_table;
    __weak IBOutlet NSBox *bindingBox;
    
    // Binding buttons
    __weak IBOutlet NSButton *bindingButton1;
    __weak IBOutlet NSButton *bindingButton2;
    __weak IBOutlet NSButton *bindingButton3;
    __weak IBOutlet NSButton *bindingButton4;
    
    // For keeping track of which buttons to check for
    int sysver;
    
    
    // MKXP Keybindings
    BDescVec *binds;
    int currentButtonCode;
    
    // Whether currently waiting for some kind of input
    bool isListening;
    
    // For the current binding selection when the table is
    // reloaded from deleting/adding keybinds
    bool keepCurrentButtonSelection;
    
    NSMutableDictionary<NSNumber*, BindingIndexArray*> *nsbinds;
    NSMutableDictionary<NSNumber*, NSString*> *bindingNames;
}

+(SettingsMenu*)openWindow {
    SettingsMenu *s = [[SettingsMenu alloc] initWithNibName:@"settingsmenu" bundle:NSBundle.mainBundle];
    // Show the window as a sheet, window events will be sucked up by SDL though
    //[NSApplication.sharedApplication.mainWindow beginSheet:s.view.window completionHandler:^(NSModalResponse _){}];
    
    // Show the view in a new window instead, so key and controller events
    // can be captured without SDL's interference
    NSWindow *win = [NSWindow windowWithContentViewController:s];
    win.styleMask &= ~NSWindowStyleMaskResizable;
    win.styleMask &= ~NSWindowStyleMaskFullScreen;
    win.styleMask &= NSWindowStyleMaskTitled;
    win.title = @"Keybindings";
    [s setWindow:win];
    [win orderFront:self];
    
    return s;
}

-(SettingsMenu*)raise {
    if (_window)
        [_window orderFront:self];
    return self;
}

-(void)closeWindow {
    [self setNotListening:true];
    [_window close];
}

-(SettingsMenu*)setWindow:(NSWindow*)window {
    _window = window;
    return self;
}

- (IBAction)acceptButton:(NSButton *)sender {
    shState->rtData().bindingUpdateMsg.post(*binds);
    storeBindings(*binds, shState->config());
    [self closeWindow];
}
- (IBAction)cancelButton:(NSButton *)sender {
    [self closeWindow];
}
- (IBAction)resetBindings:(NSButton *)sender {
    binds->clear();
    BDescVec tmp = genDefaultBindings(shState->config());
    binds->assign(tmp.begin(), tmp.end());
    
    [self setNotListening:false];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    sysver = 12;
    if (@available(macOS 10.14, *)) sysver += 2;
    if (@available(macOS 10.15, *)) sysver++;
    if (@available(macOS 11.0, *)) sysver++;
    
    isListening = false;
    keepCurrentButtonSelection = false;
    
    [self initDelegateWithTable:_table];
    _table.delegate = self;
    _table.dataSource = self;
    [self setNotListening:true];
    _table.enabled = true;
    
    bindingBox.title = @"";
    [self setButtonNames:0];
}

- (void)keyDown:(NSEvent *)event {
    [super keyDown:event];
    if (!isListening) return;
    
    BindingDesc d;
    d.target = (Input::ButtonCode)currentButtonCode;
    SourceDesc s;
    s.type = Key;
    s.d.scan = darwin_scancode_table[event.keyCode];
    d.src = s;
    binds->push_back(d);
    [self setNotListening:true];
}

#define checkButtonStart if (0) {}
#define checkButtonEnd else { return; }
#define checkButtonElement(e, b, n, min) \
else if (sysver >= min && element == gamepad.e && gamepad.b.isPressed) { \
s.type = JButton; \
s.d.jb = n; \
}

#define checkButtonAlt(b, n, min) checkButtonElement(b, b, n, min)

#define checkButton(b, n, min) checkButtonAlt(button##b, n, min)

#define setAxisData(a, n) \
GCControllerAxisInput *axis = gamepad.a; \
s.type = JAxis; \
s.d.ja.axis = n; \
s.d.ja.dir = (axis.value >= 0) ? AxisDir::Positive : AxisDir::Negative;

#define checkAxis(el, a, n) else if (element == gamepad.el && (gamepad.el.a.value >= 0.5 || gamepad.el.a.value <= -0.5)) { setAxisData(el.a, n); }

- (void)registerJoystickAction:(GCExtendedGamepad*)gamepad element:(GCControllerElement*)element {
    if (!isListening) return;
    
    BindingDesc d;
    d.target = (Input::ButtonCode)currentButtonCode;
    SourceDesc s;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
    checkButtonStart
    checkButton(A, 0, 12)
    checkButton(B, 1, 12)
    checkButton(X, 2, 12)
    checkButton(Y, 3, 12)
    checkButtonElement(dpad, dpad.up, 11, 12)
    checkButtonElement(dpad, dpad.down, 12, 12)
    checkButtonElement(dpad, dpad.left, 13, 12)
    checkButtonElement(dpad, dpad.right, 14, 12)
    checkButtonAlt(leftShoulder, 9, 12)
    checkButtonAlt(rightShoulder, 10, 12)
    
    // Requires macOS 10.14.1+
    checkButtonAlt(leftThumbstickButton, 7, 14)
    checkButtonAlt(rightThumbstickButton, 8, 14)
    
    // Requires macOS 10.15+
    checkButton(Menu, 6, 15)
    checkButton(Options, 4, 15)
    
    // Requires macOS 11.0+
    checkButton(Home, 5, 16)
    
    checkAxis(leftThumbstick, xAxis, 0)
    checkAxis(leftThumbstick, yAxis, 1)
    checkAxis(rightThumbstick, xAxis, 2)
    checkAxis(rightThumbstick, yAxis, 3)
    
    else if (element == gamepad.leftTrigger && (gamepad.leftTrigger.value >= 0.5 || gamepad.leftTrigger.value <= -0.5)) {
        s.type = JAxis;
        s.d.ja.axis = 4;
        s.d.ja.dir = AxisDir::Positive;
    }
    
    else if (element == gamepad.rightTrigger && (gamepad.rightTrigger.value >= 0.5 || gamepad.rightTrigger.value <= -0.5)) {
        s.type = JAxis;
        s.d.ja.axis = 5;
        s.d.ja.dir = AxisDir::Positive;
    }
    
    checkButtonEnd;
    
#pragma clang diagnostic pop
    d.src = s;
    binds->push_back(d);
    [self setNotListening:true];
}

+(NSString*)nameForBinding:(SourceDesc&)desc {
    switch (desc.type) {
        case Key:
            if (desc.d.scan == SDL_SCANCODE_LSHIFT) return @"Shift";
            return @(SDL_GetScancodeName(desc.d.scan));
        case JHat:
            const char *dir;
            switch (desc.d.jh.pos) {
                case SDL_HAT_UP:
                    dir = "Up";
                    break;
                case SDL_HAT_DOWN:
                    dir = "Down";
                    break;
                case SDL_HAT_LEFT:
                    dir = "Left";
                    break;
                case SDL_HAT_RIGHT:
                    dir = "Right";
                    break;
                default:
                    dir = "-";
                    break;
            }
            return [NSString stringWithFormat:@"JS Hat %d:%s", desc.d.jh.hat, dir];
        case JAxis:
            return [NSString stringWithFormat:@"JS Axis %d%s", desc.d.ja.axis, desc.d.ja.dir == Negative ? "-" : "+"];
        case JButton:
            return [NSString stringWithFormat:@"JS Button %i", desc.d.jb];
        default:
            return @"Invalid";
    }
}

-(id)initDelegateWithTable:(NSTableView*)tbl {
    binds = new BDescVec;
    nsbinds = [NSMutableDictionary new];
    bindingNames = [NSMutableDictionary new];
    
    RGSSThreadData &data = shState->rtData();
    
#define SET_BINDING(code) bindingNames[@(Input::code)] = @(#code)
#define SET_BINDING_CUSTOM(code, value) bindingNames[@(Input::code)] = @(value)
    SET_BINDING(Down);
    SET_BINDING(Left);
    SET_BINDING(Right);
    SET_BINDING(Up);
    SET_BINDING(A);
    SET_BINDING(B);
    SET_BINDING_CUSTOM(ZL, "C");
    SET_BINDING(X);
    SET_BINDING(Y);
    SET_BINDING_CUSTOM(ZR, "Z");
    SET_BINDING(L);
    SET_BINDING(R);
    
#define SET_BINDING_CONF(code, value) \
if (!data.config.kbActionNames.value.empty()) bindingNames[@(Input::code)] = \
    @(data.config.kbActionNames.value.c_str())
    SET_BINDING_CONF(A,a);
    SET_BINDING_CONF(B,b);
    SET_BINDING_CONF(ZL,c);
    SET_BINDING_CONF(X,x);
    SET_BINDING_CONF(Y,y);
    SET_BINDING_CONF(ZR,z);
    SET_BINDING_CONF(L,l);
    SET_BINDING_CONF(R,r);
    
    BDescVec oldBinds;
    data.bindingUpdateMsg.get(oldBinds);
    
    if (oldBinds.size() <= 0) {
        BDescVec defaults = genDefaultBindings(data.config);
        binds->assign(defaults.begin(), defaults.end());
    }
    else {
        binds->assign(oldBinds.begin(), oldBinds.end());
    }
    
    [self loadBinds];
    [self enableButtons:false];
    
    return self;
}

- (void) loadBinds {
    [nsbinds removeAllObjects];
    
    for (int i = 0; i < sizeof(inputMapRowToCode) / sizeof(Input::ButtonCode); i++)
        nsbinds[@(inputMapRowToCode[i])] = [NSMutableArray new];
    
    for (int i = 0; i < binds->size(); i++) {
        NSNumber *key = @(binds->at(i).target);
        if (nsbinds[key] == nil) {
            nsbinds[key] = [NSMutableArray new];
        }
        NSMutableArray *b = nsbinds[key];
        [b addObject:@(i)];
    }
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    return nsbinds.count;
}

- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    NSString *tableID = tableColumn.identifier;
    int buttonCode = inputMapRowToCode[row];
    
    NSTableCellView *cell = [tableView makeViewWithIdentifier:tableID owner:self];
    if ([tableID isEqualToString:@"action"]) {
        cell.textField.stringValue = bindingNames[@(inputMapRowToCode[row])];
        cell.textField.font = [NSFont boldSystemFontOfSize:cell.textField.font.pointSize];
        return cell;
    }
    
    long col = tableID.integerValue;
    
    if (nsbinds[@(buttonCode)].count < col) {
        cell.textField.stringValue = @"";
        return cell;
    }
    
    NSNumber *d = nsbinds[@(buttonCode)][col-1];
    BindingDesc &bind = binds->at(d.intValue);
    cell.textField.stringValue = [SettingsMenu nameForBinding:bind.src];
    return cell;
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    NSString *tableID = tableColumn.identifier;
    int buttonCode = inputMapRowToCode[row];
    
    if ([tableID isEqualToString:@"action"]) {
        return @(buttonCode);
    }
    
    long col = tableID.integerValue;
    
    if (nsbinds[@(buttonCode)].count < col) {
        return 0;
    }
    
    NSNumber *d = nsbinds[@(buttonCode)][col-1];
    BindingDesc &bind = binds->at(d.intValue);
    return @(bind.src.d.scan);
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    if (isListening)
        isListening = false;
    
    if (keepCurrentButtonSelection) {
        if (keepCurrentButtonSelection) keepCurrentButtonSelection = false;
        [_table deselectRow:_table.selectedRow];
        [self setButtonNames:currentButtonCode];
        bindingBox.title = bindingNames[@(currentButtonCode)];
        return;
    }
    
    if (_table.selectedRow > -1) {
        int buttonCode = inputMapRowToCode[_table.selectedRow];
        currentButtonCode = buttonCode;
        [self setButtonNames:buttonCode];
        bindingBox.title = bindingNames[@(currentButtonCode)];
    }
    else {
        [self setButtonNames:0];
        bindingBox.title = @"";
    }
}

- (int)setButtonNames:(int)input {
    if (!input) {
        bindingButton1.title = @"";
        bindingButton2.title = bindingButton1.title;
        bindingButton3.title = bindingButton1.title;
        bindingButton4.title = bindingButton1.title;
        [self enableButtons:false];
        return 0;
    }
    BindingIndexArray *nsbind = nsbinds[@(input)];
    NSMutableArray<NSString*> *pnames = [NSMutableArray new];
    for (int i = 0; i < 4; i++) {
        if (!nsbind.count || i > nsbind.count - 1) {
            [pnames addObject:@"(Empty)"];
        }
        else {
            BindingDesc &b = binds->at(nsbind[i].intValue);
            NSString *bindingName = [SettingsMenu nameForBinding:b.src];
            [pnames addObject:bindingName];
        }
    }
    
    bindingButton1.title = pnames[0];
    bindingButton2.title = pnames[1];
    bindingButton3.title = pnames[2];
    bindingButton4.title = pnames[3];
    [self enableButtons:true];
    
    return (int)pnames.count;
}

- (void)enableButtons:(bool)defaultSetting {
    BindingIndexArray *currentBind = nsbinds[@(currentButtonCode)];
    bindingButton1.enabled = defaultSetting;
    bindingButton2.enabled = defaultSetting && currentBind.count > 0;
    
    bindingButton3.enabled = defaultSetting && currentBind.count > 1;
    bindingButton4.enabled = defaultSetting && currentBind.count > 2;
}

- (IBAction)binding1Clicked:(NSButton *)sender {
    if (nsbinds[@(currentButtonCode)].count > 0) {
        [self removeBinding:0 forInput:currentButtonCode];
        return;
    }
    [self setListening:sender];
}
- (IBAction)binding2Clicked:(NSButton *)sender {
    if (nsbinds[@(currentButtonCode)].count > 1) {
        [self removeBinding:1 forInput:currentButtonCode];
        return;
    }
    [self setListening:sender];
}
- (IBAction)binding3Clicked:(NSButton *)sender {
    if (nsbinds[@(currentButtonCode)].count > 2) {
        [self removeBinding:2 forInput:currentButtonCode];
        return;
    }
    [self setListening:sender];
}
- (IBAction)binding4Clicked:(NSButton *)sender {
    if (nsbinds[@(currentButtonCode)].count > 3) {
        [self removeBinding:3 forInput:currentButtonCode];
        return;
    }
    [self setListening:sender];
}

- (void)removeBinding:(int)bindIndex forInput:(int)input {
    NSMutableArray<NSNumber*> *bind = nsbinds[@(input)];
    int bi = bind[bindIndex].intValue;
    binds->erase(binds->begin() + bi);
    
    [self setNotListening:true];
}

- (void)setListening:(NSButton*)src {
    if (isListening) {
        [self setNotListening:true];
        return;
    }
    
    // Stops receiving keyDown events if it's disabled, apparently
    //_table.enabled = false;
    
    [self enableButtons:false];
    
    if (src == nil) return;
    
    src.title = @"Click to Cancel...";
    src.enabled = true;
    isListening = true;
    
    NSArray<GCController*>* controllers = [GCController controllers];
    if (controllers.count <= 0) return;
    GCController *gamepad = controllers[0];
    if (gamepad.extendedGamepad == nil || gamepad.extendedGamepad.valueChangedHandler != nil) return;
    gamepad.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element)
    {[self registerJoystickAction:gamepad element:element];};
}

- (void)setNotListening:(bool)keepCurrentSelection {
    [self loadBinds];
    
    keepCurrentButtonSelection = keepCurrentSelection;
    isListening = false;
    [_table reloadData];
    [self setButtonNames:currentButtonCode];
    
}

-(void)dealloc {
    delete binds;
}

@end
