#import "GBPalettePicker.h"
#import "GBPaletteEditor.h"
#import <CoreServices/CoreServices.h>

/* TODO: Unify with Cocoa? */
#define MAGIC 'SBPL'

typedef struct __attribute__ ((packed)) {
    uint32_t magic;
    bool manual:1;
    bool disabled_lcd_color:1;
    unsigned padding:6;
    struct GB_color_s colors[5];
    int32_t brightness_bias;
    uint32_t hue_bias;
    uint32_t hue_bias_strength;
} theme_t;


@interface GBPalettePicker () <UIDocumentPickerDelegate>
@end

@implementation GBPalettePicker
{
    NSArray <NSString *>* _cacheNames;
    NSIndexPath *_renamingPath;
    NSMutableSet *_tempFiles;
}

+ (NSString *)makeUnique:(NSString *)name
{
    NSArray *builtins = @[
        @"Greyscale",
        @"Lime (Game Boy)",
        @"Olive (Pocket)",
        @"Teal (Light)",
    ];
    
    NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBThemes"];
    if (dict[name] || [builtins containsObject:name]) {
        unsigned i = 2;
        while (true) {
            NSString *attempt = [NSString stringWithFormat:@"%@ %u", name, i];
            if (!dict[attempt] && ![builtins containsObject:attempt]) {
                return attempt;
            }
            i++;
        }
    }
    return name;
}

+ (const GB_palette_t *)paletteForTheme:(NSString *)theme
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if ([theme isEqualToString:@"Greyscale"]) {
        return &GB_PALETTE_GREY;
    }
    if ([theme isEqualToString:@"Lime (Game Boy)"]) {
        return &GB_PALETTE_DMG;
    }
    if ([theme isEqualToString:@"Olive (Pocket)"]) {
        return &GB_PALETTE_MGB;
    }
    if ([theme isEqualToString:@"Teal (Light)"]) {
        return &GB_PALETTE_GBL;
    }
    static GB_palette_t customPalette;
    NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][theme][@"Colors"];
    if (colors.count != 5) return &GB_PALETTE_DMG;
    unsigned i = 0;
    for (NSNumber *color in colors) {
        uint32_t c = [color unsignedIntValue];
        customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
    }
    return &customPalette;
}

+ (UIColor *)colorFromGBColor:(const struct GB_color_s *)color
{
    return [UIColor colorWithRed:color->r / 255.0
                           green:color->g / 255.0
                            blue:color->b / 255.0
                           alpha:1.0];
}

+ (UIImage *)previewImageForTheme:(NSString *)theme
{
    const GB_palette_t *palette = [self paletteForTheme:theme];
    UIGraphicsBeginImageContextWithOptions((CGSize){29, 29}, false, [UIScreen mainScreen].scale);
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 29, 29) cornerRadius:7];
    [[self colorFromGBColor:&palette->colors[4]] set];
    [path fill];
    
    path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(4, 4, 9, 9) cornerRadius:2];
    [[self colorFromGBColor:&palette->colors[0]] set];
    [path fill];
    
    path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(16, 4, 9, 9) cornerRadius:2];
    [[self colorFromGBColor:&palette->colors[1]] set];
    [path fill];
    
    path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(4, 16, 9, 9) cornerRadius:2];
    [[self colorFromGBColor:&palette->colors[2]] set];
    [path fill];
    
    path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(16, 16, 9, 9) cornerRadius:2];
    [[self colorFromGBColor:&palette->colors[3]] set];
    [path fill];
    
    UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return ret;
}

