//
//  HFASCIITextRepresenter.m
//  HexFiend_2
//
//  Copyright 2007 ridiculous_fish. All rights reserved.
//

#import <HexFiend/HFStringEncodingTextRepresenter.h>
#import <HexFiend/HFRepresenterStringEncodingTextView.h>
#import <HexFiend/HFPasteboardOwner.h>
#import <HexFiend/HFProgressTracker.h>

@interface HFStringEncodingPasteboardOwner : HFPasteboardOwner {
    NSStringEncoding encoding;
}
@property (nonatomic) NSStringEncoding encoding;
@end

@implementation HFStringEncodingPasteboardOwner
- (void)setEncoding:(NSStringEncoding)val { encoding = val; }
- (NSStringEncoding)encoding { return encoding; }

- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(HFProgressTracker *)tracker {
    HFASSERT([type isEqual:NSStringPboardType]);
    HFByteArray *byteArray = [self byteArray];
    HFASSERT(length <= NSUIntegerMax);
    NSUInteger dataLength = ll2l(length);
    NSUInteger stringLength = dataLength;
    NSUInteger offset = 0, remaining = dataLength;
    volatile long long * const progressReportingPointer = (volatile long long *)&tracker->currentProgress;
    [tracker setMaxProgress:dataLength];
    unsigned char * restrict const stringBuffer = check_malloc(stringLength);
    while (remaining > 0) {
        if (tracker->cancelRequested) break;
        NSUInteger amountToCopy = MIN(32u * 1024u, remaining);
        [byteArray copyBytes:stringBuffer + offset range:HFRangeMake(offset, amountToCopy)];
        offset += amountToCopy;
        remaining -= amountToCopy;
        HFAtomicAdd64(amountToCopy, progressReportingPointer);
    }
    if (tracker->cancelRequested) {
        [pboard setString:@"" forType:type];
        free(stringBuffer);
    }
    else {
        NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:encoding freeWhenDone:YES];
        [pboard setString:string forType:type];
    }
}

- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength {
    return dataLength;
}

@end

@implementation HFStringEncodingTextRepresenter

- (instancetype)init {
    self = [super init];
    stringEncoding = [NSString defaultCStringEncoding];
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    HFASSERT([coder allowsKeyedCoding]);
    self = [super initWithCoder:coder];
    stringEncoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"];
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    HFASSERT([coder allowsKeyedCoding]);
    [super encodeWithCoder:coder];
    [coder encodeInt64:stringEncoding forKey:@"HFStringEncoding"];
}

- (Class)_textViewClass {
    return [HFRepresenterStringEncodingTextView class];
}

- (NSStringEncoding)encoding {
    return stringEncoding;
}

- (void)setEncoding:(NSStringEncoding)encoding {
    stringEncoding = encoding;
    [[self view] setEncoding:encoding];
    [[self controller] representer:self changedProperties:HFControllerViewSizeRatios];
}

- (void)initializeView {
    [[self view] setEncoding:stringEncoding];
    [super initializeView];
}

- (void)insertText:(NSString *)text {
    REQUIRE_NOT_NULL(text);
    NSData *data = [text dataUsingEncoding:[self encoding] allowLossyConversion:NO];
    if (! data) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString *key = @"HFStringEncodingConversionFailureShowAlert";
            if (![[NSUserDefaults standardUserDefaults] objectForKey:key]) {
                NSAlert *alert = [[NSAlert alloc] init];
                alert.messageText = NSLocalizedString(@"Failed to convert text", "");
                alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"The text \"%@\" could not be converted to the current encoding \"%@\". The encoding may not support these characters.", ""), text, [NSString localizedNameOfStringEncoding:[self encoding]]];
                (void)[alert addButtonWithTitle:NSLocalizedString(@"OK", "")];
                (void)[alert addButtonWithTitle:NSLocalizedString(@"Do Not Show Again", "")];
                if ([alert runModal] == NSAlertSecondButtonReturn) {
                    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
                }
            }
        });
        NSLog(@"%s: Can't convert \"%@\" to encoding %@", __PRETTY_FUNCTION__, text, [NSString localizedNameOfStringEncoding:[self encoding]]);
        NSBeep();
    }
    else if ([data length]) { // a 0 length text can come about via e.g. option-e
        [[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:YES];
    }
}

- (NSData *)dataFromPasteboardString:(NSString *)string {
    REQUIRE_NOT_NULL(string);
    return [string dataUsingEncoding:[self encoding] allowLossyConversion:NO];
}

+ (NSPoint)defaultLayoutPosition {
    return NSMakePoint(1, 0);
}

- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb {
    return [self copySelectedBytesToPasteboard:pb encoding:[self encoding]];
}

- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb encoding:(NSStringEncoding)enc {
    REQUIRE_NOT_NULL(pb);
    HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges];
    HFASSERT(selection != NULL);
    if ([selection length] == 0) {
        NSBeep();
    }
    else {
        HFStringEncodingPasteboardOwner *owner = [HFStringEncodingPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]];
        [owner setEncoding:enc];
        [owner setBytesPerLine:[self bytesPerLine]];
    }
}

@end
