/*****************************************************************************
 * Free42 -- an HP-42S calculator simulator
 * Copyright (C) 2004-2026  Thomas Okken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/licenses/.
 *****************************************************************************/

#import <dirent.h>
#import <sys/stat.h>
#import "RootViewController.h"
#import "SelectFileView.h"

@implementation SelectFileView

@synthesize navigationItem;
@synthesize directoryLabel;
@synthesize directoryListingView;
@synthesize nameField;
@synthesize typeSelector;
@synthesize selectFileButton;
@synthesize scrollView;
@synthesize contentView;

static NSString *windowTitle;
static NSString *selectTitle;
static NSString *types;
static BOOL selectDir;
static id callbackObject;
static SEL callbackSelector;

static NSString *dirName = NULL;
static NSMutableArray *dirList = NULL;
static bool *dirType = NULL;
static int dirTypeLength = 0;
static int dirTypeCapacity = 0;

- (id) initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // Initialization code
        activeField = nil;
    }
    return self;
}


- (void) drawRect:(CGRect)rect {
    // Drawing code
}

+ (void) raiseWithTitle:(NSString *)wt selectTitle:(NSString *)st types:(NSString *)t initialFile:(NSString *)path selectDir:(BOOL)sd callbackObject:(id)cb_id callbackSelector:(SEL)cb_sel {
    windowTitle = [wt retain];
    selectTitle = [st retain];
    types = [t retain];
    selectDir = sd;
    callbackObject = [cb_id retain];
    callbackSelector = cb_sel;
    dirName = [path retain];
    [RootViewController showSelectFile];
}

- (void) raised {
    // watch the keyboard so we can adjust the user interface if necessary.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    if ([[self gestureRecognizers] count] == 0) {
        UITapGestureRecognizer* tapBackground = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideKeyboard)];
        [tapBackground setNumberOfTapsRequired:1];
        tapBackground.cancelsTouchesInView = NO;
        [self addGestureRecognizer:tapBackground];
    }
    scrollView.contentSize = contentView.frame.size;
    
    [navigationItem setTitle:windowTitle];
    [windowTitle release];
    [selectFileButton setTitle:selectTitle forState:UIControlStateNormal];
    [selectFileButton setTitle:selectTitle forState:UIControlStateHighlighted];
    [selectFileButton setTitle:selectTitle forState:UIControlStateDisabled];
    [selectFileButton setTitle:selectTitle forState:UIControlStateSelected];
    [selectTitle release];
    
    [typeSelector removeAllSegments];
    NSArray *ta = [types componentsSeparatedByString:@","];
    NSUInteger tc = [ta count];
    if (tc == 0)
        typeSelector.enabled = NO;
    else {
        typeSelector.enabled = YES;
        for (int i = 0; i < tc; i++) {
            [typeSelector insertSegmentWithTitle:[ta objectAtIndex:i] atIndex:i animated:NO];
            [typeSelector setWidth:0 forSegmentAtIndex:i];
        }
        typeSelector.selectedSegmentIndex = 0;
        // It appears that UISegmentedControl.removeAllSegments does
        // not actually remove all segments, so now we make sure:
        while ([typeSelector numberOfSegments] > tc)
            [typeSelector removeSegmentAtIndex:tc animated:NO];
    }

    if (dirName == nil) {
        [nameField setText:@""];
        dirName = @".";
    } else {
        NSRange r = [dirName rangeOfString:@"/" options:NSBackwardsSearch];
        if (r.location == NSNotFound) {
            [nameField setText:dirName];
            [dirName release];
            dirName = @".";
        } else {
            [nameField setText:[dirName substringFromIndex:r.location + 1]];
            NSString *newDirName = [dirName substringToIndex:r.location];
            r = [newDirName rangeOfString:@"./" options:NSAnchoredSearch];
            if (r.location == NSNotFound)
                newDirName = [NSString stringWithFormat:@"./%@", newDirName];
            [dirName release];
            dirName = [newDirName retain];
        }
    }
    
    // Force initial directory read
    [self typeChanged];
}