- (instancetype)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    self.navigationItem.rightBarButtonItem = self.editButtonItem;
    _tempFiles = [NSMutableSet set];
    self.tableView.allowsSelectionDuringEditing = true;
    return self;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 3;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (section == 0) return 4;
    if (section == 2) return 3;
    _cacheNames = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBThemes"].allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    return _cacheNames.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    
    NSString *name = nil;
    if (indexPath.section == 2) {
        switch (indexPath.row) {
            case 0:
                cell.textLabel.text = @"New Palette";
                break;
            case 1:
                cell.textLabel.text = @"Import Palette";
                break;
            case 2:
                cell.textLabel.text = @"Restore Defaults";
                cell.textLabel.textColor = [UIColor systemRedColor];
                break;
        }
        return cell;
    }
    else if (indexPath.section == 0) {
        name = @[
            @"Greyscale",
            @"Lime (Game Boy)",
            @"Olive (Pocket)",
            @"Teal (Light)",
        ][indexPath.row];
    }
    else {
        name = _cacheNames[indexPath.row];
    }
    
    cell.textLabel.text = name;
    if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] isEqual:name]) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }

    cell.imageView.image = [self.class previewImageForTheme:name];
    return cell;

}


- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{
    [url startAccessingSecurityScopedResource];
    if ([self.class importPalette:url.path]) {
        [self.tableView reloadData];
    }
    else {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Import Failed"
                                                                                 message:@"The imported palette file is invalid."
                                                                          preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]];
        [self presentViewController:alertController animated:true completion:nil];
        return;
    }
    [url stopAccessingSecurityScopedResource];
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.editing) {
        if (indexPath.section != 1) return nil;
        if (@available(iOS 14.0, *)) {
            [self.navigationController pushViewController:[[GBPaletteEditor alloc] initForPalette:[self.tableView cellForRowAtIndexPath:indexPath].textLabel.text]
                                                 animated:true];
        }
        return nil;
    }
    if (indexPath.section == 2) {
        switch (indexPath.row) {
            case 0: {
                if (@available(iOS 14.0, *)) {
                    NSString *name = [self.class makeUnique:@"Untitled Palette"];
                    NSMutableDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBThemes"].mutableCopy;
                    srandom(time(NULL));
                    dict[name] = @{
                        @"BrightnessBias": @((random() & 0xFFF) / (double)0xFFF * 2 - 1),
                        @"Colors": @[@((random() & 0x3f3f3f) | 0xFF000000), @0, @0, @0, @((random() | 0xffc0c0c0))],
                        @"DisabledLCDColor": @YES,
                        @"HueBias": @((random() & 0xFFF) / (double)0xFFF),
                        @"HueBiasStrength": @((random() & 0xFFF) / (double)0xFFF),
                        @"Manual": @NO,
                    };
                    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"GBThemes"];
                    [self.navigationController pushViewController:[[GBPaletteEditor alloc] initForPalette:name]
                                                         animated:true];
                }
                else {
                    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Palette Editor Unavailable"
                                                                                             message:@"The palette editor requires iOS 14 or newer."
                                                                                      preferredStyle:UIAlertControllerStyleAlert];
                    [alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]];
                    [self presentViewController:alertController animated:true completion:nil];
                }
                break;
            }
            case 1: {
                [self setEditing:false animated:true];
                NSString *sbpUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"sbp", NULL);
                
                
                UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[sbpUTI]
                                                                                                                inMode:UIDocumentPickerModeImport];
                if (@available(iOS 13.0, *)) {
                    picker.shouldShowFileExtensions = true;
                }
                picker.delegate = self;
                [self presentViewController:picker animated:true completion:nil];
                break;
            }
            case 2: {
                UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Restore default palettes?"
                                                                               message: @"The defaults palettes will be restored, changes will be reverted, and created or imported palettes will be deleted. This change cannot be undone."
                                                                        preferredStyle:UIAlertControllerStyleAlert];
                [alert addAction:[UIAlertAction actionWithTitle:@"Restore"
                                                          style:UIAlertActionStyleDestructive
                                                        handler:^(UIAlertAction *action) {
                    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GBCurrentTheme"];
                    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GBThemes"];
                    [self.tableView reloadData];
                }]];
                [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
                                                          style:UIAlertActionStyleCancel
                                                        handler:nil]];
                [self presentViewController:alert animated:true completion:nil];
                break;
            }
        }
        return nil;
    }
    [[NSUserDefaults standardUserDefaults] setObject:[self.tableView cellForRowAtIndexPath:indexPath].textLabel.text
                                              forKey:@"GBCurrentTheme"];
    [self.tableView reloadData];
    return nil;
}


