//
//  PlaylistEntry.m
//  Cog
//
//  Created by Vincent Spader on 3/14/05.
//  Copyright 2005 Vincent Spader All rights reserved.
//

#import <Foundation/Foundation.h>

#import <CoreData/CoreData.h>

#import "PlaylistEntry.h"

#import "AVIFDecoder.h"
#import "SHA256Digest.h"
#import "SecondsFormatter.h"

extern NSPersistentContainer *__persistentContainer;
extern NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary;

@implementation PlaylistEntry (Extension)

// The following read-only keys depend on the values of other properties

+ (NSSet *)keyPathsForValuesAffectingUrl {
	return [NSSet setWithObject:@"urlString"];
}

+ (NSSet *)keyPathsForValuesAffectingTrashUrl {
	return [NSSet setWithObject:@"trashUrlString"];
}

+ (NSSet *)keyPathsForValuesAffectingTitle {
	return [NSSet setWithObject:@"rawTitle"];
}

+ (NSSet *)keyPathsForValuesAffectingDisplay {
	return [NSSet setWithObjects:@"artist", @"title", nil];
}

+ (NSSet *)keyPathsForValuesAffectingLength {
	return [NSSet setWithObjects:@"metadataLoaded", @"totalFrames", @"sampleRate", nil];
}

+ (NSSet *)keyPathsForValuesAffectingPath {
	return [NSSet setWithObject:@"url"];
}

+ (NSSet *)keyPathsForValuesAffectingFilename {
	return [NSSet setWithObject:@"url"];
}

+ (NSSet *)keyPathsForValuesAffectingStatus {
	return [NSSet setWithObjects:@"current", @"queued", @"error", @"stopAfter", nil];
}

+ (NSSet *)keyPathsForValuesAffectingStatusMessage {
	return [NSSet setWithObjects:@"current", @"queued", @"queuePosition", @"error", @"errorMessage", @"stopAfter", nil];
}

