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

#import "SlotViewController.h"

const unsigned kMemoryMask = 1 << 16;

@interface SlotViewController () {

    unsigned _slots_explicit;
    unsigned _slots_valid;
    unsigned _slots_default;
    
    NSDictionary *_slot_object[14];
    NSDictionary *_slot_media[14];
    NSDictionary *_machine_media;
}

@property (weak) IBOutlet NSPopUpButton *ram_menu;
@property (weak) IBOutlet NSPopUpButton *sl0_menu;
@property (weak) IBOutlet NSPopUpButton *sl1_menu;
@property (weak) IBOutlet NSPopUpButton *sl2_menu;
@property (weak) IBOutlet NSPopUpButton *sl3_menu;
@property (weak) IBOutlet NSPopUpButton *sl4_menu;
@property (weak) IBOutlet NSPopUpButton *sl5_menu;
@property (weak) IBOutlet NSPopUpButton *sl6_menu;
@property (weak) IBOutlet NSPopUpButton *sl7_menu;
@property (weak) IBOutlet NSPopUpButton *exp_menu;
@property (weak) IBOutlet NSPopUpButton *aux_menu;
@property (weak) IBOutlet NSPopUpButton *rs232_menu;
@property (weak) IBOutlet NSPopUpButton *game_menu;
@property (weak) IBOutlet NSPopUpButton *printer_menu;
@property (weak) IBOutlet NSPopUpButton *modem_menu;


@end

@implementation SlotViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do view setup here.

    //[self setModel: @"apple2gs"];
}

-(void)reset {
    
}


-(void)setModel:(NSString *)model {
    
    if (model == _model) return;
    if ([model isEqualToString: _model]) return;
    
    _model = model;

    [self loadMachine: model];
}

-(void)resetMachine {

    [self setMachine: @{}];
#if 0
    [self setSl0: @""];
    [self setSl1: @""];
    [self setSl2: @""];
    [self setSl3: @""];
    [self setSl4: @""];
    [self setSl5: @""];
    [self setSl6: @""];
    [self setSl7: @""];

    [self setRs232: @""];
    [self setAux: @""];
    [self setExp: @""];
    [self setGameio: @""];
    [self setPrinter: @""];
    [self setModem: @""];

    [self setMemory: @""];
    [self setMemoryBytes: 0];
#endif
    [self setResolution: NSMakeSize(0, 0)];

    _slots_default = 0;
    //_slots_explicit = 0;
    _slots_valid = 0;

    _machine_media = nil;
    
    [self setArgs: @[]];
    [self setMedia: @{}];
    

#if 0
    // retain for later?
    for (unsigned i = 0; i < 14; ++i) {
        _slot_media[i] = nil;
        _slot_object[i] = nil;
    }
#endif
}

static NSFont *ItalicMenuFont(void) {
    NSFont *font = [NSFont menuFontOfSize: 0];
    NSFontDescriptor *fd = [font fontDescriptor];
    NSFontDescriptor *fd2 = [fd fontDescriptorWithSymbolicTraits: NSFontDescriptorTraitItalic];
    return [NSFont fontWithDescriptor: fd2 size: [font pointSize]];
}

// entry 0 is None/Empty for slots, but populated for RAM.
static int SetDefaultMenu(NSArray *items, NSPopUpButton *button) {

    static NSDictionary *attr = nil;
    if (!attr) {
        attr = @{
            NSFontAttributeName: ItalicMenuFont()
        };
    }

    unsigned ix = 0;

    
    for (NSDictionary *d in items) {
        BOOL def = [(NSNumber *)[d objectForKey: @"default"] boolValue];
        if (!def) {
            ++ix;
            continue;
        }

        NSMenuItem *item = [button itemAtIndex: ix];
        NSString *title = [d objectForKey: @"description"];
        NSAttributedString *t = [[NSAttributedString alloc] initWithString: title attributes: attr];
        [item setAttributedTitle: t];
        return ix;
    }
    return 0;
}

static void DeactivateMenus(NSArray *items, NSPopUpButton *button) {
    
    [button setAutoenablesItems: NO];
    unsigned ix = 0;
    for (NSDictionary *d in items) {
        BOOL value = [(NSNumber *)[d objectForKey: @"disabled"] boolValue];
        if (value) {
            
            NSMenuItem *item = [button itemAtIndex: ix];
            [item setEnabled: NO];
        }
        ++ix;
    }
    
}

