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

#import "PlaylistEntry.h"
#import "AVIFDecoder.h"
#import "SecondsFormatter.h"

@implementation PlaylistEntry

@synthesize index;
@synthesize shuffleIndex;
@synthesize dbIndex;
@synthesize entryId;
@synthesize artId;

@synthesize current;
@synthesize removed;

@synthesize stopAfter;

@synthesize queued;
@synthesize queuePosition;

@synthesize error;
@synthesize errorMessage;

@synthesize URL;
@synthesize trashURL;

@synthesize artist;
@synthesize albumartist;
@synthesize album;
@synthesize genre;
@synthesize year;
@synthesize track;
@synthesize disc;

@synthesize cuesheet;

@synthesize totalFrames;
@synthesize bitrate;
@synthesize channels;
@synthesize channelConfig;
@synthesize bitsPerSample;
@synthesize floatingPoint;
@synthesize Unsigned;
@synthesize sampleRate;

@synthesize codec;

@synthesize replayGainAlbumGain;
@synthesize replayGainAlbumPeak;
@synthesize replayGainTrackGain;
@synthesize replayGainTrackPeak;
@synthesize volume;

@synthesize currentPosition;

@synthesize endian;

@synthesize encoding;

@synthesize seekable;

@synthesize metadataLoaded;

@synthesize deleted;

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

+ (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", @"title", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", 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];
}

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

- (id)init {
	if(self = [super init]) {
		self.replayGainAlbumGain = 0;
		self.replayGainAlbumPeak = 0;
		self.replayGainTrackGain = 0;
		self.replayGainTrackPeak = 0;
		self.volume = 1;
		self.deleted = NO;
	}
	return self;
}

- (void)dealloc {
	self.errorMessage = nil;

	self.URL = nil;

	self.artist = nil;
	self.albumartist = nil;
	self.album = nil;
	self.title = nil;
	self.genre = nil;
	self.year = nil;
	self.track = nil;
	self.disc = nil;
	self.albumArtInternal = nil;

	self.cuesheet = nil;

	self.endian = nil;
	self.codec = nil;
}

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

@synthesize rawTitle;
- (NSString *)rawTitle {
	return title;
}

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

@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) && ([self.track intValue] != 0);
	BOOL hasLength = (self.totalFrames != 0);
	BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current);
	BOOL hasExtension = NO;
	BOOL hasTitle = (title != nil) && (![title 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:title];
	} 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:[NSNumber numberWithFloat: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 intValue]) {
		if([self.disc intValue]) {
			return [NSString stringWithFormat:@"%@.%02u", self.disc, [self.track intValue]];
		} else {
			return [NSString stringWithFormat:@"%02u", [self.track intValue]];
		}
	} else {
		return @"";
	}
}

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

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

@dynamic gainCorrection;
- (NSString *)gainCorrection {
	if(replayGainAlbumGain) {
		if(replayGainAlbumPeak)
			return @"Album Gain plus Peak";
		else
			return @"Album Gain";
	} else if(replayGainTrackGain) {
		if(replayGainTrackPeak)
			return @"Track Gain plus Peak";
		else
			return @"Track Gain";
	} else if(volume && volume != 1) {
		return @"Volume scale";
	} else {
		return @"None";
	}
}

@dynamic gainInfo;
- (NSString *)gainInfo {
	NSMutableArray *gainItems = [[NSMutableArray alloc] init];
	if(replayGainAlbumGain) {
		[gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", replayGainAlbumGain]];
	}
	if(replayGainAlbumPeak) {
		[gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", replayGainAlbumPeak]];
	}
	if(replayGainTrackGain) {
		[gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", replayGainTrackGain]];
	}
	if(replayGainTrackPeak) {
		[gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", replayGainTrackPeak]];
	}
	if(volume && volume != 1) {
		[gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", volume, (unichar)0x00D7]];
	}
	return [gainItems componentsJoinedByString:@"\n"];
}

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

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

@synthesize albumArtInternal;

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

	NSString *imageCacheTag = [NSString stringWithFormat:@"%ld", artId];
	NSImage *image = [NSImage imageNamed:imageCacheTag];

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

	return image;
}

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

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

@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: %li", self.queuePosition + 1];
	} else if(self.error) {
		return errorMessage;
	}

	return nil;
}

- (void)setMetadata:(NSDictionary *)metadata {
	if(metadata == nil) {
		self.error = YES;
		self.errorMessage = @"Unable to retrieve metadata.";
	} else {
		[self setValuesForKeysWithDictionary:metadata];
	}

	[self setMetadataLoaded:YES];
}

// Now we duplicate the object to a new handle, but merely reference the same data
- (id)copyWithZone:(NSZone *)zone {
	PlaylistEntry *pe = [[[self class] allocWithZone:zone] init];

	if(pe) {
		pe->index = index;
		pe->shuffleIndex = shuffleIndex;
		pe->dbIndex = dbIndex;
		pe->entryId = entryId;
		pe->artId = artId;

		pe->current = current;
		pe->removed = removed;

		pe->stopAfter = stopAfter;

		pe->queued = queued;
		pe->queuePosition = queuePosition;

		pe->error = error;
		pe->errorMessage = errorMessage;

		pe->URL = URL;

		pe->artist = artist;
		pe->albumartist = albumartist;
		pe->album = album;
		pe->title = title;
		pe->genre = genre;
		pe->year = year;
		pe->track = track;
		pe->disc = disc;

		pe->cuesheet = cuesheet;

		pe->albumArtInternal = albumArtInternal;

		pe->replayGainAlbumGain = replayGainAlbumGain;
		pe->replayGainAlbumPeak = replayGainAlbumPeak;
		pe->replayGainTrackGain = replayGainTrackGain;
		pe->replayGainTrackPeak = replayGainTrackPeak;
		pe->volume = volume;

		currentPosition = pe->currentPosition;

		pe->totalFrames = totalFrames;
		pe->bitrate = bitrate;
		pe->channels = channels;
		pe->channelConfig = channelConfig;
		pe->bitsPerSample = bitsPerSample;
		pe->floatingPoint = floatingPoint;
		pe->Unsigned = Unsigned;
		pe->sampleRate = sampleRate;

		pe->codec = codec;

		pe->endian = endian;

		pe->encoding = encoding;

		pe->seekable = seekable;

		pe->metadataLoaded = metadataLoaded;

		pe->deleted = deleted;
	}

	return pe;
}

@end