+ (NSSet *)keyPathsForValuesAffectingSpam {
	return [NSSet setWithObjects:@"albumartist", @"artist", @"rawTitle", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", nil];
}

+ (NSSet *)keyPathsForValuesAffectingIndexedSpam {
	return [NSSet setWithObjects:@"albumartist", @"artist", @"rawTitle", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", @"index", nil];
}

+ (NSSet *)keyPathsForValuesAffectingTrackText {
	return [NSSet setWithObjects:@"track", @"disc", nil];
}

+ (NSSet *)keyPathsForValuesAffectingYearText {
	return [NSSet setWithObject:@"year"];
}

+ (NSSet *)keyPathsForValuesAffectingCuesheetPresent {
	return [NSSet setWithObject:@"cuesheet"];
}

+ (NSSet *)keyPathsForValuesAffectingPositionText {
	return [NSSet setWithObject:@"currentPosition"];
}

+ (NSSet *)keyPathsForValuesAffectingLengthText {
	return [NSSet setWithObject:@"length"];
}

+ (NSSet *)keyPathsForValuesAffectingAlbumArt {
	return [NSSet setWithObjects:@"albumArtInternal", @"artId", nil];
}

+ (NSSet *)keyPathsForValuesAffectingGainCorrection {
	return [NSSet setWithObjects:@"replayGainAlbumGain", @"replayGainAlbumPeak", @"replayGainTrackGain", @"replayGainTrackPeak", @"volume", nil];
}

+ (NSSet *)keyPathsForValuesAffectingGainInfo {
	return [NSSet setWithObjects:@"replayGainAlbumGain", @"replayGainAlbumPeak", @"replayGainTrackGain", @"replayGainTrackPeak", @"volume", nil];
}

+ (NSSet *)keyPathsForValuesAffectingUnsigned {
	return [NSSet setWithObject:@"unSigned"];
}

- (NSString *)description {
	return [NSString stringWithFormat:@"PlaylistEntry %lli:(%@)", self.index, self.url];
}

// Get the URL if the title is blank
@dynamic title;
- (NSString *)title {
	if((self.rawTitle == nil || [self.rawTitle isEqualToString:@""]) && self.url) {
		return [[self.url path] lastPathComponent];
	}
	return self.rawTitle;
}

- (void)setTitle:(NSString *)title {
	self.rawTitle = title;
}

@dynamic display;
- (NSString *)display {
	if((self.artist == NULL) || ([self.artist isEqualToString:@""]))
		return self.title;
	else {
		return [NSString stringWithFormat:@"%@ - %@", self.artist, self.title];
	}
}

@dynamic indexedSpam;
- (NSString *)indexedSpam {
	return [NSString stringWithFormat:@"%llu. %@", self.index, self.spam];
}

@dynamic spam;
- (NSString *)spam {
	BOOL hasBitrate = (self.bitrate != 0);
	BOOL hasArtist = (self.artist != nil) && (![self.artist isEqualToString:@""]);
	BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]);
	BOOL hasTrackArtist = (hasArtist && hasAlbumArtist) && (![self.albumartist isEqualToString:self.artist]);
	BOOL hasAlbum = (self.album != nil) && (![self.album isEqualToString:@""]);
	BOOL hasTrack = (self.track != 0);
	BOOL hasLength = (self.totalFrames != 0);
	BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current);
	BOOL hasExtension = NO;
	BOOL hasTitle = (self.rawTitle != nil) && (![self.rawTitle isEqualToString:@""]);
	BOOL hasCodec = (self.codec != nil) && (![self.codec isEqualToString:@""]);

	NSMutableString *filename = [NSMutableString stringWithString:self.filename];
	NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch];
	NSString *extension = nil;

	if(dotPosition.length > 0) {
		dotPosition.location++;
		dotPosition.length = [filename length] - dotPosition.location;
		extension = [filename substringWithRange:dotPosition];
		dotPosition.location--;
		dotPosition.length++;
		[filename deleteCharactersInRange:dotPosition];
		hasExtension = YES;
	}

	NSMutableArray *elements = [NSMutableArray array];

	if(hasExtension) {
		[elements addObject:@"["];
		if(hasCodec) {
			[elements addObject:self.codec];
		} else {
			[elements addObject:[extension uppercaseString]];
		}
		if(hasBitrate) {
			[elements addObject:@"@"];
			[elements addObject:[NSString stringWithFormat:@"%u", self.bitrate]];
			[elements addObject:@"kbps"];
		}
		[elements addObject:@"] "];
	}

	if(hasArtist) {
		if(hasAlbumArtist) {
			[elements addObject:self.albumartist];
		} else {
			[elements addObject:self.artist];
		}
		[elements addObject:@" - "];
	}

	if(hasAlbum) {
		[elements addObject:@"["];
		[elements addObject:self.album];
		if(hasTrack) {
			[elements addObject:@" #"];
			[elements addObject:self.trackText];
		}
		[elements addObject:@"] "];
	}

	if(hasTitle) {
		[elements addObject:self.rawTitle];
	} else {
		[elements addObject:filename];
	}

	if(hasTrackArtist) {
		[elements addObject:@" // "];
		[elements addObject:self.artist];
	}

	if(hasCurrentPosition || hasLength) {
		SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
		[elements addObject:@" ("];
		if(hasCurrentPosition) {
			[elements addObject:[secondsFormatter stringForObjectValue:@(self.currentPosition)]];
		}
		if(hasLength) {
			if(hasCurrentPosition) {
				[elements addObject:@" / "];
			}
			[elements addObject:[secondsFormatter stringForObjectValue:[self length]]];
		}
		[elements addObject:@")"];
	}

	return [elements componentsJoinedByString:@""];
}

@dynamic trackText;
- (NSString *)trackText {
	if(self.track != 0) {
		if(self.disc != 0) {
			return [NSString stringWithFormat:@"%u.%02u", self.disc, self.track];
		} else {
			return [NSString stringWithFormat:@"%02u", self.track];
		}
	} else {
		return @"";
	}
}

@dynamic yearText;
- (NSString *)yearText {
	if(self.year != 0) {
		return [NSString stringWithFormat:@"%u", self.year];
	} else {
		return @"";
	}
}

@dynamic cuesheetPresent;
- (NSString *)cuesheetPresent {
	if(self.cuesheet && [self.cuesheet length]) {
		return @"yes";
	} else {
		return @"no";
	}
}

@dynamic gainCorrection;
- (NSString *)gainCorrection {
	if(self.replayGainAlbumGain) {
		if(self.replayGainAlbumPeak)
			return NSLocalizedStringFromTableInBundle(@"GainAlbumGainPeak", nil, [NSBundle bundleForClass:[self class]], @"");
		else
			return NSLocalizedStringFromTableInBundle(@"GainAlbumGain", nil, [NSBundle bundleForClass:[self class]], @"");
	} else if(self.replayGainTrackGain) {
		if(self.replayGainTrackPeak)
			return NSLocalizedStringFromTableInBundle(@"GainTrackGainPeak", nil, [NSBundle bundleForClass:[self class]], @"");
		else
			return NSLocalizedStringFromTableInBundle(@"GainTrackGain", nil, [NSBundle bundleForClass:[self class]], @"");
	} else if(self.volume && self.volume != 1.0) {
		return NSLocalizedStringFromTableInBundle(@"GainVolumeScale", nil, [NSBundle bundleForClass:[self class]], @"");
	} else {
		return NSLocalizedStringFromTableInBundle(@"GainNone", nil, [NSBundle bundleForClass:[self class]], @"");
	}
}