-(void)syncMemory {
    
    int ix = 0;
    NSArray *items = [_machine objectForKey: @"ram"];

    unsigned default_index = SetDefaultMenu(items, _ram_menu);
    _slots_valid |= kMemoryMask;
    _slots_default &= ~kMemoryMask;
    if (default_index) _slots_default |= kMemoryMask;
    
    if (_slots_explicit & kMemoryMask) {
        // if ram was explicitly set, try to keep it.

        for (NSDictionary *d in items) {
            unsigned size = [(NSNumber *)[d objectForKey: @"intValue"] unsignedIntValue];
            if (size == _memoryBytes) {
                [_ram_menu selectItemAtIndex: ix];
                [self setMemory: [d objectForKey: @"value"]];
                return;
            }
            ++ix;
        }
    }
    
    _slots_explicit &= ~kMemoryMask;
    if (default_index) {
        NSDictionary *d = [items objectAtIndex: default_index];

        [_ram_menu selectItemAtIndex: default_index];
        [self setMemory: [d objectForKey: @"value"]];
        [self setMemoryBytes: [(NSNumber *)[d objectForKey: @"intValue"] unsignedIntValue]];
    } else {
        [self setMemoryBytes: 0];
        [self setMemory: @""];
        [_ram_menu selectItemAtIndex: 0];
    }
}

-(void)resetMemory {
    
    NSArray *items = [_machine objectForKey: @"ram"];
    if (![items count]) return;

    unsigned ix = 0;
    for (NSDictionary *d in items) {
         BOOL def = [(NSNumber *)[d objectForKey: @"default"] boolValue];
        if (!def) {
            ++ix;
            continue;
        }
        // ram should always have a default.
        [_ram_menu selectItemAtIndex: ix];
        [self setMemory: [d objectForKey: @"description"]];
        _slots_default |= kMemoryMask;
        return;
    }
    // just in case
    [_ram_menu selectItemAtIndex: 0];
}

-(void)syncSlot: (NSString *)slot button: (NSPopUpButton *)button index: (unsigned)index {
    
    NSString *value = [self valueForKey: slot];
    NSArray *items = [_machine objectForKey: slot];

    unsigned mask = 1 << index;

    _slots_default &= ~mask;
    _slots_valid &= ~mask;

    if (![items count]) {
        //[self setValue: @"" forKey: slot]; // retain for later.
        //_slots_explicit &= ~mask;
        
        return;
    }
    _slots_valid |= mask;

    DeactivateMenus(items, button);
    unsigned default_index = SetDefaultMenu(items, button);

    if (default_index) _slots_default |= mask;


    if (_slots_explicit & mask) {
        int ix = 0;
        for (NSDictionary *d in items) {
            if ([value isEqualToString: [d objectForKey: @"value"]]) {

                [button selectItemAtIndex: ix];
                _slot_object[index] = d;
                _slot_media[index] = [d objectForKey: @"media"];
                return;
            }
            ++ix;
        }
    }
    _slots_explicit &= ~mask;
    
    if (default_index) {
        NSDictionary *d = [items objectAtIndex: default_index];
        [button selectItemAtIndex: default_index];
        [self setValue: [d objectForKey: @"value"] forKey: slot];
        _slot_object[index] = d;
        _slot_media[index] = [d objectForKey: @"media"];
    } else {
        [button selectItemAtIndex: 0];
        [self setValue: @"" forKey: slot];
        _slot_object[index] = nil;
        _slot_media[index] = nil;
    }
}

-(void)resetSlot: (NSString *)slot button: (NSPopUpButton *)button index: (unsigned)index {
    
    NSArray *items = [_machine objectForKey: slot];
    if (![items count]) return;

    unsigned ix = 0;
    for (NSDictionary *d in items) {
         BOOL def = [(NSNumber *)[d objectForKey: @"default"] boolValue];
        if (!def) {
            ++ix;
            continue;
        }
        [button selectItemAtIndex: ix];
        [self setValue: [d objectForKey: @"value"] forKey: slot];
        _slot_object[index] = d;
        _slot_media[index] = [d objectForKey: @"media"];

        _slots_default |= (1 << index);
        return;
    }
    // just in case
    NSDictionary *d = [items firstObject];

    [button selectItemAtIndex: 0];
    [self setValue: [d objectForKey: @"value"] forKey: slot];
    _slot_object[index] = d;
    _slot_media[index] = [d objectForKey: @"media"];
}