- (NSString *)title
{
    return @"Monochrome Palette";
}

- (void)renameRow:(NSIndexPath *)indexPath
{
    if (indexPath.section != 1) return;
    
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    CGRect frame = cell.textLabel.frame;
    frame.size.width = cell.textLabel.superview.frame.size.width - 8 - frame.origin.x;
    UITextField *field = [[UITextField alloc] initWithFrame:frame];
    field.font = cell.textLabel.font;
    field.text = cell.textLabel.text;
    cell.textLabel.textColor = [UIColor clearColor];
    [[cell.textLabel superview] addSubview:field];
    [field becomeFirstResponder];
    [field selectAll:nil];
    _renamingPath = indexPath;
    [field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit];
}

- (void)doneRename:(UITextField *)sender
{
    if (!_renamingPath) return;
    NSString *newName = sender.text;
    NSString *oldName = [self.tableView cellForRowAtIndexPath:_renamingPath].textLabel.text;
    
    _renamingPath = nil;
    if ([newName isEqualToString:oldName]) {
        [self.tableView reloadData];
        return;
    }
    
    NSMutableDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBThemes"].mutableCopy;
    newName = [self.class makeUnique:newName];
    
    dict[newName] = dict[oldName];
    [dict removeObjectForKey:oldName];
    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"GBThemes"];
    if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] isEqual:oldName]) {
        [[NSUserDefaults standardUserDefaults] setObject:newName forKey:@"GBCurrentTheme"];
    }
    [self.tableView reloadData];
    _renamingPath = nil;
}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section != 1) return;
    
    if (editingStyle != UITableViewCellEditingStyleDelete) return;
    NSString *rom = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Delete “%@”?", rom]
                                                                   message: @"This change cannot be undone."
                                                            preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"Delete"
                                              style:UIAlertActionStyleDestructive
                                            handler:^(UIAlertAction *action) {
        NSMutableDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBThemes"].mutableCopy;
        NSString *name = _cacheNames[indexPath.row];
        [dict removeObjectForKey:name];
        [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"GBThemes"];
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

        if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] isEqual:name]) {
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GBCurrentTheme"];
            [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
        }
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
                                              style:UIAlertActionStyleCancel
                                            handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return indexPath.section == 1;
}

- (NSString *)exportTheme:(NSString *)name
{
    theme_t theme = {0,};
    theme.magic = MAGIC;
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *themeDict = [defaults dictionaryForKey:@"GBThemes"][name];
    NSArray *colors = themeDict[@"Colors"];
    if (colors.count <= 5) {
        unsigned i = 0;
        for (NSNumber *color in colors) {
            uint32_t c = [color unsignedIntValue];
            theme.colors[i++] = (struct GB_color_s){
                (c & 0xFF),
                ((c >> 8) & 0xFF),
                ((c >> 16) & 0xFF)};
        }
    }
    
    theme.manual = [themeDict[@"Manual"] boolValue];
    theme.disabled_lcd_color = [themeDict[@"DisabledLCDColor"] boolValue];

    theme.brightness_bias = ([themeDict[@"BrightnessBias"] doubleValue] * 0x40000000);
    theme.hue_bias = round([themeDict[@"HueBias"] doubleValue] * 0x80000000);
    theme.hue_bias_strength = round([themeDict[@"HueBiasStrength"] doubleValue] * 0x80000000);
    
    size_t size = sizeof(theme);
    if (theme.manual) {
        size = theme.disabled_lcd_color? 5 + 5 * sizeof(theme.colors[0]) : 5 + 4 * sizeof(theme.colors[0]);
    }
    
    NSString *path = [[NSTemporaryDirectory() stringByAppendingPathComponent:name] stringByAppendingPathExtension:@"sbp"];
    [[NSData dataWithBytes:&theme length:size] writeToFile:path atomically:false];
    [_tempFiles addObject:path];
    return path;
}