- (void) keyboardDidShow:(NSNotification *)notif {
    NSDictionary *userInfo = [notif userInfo];
    CGSize kbSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    scrollView.contentInset = contentInsets;
    scrollView.scrollIndicatorInsets = contentInsets;
    
    // Make sure the active field is visible
    if (activeField != nil)
        [self.scrollView scrollRectToVisible:activeField.frame animated:YES];
}

- (void) keyboardWillHide:(NSNotification *)notif {
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    scrollView.contentInset = contentInsets;
    scrollView.scrollIndicatorInsets = contentInsets;
}

- (void) textFieldDidBeginEditing:(UITextField *)textField {
    activeField = textField;
}

- (void) textFieldDidEndEditing:(UITextField *)textField {
    if (activeField == textField)
        activeField = nil;
}

- (BOOL) textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return NO;
}

- (void)hideKeyboard {
    [self endEditing:YES];
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger n = [indexPath indexAtPosition:1];
    if (dirList == NULL || n >= [dirList count])
        return nil;
    NSString *item = [dirList objectAtIndex:n];
    if (dirType[n]) {
        // Selected item is a directory
        NSString *newDirName;
        if ([item isEqualToString:@".."]) {
            NSRange r = [dirName rangeOfString:@"/" options:NSBackwardsSearch];
            if (r.location == NSNotFound)
                return nil;
            newDirName = [dirName substringToIndex:r.location];
        } else {
            newDirName = [NSString stringWithFormat:@"%@/%@", dirName, item];
        }
        [dirName release];
        dirName = [newDirName retain];
        [self performSelectorOnMainThread:@selector(typeChanged) withObject:nil waitUntilDone:NO];
    } else {
        // Selected item is a file
        [nameField setText:item];
    }
    return nil;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger n = [indexPath indexAtPosition:1];
    if (dirList == NULL || n >= [dirList count])
        return nil;
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.textLabel.text = [dirList objectAtIndex:n];
    return cell;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0 && dirList != NULL)
        return [dirList count];
    else
        return 0;
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleDelete;
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger n = [indexPath indexAtPosition:1];
    if (dirList == NULL || n >= [dirList count])
        return NO;
    return dirType[n] ? NO : YES;
}

- (UISwipeActionsConfiguration *) tableView:(UITableView *) tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *) indexPath {
    return nil;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSUInteger n = [indexPath indexAtPosition:1];
        if (dirList == NULL || n >= [dirList count] || dirType[n])
            return;
        NSString *path = [NSString stringWithFormat:@"%@/%@", dirName, [dirList objectAtIndex:n]];
        remove([path UTF8String]);
        [self typeChanged];
    }
}

- (IBAction) selectFile {
    [self endEditing:YES];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];

    [[self superview] sendSubviewToBack:self];
    NSString *fullName = [NSString stringWithFormat:@"%@/%@", dirName, [nameField text]];
    // Get rid of the leading "./"
    fullName = [fullName substringFromIndex:2];
    // If the selected type is not All Files (*), append file type, if necessary
    if ([fullName length] > 0) {
        NSString *suffix = [typeSelector titleForSegmentAtIndex:[typeSelector selectedSegmentIndex]];
        if ([suffix compare:@"*"] != NSOrderedSame) {
            suffix = [NSString stringWithFormat:@".%@", suffix];
            if (![[fullName lowercaseString] hasSuffix:[suffix lowercaseString]])
                fullName = [fullName stringByAppendingString:suffix];
        }
    }
    [callbackObject performSelector:callbackSelector withObject:fullName];
    [callbackObject release];
    [dirName release];
}

- (IBAction) cancel {
    [self endEditing:YES];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];

    [[self superview] sendSubviewToBack:self];
    [callbackObject release];
    [dirName release];
}

- (IBAction) mkDir {
    NSString *fullName = [NSString stringWithFormat:@"%@/%@", dirName, [nameField text]];
    const char *cpath = [fullName UTF8String];
    mkdir(cpath, 0755);
    [self typeChanged];
}