@dynamic gainInfo;
- (NSString *)gainInfo {
	NSMutableArray *gainItems = [[NSMutableArray alloc] init];
	if(self.replayGainAlbumGain) {
		[gainItems addObject:[NSString stringWithFormat:@"%@: %+.2f dB", NSLocalizedStringFromTableInBundle(@"GainAlbumGain", nil, [NSBundle bundleForClass:[self class]], @""), self.replayGainAlbumGain]];
	}
	if(self.replayGainAlbumPeak) {
		[gainItems addObject:[NSString stringWithFormat:@"%@: %.6f", NSLocalizedStringFromTableInBundle(@"GainAlbumPeak", nil, [NSBundle bundleForClass:[self class]], @""), self.replayGainAlbumPeak]];
	}
	if(self.replayGainTrackGain) {
		[gainItems addObject:[NSString stringWithFormat:@"%@: %+.2f dB", NSLocalizedStringFromTableInBundle(@"GainTrackGain", nil, [NSBundle bundleForClass:[self class]], @""), self.replayGainTrackGain]];
	}
	if(self.replayGainTrackPeak) {
		[gainItems addObject:[NSString stringWithFormat:@"%@: %.6f", NSLocalizedStringFromTableInBundle(@"GainTrackPeak", nil, [NSBundle bundleForClass:[self class]], @""), self.replayGainTrackPeak]];
	}
	if(self.volume && self.volume != 1) {
		[gainItems addObject:[NSString stringWithFormat:@"%@: %.2f%C", NSLocalizedStringFromTableInBundle(@"GainVolumeScale", nil, [NSBundle bundleForClass:[self class]], @""), self.volume, (unichar)0x00D7]];
	}
	return [gainItems componentsJoinedByString:@"\n"];
}

@dynamic positionText;
- (NSString *)positionText {
	SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
	NSString *time = [secondsFormatter stringForObjectValue:@(self.currentPosition)];
	return time;
}

@dynamic lengthText;
- (NSString *)lengthText {
	SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
	NSString *time = [secondsFormatter stringForObjectValue:self.length];
	return time;
}

@dynamic albumArt;
- (NSImage *)albumArt {
	if(!self.albumArtInternal || ![self.albumArtInternal length]) return nil;

	NSString *imageCacheTag = self.artHash;
	NSImage *image = [NSImage imageNamed:imageCacheTag];

	if(image == nil) {
		if([AVIFDecoder isAVIFFormatForData:self.albumArtInternal]) {
			CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:self.albumArtInternal];
			if(imageRef) {
				image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize];
				CFRelease(imageRef);
			}
		} else {
			image = [[NSImage alloc] initWithData:self.albumArtInternal];
		}
		[image setName:imageCacheTag];
	}

	return image;
}

- (void)setAlbumArt:(id)data {
	if([data isKindOfClass:[NSData class]]) {
		[self setAlbumArtInternal:data];
	}
}

@dynamic albumArtInternal;
- (NSData *)albumArtInternal {
	NSString *imageCacheTag = self.artHash;
	return [__artworkDictionary objectForKey:imageCacheTag].artData;
}

- (void)setAlbumArtInternal:(NSData *)albumArtInternal {
	if(!albumArtInternal || [albumArtInternal length] == 0) return;

	NSString *imageCacheTag = [SHA256Digest digestDataAsString:albumArtInternal];

	self.artHash = imageCacheTag;

	if(![__artworkDictionary objectForKey:imageCacheTag]) {
		AlbumArtwork *art = [NSEntityDescription insertNewObjectForEntityForName:@"AlbumArtwork" inManagedObjectContext:__persistentContainer.viewContext];
		art.artHash = imageCacheTag;
		art.artData = albumArtInternal;

		[__artworkDictionary setObject:art forKey:imageCacheTag];
	}
}

@dynamic length;
- (NSNumber *)length {
	return (self.metadataLoaded) ? @(((double)self.totalFrames / self.sampleRate)) : @(0.0);
}

NSURL *_Nullable urlForPath(NSString *_Nullable path) {
	if(!path || ![path length]) {
		return nil;
	}

	NSRange protocolRange = [path rangeOfString:@"://"];
	if(protocolRange.location != NSNotFound) {
		return [NSURL URLWithString:path];
	}

	NSMutableString *unixPath = [path mutableCopy];

	// Get the fragment
	NSString *fragment = @"";
	NSScanner *scanner = [NSScanner scannerWithString:unixPath];
	NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"];
	while(![scanner isAtEnd]) {
		NSString *possibleFragment;
		[scanner scanUpToString:@"#" intoString:nil];

		if([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) {
			fragment = possibleFragment;
			[unixPath deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])];
			break;
		}
	}

	// Append the fragment
	NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString:fragment]];
	return url;
}