+ (bool)importPalette:(NSString *)path
{
    NSData *data = [NSData dataWithContentsOfFile:path];
    theme_t theme = {0,};
    memcpy(&theme, data.bytes, MIN(sizeof(theme), data.length));
    if (theme.magic != MAGIC) {
        return false;
    }
    
    NSMutableDictionary *themeDict = [NSMutableDictionary dictionary];
    themeDict[@"Manual"] = theme.manual? @YES : @NO;
    themeDict[@"DisabledLCDColor"] = theme.disabled_lcd_color? @YES : @NO;
    
#define COLOR_TO_INT(color) ((color.r << 0) | (color.g << 8) | (color.b << 16) | 0xFF000000)
    themeDict[@"Colors"] = @[
        @(COLOR_TO_INT(theme.colors[0])),
        @(COLOR_TO_INT(theme.colors[1])),
        @(COLOR_TO_INT(theme.colors[2])),
        @(COLOR_TO_INT(theme.colors[3])),
        @(COLOR_TO_INT(theme.colors[theme.disabled_lcd_color? 4 : 3])),
    ];

    
    themeDict[@"BrightnessBias"] = @(theme.brightness_bias / (double)0x40000000);
    themeDict[@"HueBias"] = @(theme.hue_bias / (double)0x80000000);
    themeDict[@"HueBiasStrength"] = @(theme.hue_bias_strength / (double)0x80000000);
        
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary *themes = [defaults dictionaryForKey:@"GBThemes"].mutableCopy;
    NSString *baseName = path.lastPathComponent.stringByDeletingPathExtension;
    NSString *newName = [self.class makeUnique:baseName];
    themes[newName] = themeDict;
    [defaults setObject:themes forKey:@"GBThemes"];
    [defaults setObject:newName forKey:@"GBCurrentTheme"];
    return true;
}

- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
                                    point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
    if (indexPath.section != 1) return nil;
    
    return [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                   previewProvider:nil
                                                    actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
        UIAction *deleteAction = [UIAction actionWithTitle:@"Delete"
                                                     image:[UIImage systemImageNamed:@"trash"]
                                                identifier:nil
                                                   handler:^(UIAction *action) {
            [self tableView:tableView
         commitEditingStyle:UITableViewCellEditingStyleDelete
          forRowAtIndexPath:indexPath];
        }];
        
        deleteAction.attributes = UIMenuElementAttributesDestructive;
        return [UIMenu menuWithTitle:nil children:@[
            [UIAction actionWithTitle:@"Edit"
                                image:[UIImage systemImageNamed:@"paintbrush"]
                           identifier:nil
                              handler:^(__kindof UIAction *action) {
                if (@available(iOS 14.0, *)) {
                    [self.navigationController pushViewController:[[GBPaletteEditor alloc] initForPalette:[self.tableView cellForRowAtIndexPath:indexPath].textLabel.text]
                                                         animated:true];
                }
                else {
                    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Palette Editor Unavailable"
                                                                                             message:@"The palette editor requires iOS 14 or newer."
                                                                                      preferredStyle:UIAlertControllerStyleAlert];
                    [alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]];
                    [self presentViewController:alertController animated:true completion:nil];
                    return;
                }
            }],
            [UIAction actionWithTitle:@"Share"
                                image:[UIImage systemImageNamed:@"square.and.arrow.up"]
                           identifier:nil
                              handler:^(__kindof UIAction *action) {
                [self setEditing:false animated:true];
                UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                NSString *file = [self exportTheme:cell.textLabel.text];
                NSURL *url = [NSURL fileURLWithPath:file];
                UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
                                                                                         applicationActivities:nil];
                
                if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
                    controller.popoverPresentationController.sourceView = cell.contentView;
                }
                
                [self presentViewController:controller
                                   animated:true
                                 completion:nil];
            }],
            [UIAction actionWithTitle:@"Rename"
                                image:[UIImage systemImageNamed:@"pencil"]
                           identifier:nil
                              handler:^(__kindof UIAction *action) {
                [self renameRow:indexPath];
            }],
            deleteAction,
        ]];
    }];
}

- (void)dealloc
{
    for (NSString *file in _tempFiles) {
        [[NSFileManager defaultManager] removeItemAtPath:file error:nil];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [self.tableView reloadData];
    [super viewWillAppear:animated];
}

@end