-(void)syncSlots {
    
    [self syncMemory];
    [self syncSlot: @"sl0" button: _sl0_menu index: 0];
    [self syncSlot: @"sl1" button: _sl1_menu index: 1];
    [self syncSlot: @"sl2" button: _sl2_menu index: 2];
    [self syncSlot: @"sl3" button: _sl3_menu index: 3];
    [self syncSlot: @"sl4" button: _sl4_menu index: 4];
    [self syncSlot: @"sl5" button: _sl5_menu index: 5];
    [self syncSlot: @"sl6" button: _sl6_menu index: 6];
    [self syncSlot: @"sl7" button: _sl7_menu index: 7];
    [self syncSlot: @"exp" button: _exp_menu index: 8];
    [self syncSlot: @"aux" button: _aux_menu index: 9];
    [self syncSlot: @"rs232" button: _rs232_menu index: 10];
    [self syncSlot: @"gameio" button: _game_menu index: 11];
    [self syncSlot: @"printer" button: _printer_menu index: 12];
    [self syncSlot: @"modem" button: _modem_menu index: 13];
}

-(void)loadMachine: (NSString *)model {
    
    if (!model) {
        [self resetMachine];
        return;
    }
    
    NSBundle *bundle = [NSBundle mainBundle];
    NSURL *url= [bundle URLForResource: model withExtension: @"plist"];
    
    NSDictionary *d = [NSDictionary dictionaryWithContentsOfURL: url];

    if (!d) {
        [self resetMachine];
        return;
    }

    NSArray *r = [d objectForKey: @"resolution"];
    NSSize res = NSMakeSize(0, 0);
    if (r) {
        res.width = [(NSNumber *)[r objectAtIndex: 0 /*@"width"*/] doubleValue];
        res.height = [(NSNumber *)[r objectAtIndex: 1 /*@"height"*/] doubleValue];
    }
    [self setResolution: res];
    
    _machine_media = [d objectForKey: @"media"];

    // n.b. - does content binding propogate immediately?
    [self setMachine: d];
    [self syncSlots];
    [self rebuildArgs];
    [self rebuildMedia];
}

static NSString *SlotNames[] = {

    @"sl0", @"sl1", @"sl2", @"sl3",
    @"sl4", @"sl5", @"sl6", @"sl7",
    @"exp", @"aux", @"rs232",
    @"gameio", @"printer", @"modem",
};

unsigned SlotCount = 14;

- (IBAction)menuChanged:(NSPopUpButton *)sender {


    
    NSInteger tag = [sender tag];

//    NSInteger ix = [sender indexOfSelectedItem];
    
    NSString *key = SlotNames[tag];
        
    _slots_explicit |= (1 << tag);

    NSDictionary *o = [[sender selectedItem] representedObject];
    
    [self setValue: [o objectForKey: @"value"] forKey: key];

    _slot_object[tag] = o;
    NSDictionary *newMedia = [o objectForKey: @"media"];
    NSDictionary *oldMedia = _slot_media[tag];

    if (newMedia != oldMedia) {
        _slot_media[tag] = newMedia;
        [self rebuildMedia];
    }
    
    
    [self rebuildArgs];
}

- (IBAction)memoryMenuChanged:(NSPopUpButton *)sender {

    //
    NSDictionary *o = [[sender selectedItem] representedObject];
    NSString *title = [o objectForKey: @"description"];
    [self setMemory: title];
    [self setMemoryBytes: [(NSNumber *)[o objectForKey: @"value"] unsignedIntValue]];
    
    _slots_explicit |= kMemoryMask;

    // if pull-down menu
    if ([sender pullsDown])
        [sender setTitle: title];
    
    [self rebuildArgs];
}