static int dirListCompare(int a, int b) {
    if (dirType[a] != dirType[b])
        // Directories always come first
        return dirType[a] ? -1 : 1;
    NSString *sa = [dirList objectAtIndex:a];
    NSString *sb = [dirList objectAtIndex:b];
    if (dirType[a]) {
        if ([sa isEqualToString:@".."])
            return -1;
        else if ([sb isEqualToString:@".."])
            return 1;
    }
    bool da = [sa characterAtIndex:0] == '.';
    bool db = [sb characterAtIndex:0] == '.';
    if (da != db)
        return da ? -1 : 1;
    return [sa caseInsensitiveCompare:sb];
}

static void dirListSwap(int a, int b) {
    NSString *ts = [dirList objectAtIndex:a];
    [dirList replaceObjectAtIndex:a withObject:[dirList objectAtIndex:b]];
    [dirList replaceObjectAtIndex:b withObject:ts];
    bool tb = dirType[a];
    dirType[a] = dirType[b];
    dirType[b] = tb;
}

- (IBAction) typeChanged {
    if (dirList == NULL)
        dirList = [[NSMutableArray arrayWithCapacity:10] retain];
    else
        [dirList removeAllObjects];
    dirTypeLength = 0;
    
    // Display directory name
    if ([dirName isEqualToString:@"."])
        [directoryLabel setText:@"Documents"];
    else {
        NSRange r = [dirName rangeOfString:@"/" options:NSBackwardsSearch];
        [directoryLabel setText:[dirName substringFromIndex:(r.location + 1)]];
    }
    
    const char *typeStr = [[typeSelector titleForSegmentAtIndex:[typeSelector selectedSegmentIndex]] UTF8String];

    const char *cDirName = [dirName UTF8String];
    DIR *dir = opendir(cDirName);
    struct dirent *d;
    
    while ((d = readdir(dir)) != NULL) {
        if (strcmp(d->d_name, "..") == 0) {
            if (strcmp(cDirName, ".") == 0)
                // Don't show ".." in the top-level (Documents) directory
                continue;
        } else if (d->d_name[0] == '.') {
            // Apart from '..', suppress everything that starts with '.'
            continue;
        }
        char *p = (char *) malloc(strlen(cDirName) + strlen(d->d_name) + 2);
        strcpy(p, cDirName);
        strcat(p, "/");
        strcat(p, d->d_name);
        struct stat s;
        int err = stat(p, &s);
        free(p);
        if (err == 0 && (S_ISREG(s.st_mode) || S_ISDIR(s.st_mode))) {
            if (S_ISREG(s.st_mode) && strcmp(typeStr, "*") != 0) {
                // Check file suffix
                size_t flen = strlen(d->d_name);
                size_t slen = strlen(typeStr);
                if (flen <= slen)
                    continue;
                if (d->d_name[flen - slen - 1] != '.' || strcasecmp(d->d_name + (flen - slen), typeStr) != 0)
                    continue;
            }
            [dirList addObject:[NSString stringWithUTF8String:d->d_name]];
            dirTypeLength++;
            if (dirTypeLength > dirTypeCapacity) {
                dirTypeCapacity += 10;
                dirType = (bool *) realloc(dirType, dirTypeCapacity * sizeof(bool));
            }
            dirType[[dirList count] - 1] = S_ISDIR(s.st_mode);
        }
    }
    closedir(dir);

    /* https://en.wikipedia.org/wiki/Heapsort#Standard_implementation */
    int start = dirTypeLength / 2;
    int end = dirTypeLength;
    while (end > 1) {
        if (start > 0) {    /* Heap construction */
            start--;
        } else {            /* Heap extraction */
            end--;
            dirListSwap(end, 0);
        }

        /* The following is siftDown(a, start, end) */
        int root = start;
        int child;
        while ((child = root * 2 + 1) < end) {
            /* If there is a right child and that child is greater */
            if (child + 1 < end && dirListCompare(child, child + 1) < 0)
                child++;

            if (dirListCompare(root, child) < 0) {
                dirListSwap(root, child);
                root = child;         /* repeat to continue sifting down the child now */
            } else {
                break;                /* return to outer loop */
            }
        }
    }

    [directoryListingView reloadData];
}

- (void) dealloc {
    [super dealloc];
}


@end