@dynamic url;
- (NSURL *)url {
	return urlForPath(self.urlString);
}

- (void)setUrl:(NSURL *)url {
	self.urlString = url ? [url absoluteString] : nil;
}

@dynamic trashUrl;
- (NSURL *)trashUrl {
	return urlForPath(self.trashUrlString);
}

- (void)setTrashUrl:(NSURL *)trashUrl {
	self.trashUrlString = trashUrl ? [trashUrl absoluteString] : nil;
}

@dynamic path;
- (NSString *)path {
	if([self.url isFileURL])
		return [[self.url path] stringByAbbreviatingWithTildeInPath];
	else
		return [self.url absoluteString];
}

@dynamic filename;
- (NSString *)filename {
	return [[self.url path] lastPathComponent];
}

@dynamic status;
- (NSString *)status {
	if(self.stopAfter) {
		return @"stopAfter";
	} else if(self.current) {
		return @"playing";
	} else if(self.queued) {
		return @"queued";
	} else if(self.error) {
		return @"error";
	}

	return nil;
}

@dynamic statusMessage;
- (NSString *)statusMessage {
	if(self.stopAfter) {
		return @"Stopping once finished...";
	} else if(self.current) {
		return @"Playing...";
	} else if(self.queued) {
		return [NSString stringWithFormat:@"Queued: %lli", self.queuePosition + 1];
	} else if(self.error) {
		return self.errorMessage;
	}

	return nil;
}

// Gotta love that requirement of Core Data that everything starts with a lower case letter
@dynamic Unsigned;
- (BOOL)Unsigned {
	return self.unSigned;
}

- (void)setUnsigned:(BOOL)Unsigned {
	self.unSigned = Unsigned;
}

// More of the same
@dynamic URL;
- (NSURL *)URL {
	return self.url;
}

- (void)setURL:(NSURL *)URL {
	self.url = URL;
}

- (void)setMetadata:(NSDictionary *)metadata {
	if(metadata == nil) {
		self.error = YES;
		self.errorMessage = NSLocalizedStringFromTableInBundle(@"ErrorMetadata", nil, [NSBundle bundleForClass:[self class]], @"");
	} else {
		self.volume = 1;
		[self setValuesForKeysWithDictionary:metadata];
	}

	[self setMetadataLoaded:YES];
}

@dynamic playCountItem;
- (PlayCount *)playCountItem {
	NSPredicate *albumPredicate = [NSPredicate predicateWithFormat:@"album == %@", self.album];
	NSPredicate *artistPredicate = [NSPredicate predicateWithFormat:@"artist == %@", self.artist];
	NSPredicate *titlePredicate = [NSPredicate predicateWithFormat:@"title == %@", self.title];

	NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[albumPredicate, artistPredicate, titlePredicate]];

	NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
	request.predicate = predicate;

	NSError *error = nil;
	NSArray *results = [__persistentContainer.viewContext executeFetchRequest:request error:&error];

	if(!results || [results count] < 1) {
		NSPredicate *filenamePredicate = [NSPredicate predicateWithFormat:@"filename == %@", self.filename];

		request = [NSFetchRequest fetchRequestWithEntityName:@"PlayCount"];
		request.predicate = filenamePredicate;

		results = [__persistentContainer.viewContext executeFetchRequest:request error:&error];
	}

	if(!results || [results count] < 1) return nil;

	return results[0];
}

@dynamic playCount;
- (NSString *)playCount {
	PlayCount *pc = self.playCountItem;
	if(pc)
		return [NSString stringWithFormat:@"%llu", pc.count];
	else
		return @"0";
}

@dynamic playCountInfo;
- (NSString *)playCountInfo {
	PlayCount *pc = self.playCountItem;
	if(pc) {
		NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
		dateFormatter.dateStyle = NSDateFormatterMediumStyle;
		dateFormatter.timeStyle = NSDateFormatterShortStyle;

		if(pc.count) {
			return [NSString stringWithFormat:@"%@: %@\n%@: %@", NSLocalizedStringFromTableInBundle(@"TimeFirstSeen", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.firstSeen], NSLocalizedStringFromTableInBundle(@"TimeLastPlayed", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.lastPlayed]];
		} else {
			return [NSString stringWithFormat:@"%@: %@", NSLocalizedStringFromTableInBundle(@"TimeFirstSeen", nil, [NSBundle bundleForClass:[self class]], @""), [dateFormatter stringFromDate:pc.firstSeen]];
		}
	}
	return @"";
}

@end