-(IBAction)resetSlots:(id)sender {
    /* reset slots to default */
    
    _slots_explicit = 0;
    _slots_default = 0;
    for (unsigned i = 0 ; i < SlotCount; ++i) {
        [self setValue: @"" forKey: SlotNames[i]];
        _slot_media[i] = nil;
        _slot_object[i] = nil;
    }
    _memory = @"";
    _memoryBytes = 0;
    
    [self resetMemory];
    [self resetSlot: @"sl0" button: _sl0_menu index: 0];
    [self resetSlot: @"sl1" button: _sl1_menu index: 1];
    [self resetSlot: @"sl2" button: _sl2_menu index: 2];
    [self resetSlot: @"sl3" button: _sl3_menu index: 3];
    [self resetSlot: @"sl4" button: _sl4_menu index: 4];
    [self resetSlot: @"sl5" button: _sl5_menu index: 5];
    [self resetSlot: @"sl6" button: _sl6_menu index: 6];
    [self resetSlot: @"sl7" button: _sl7_menu index: 7];
    [self resetSlot: @"exp" button: _exp_menu index: 8];
    [self resetSlot: @"aux" button: _aux_menu index: 9];
    [self resetSlot: @"rs232" button: _rs232_menu index: 10];
    [self resetSlot: @"gameio" button: _game_menu index: 11];
    [self resetSlot: @"printer" button: _printer_menu index: 12];
    [self resetSlot: @"modem" button: _modem_menu index: 13];
    
    [self rebuildArgs];
    [self rebuildMedia];
}

static BOOL should_add_arg(unsigned slot, unsigned valid_slots, unsigned explicit_slots, unsigned default_slots, NSString *value) {
    
    unsigned mask = 1 << slot;
    if (~valid_slots & mask) return NO;

    if (default_slots & mask) {
        if (explicit_slots & mask)
            return YES;
        return NO;
    }
    return [value length];
}

-(void)rebuildArgs {
    
    NSMutableArray *args = [NSMutableArray new];
    
    
    /* if there IS a default card for the slot and nothing is selected, need to -sl0 "" it. */
    

    #define _(ix, a, b) \
    if (should_add_arg(ix, _slots_valid, _slots_explicit, _slots_default, a)) { \
        [args addObject: b]; [args addObject: a]; \
    } \

    _(16, _memory, @"-ramsize")

    _(0, _sl0, @"-sl0")
    _(1, _sl1, @"-sl1")
    _(2, _sl2, @"-sl2")
    _(3, _sl3, @"-sl3")
    _(4, _sl4, @"-sl4")
    _(5, _sl5, @"-sl5")
    _(6, _sl6, @"-sl6")
    _(7, _sl7, @"-sl7")
    _(8, _exp, @"-exp")
    _(9, _aux, @"-aux")
    _(10, _rs232, @"-rs232")
    _(11, _gameio, @"-gameio")
    _(12, _printer, @"-printer")
    _(13, _modem, @"-modem")
#undef _
    [self setArgs: args];
}


-(void)rebuildMedia {
    

#define _(var, o) var += [[o objectForKey: @ # var ] unsignedIntValue]
    
    unsigned cass = 0;
    unsigned cdrm = 0;
    unsigned hard = 0;
    unsigned flop_5_25 = 0;
    unsigned flop_3_5 = 0;

    unsigned mask = 1;
    for (unsigned i = 0; i < 14; ++i, mask <<= 1) {
        
        if (_slots_valid & mask) {
            NSDictionary *tmp = _slot_media[i];
            if (tmp) {
                _(cass, tmp);
                _(cdrm, tmp);
                _(hard, tmp);
                _(flop_5_25, tmp);
                _(flop_3_5, tmp);
            }
        }
    }
    NSDictionary *tmp = _machine_media;
    if (tmp) {
        _(cass, tmp);
        _(cdrm, tmp);
        _(hard, tmp);
        _(flop_5_25, tmp);
        _(flop_3_5, tmp);
    }
    
    [self setMedia: @{
        @"cass": @(cass),
        @"cdrm": @(cdrm),
        @"hard": @(hard),
        @"flop_5_25": @(flop_5_25),
        @"flop_3_5": @(flop_3_5),
    }];
}


@end
