/* vim: set ft=objc ts=4 nowrap: */
/*
 *  Player.m
 *
 *  Copyright (c) 1999 - 2003
 *
 *  Author: ACKyugo <ackyugo@geocities.co.jp>
 *	    Andreas Schik <andreas@schik.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "Player.h"
#include "LED.h"
#include "TrackList.h"

static BOOL mustReadTOC = NO;
static Player *sharedPlayer = nil;

@implementation Player

- init
{
	if (sharedPlayer) {
		[self dealloc];
	} else {
		self = [super init];

		sharedPlayer = self;

		track = 1;
		drive = nil;
		autoPlay = NO;

		// we must already create the (hidden) track list
		[TrackList sharedTrackList];

		if (![self loadAudioCDBundle]) {
			[self release];
			return nil;
		}

		timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
									target: self
								  selector: @selector(timer:)
					 			  userInfo: self
								   repeats: YES];

		[[NSNotificationCenter defaultCenter] addObserver: self
							   selector: @selector(playTrack:)
							   name: @"PlayTrack"
							   object: nil];
	}
	return sharedPlayer;
}

- (void) dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver: self
					   name: @"PlayTrack"
					   object: nil];
	[drive release];
	[timer invalidate];
	[timer release];

	[led release];
	[window release];
	[prev release];
	[play release];
	[pause release];
	[stop release];
	[next release];

	[super dealloc];
}

- (BOOL) loadAudioCDBundle
{
	int i;
	NSString	*path;
	NSArray		*searchPaths;

	// try to load the AudioCD bundle
	searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
							NSUserDomainMask|NSLocalDomainMask|NSSystemDomainMask, YES);

	for (i = 0; i < [searchPaths count]; i++) {
		NSBundle *bundle;
		NSString *bundlePath = [NSString stringWithFormat:
						@"%@/Bundles/AudioCD.bundle",
						[searchPaths objectAtIndex: i]];

		bundle = [NSBundle bundleWithPath: bundlePath];
		if (bundle) {
			audiocdClass = [bundle principalClass];
			if (audiocdClass) {
				break;
			}
		}
	}   // for (i = 0; i < [searchPaths count]; i++)

	if (!audiocdClass) {
		NSRunAlertPanel(@"CDPlayer",
				_(@"Couldn't find AudioCD bundle."),
				_(@"Exit"), nil, nil);
		exit(-1);
	}

	path = [[NSUserDefaults standardUserDefaults] stringForKey: @"Device"];
	drive = [[audiocdClass alloc] initWithHandler: self];
	[drive startPollingWithPreferredDevice: path];

	return YES;
}


//
// 
//
- (void) timer: (id)timer
{
	NSString	*path;
	int		state;

	path = [[NSUserDefaults standardUserDefaults] stringForKey: @"Device"];

	if (mustReadTOC) {
		[[TrackList sharedTrackList] setTOC: [drive readTOC]];
		mustReadTOC = NO;
	}

	// is a disc in the drive?
	if([drive cdPresent] == NO) {
		[led setNoCD];
		[led display];
		return;
	}

	if (autoPlay) {
		[drive playStart: 1 End: [drive totalTrack]];
		autoPlay = NO;
	}

	state = [drive currentState];

	if(state == AUDIOCD_PLAYING) {
		[led setMin: [drive currentMin]];
		[led setSec: [drive currentSec]];
    	track = [drive currentTrack];
	} else if(state == AUDIOCD_NOSTATUS) {
		[led setMin: 0];
		[led setSec: 0];
	}
	[led setTrack: track];
	[led display];
	[[TrackList sharedTrackList] setPlaysTrack: track];
}



//
//
//

- (void) playTrack: (NSNotification *)not
{
	int nextTrack = [[[not userInfo] objectForKey: @"Track"] intValue];

    if (nextTrack == track)
		return;

	track = nextTrack;
	[drive playStart: track End: [drive totalTrack]];
	[led setTrack: track];
	[led display];
	[[TrackList sharedTrackList] setPlaysTrack: track];
}


- (void) play: (id)sender
{
	if([drive cdPresent] == NO) {
		return;
	}

	if ([drive currentState] == AUDIOCD_PAUSED) {
		[drive resume];
	} else {
		[drive playStart: track End: [drive totalTrack]];
	}
}

- (void) pause: (id)sender
{
	if ([drive cdPresent] == NO) {
		return;
	}

	if ([drive currentState] == AUDIOCD_PLAYING) {
		[drive pause];
	} else if ([drive currentState] == AUDIOCD_PAUSED) {
		[drive resume];
	}
}

- (void) stop: (id)sender
{
	// the 'stop' button is also 'eject' if CD is already halted,
	// but not the Controller, which may also stop the CD on exit
	if(sender == stop) {
		if ([drive cdPresent] == NO) {
			[drive close];
			return;
		}

		if((sender == stop) && ([drive currentState] == AUDIOCD_NOSTATUS)) {
			[drive eject];
			return;
		}
	}

	[drive stop];
	track = 1;
	[led setTrack: track];
	[led setMin: 0];
	[led setSec: 0];
	[led display];
	[[TrackList sharedTrackList] setPlaysTrack: 0];
}

- (void) eject: (id)sender
{
	if([drive cdPresent] == NO) {
		return;
	}

	[drive eject];
}

- (void) next: (id)sender
{
	if([drive cdPresent] == NO) {
		return;
	}

	track++;
	if(track > [drive totalTrack]) {
		track = 1;
	}

	if(([drive currentState] == AUDIOCD_PLAYING) ||
	   ([drive currentState] == AUDIOCD_PAUSED)) {
		[drive playStart: track End: [drive totalTrack]];
	}
	[led setTrack: track];
	[led display];
	[[TrackList sharedTrackList] setPlaysTrack: track];
}

- (void) prev: (id)sender
{
	if([drive cdPresent] == NO) {
		return;
	}

	// We jump back only if we are not playing at the moment
	// or if we are at the very beginning of a playing track.
	// The latter condition allows to jump back to the beginning
	// of the current track before jumping back one more track.
	if (([drive currentState] == AUDIOCD_NOSTATUS) ||
		([drive currentSec] == 0 && [drive currentMin] == 0)) {
		track--;
		if(track < 1) {
			track = [drive totalTrack];
		}
	}

	if(([drive currentState] == AUDIOCD_PLAYING) ||
	   ([drive currentState] == AUDIOCD_PAUSED)) {
		[drive playStart: track End: [drive totalTrack]];
	}

	[led setTrack: track];
	[led display];
	[[TrackList sharedTrackList] setPlaysTrack: track];
}


//
//
//
//
- (BOOL) audioCD: (id)sender error: (int)no message: (NSString *)msg
{
	// FIXME: Take appropriate action when an error occurs!!
//	NSRunAlertPanel(@"CDPlayer",
//			msg,
//			_(@"OK"), nil, nil);
	NSLog(@"CDPlayer error: %@", msg);
	return YES;
}

- (void) audioCDChanged: (id)sender
{
	mustReadTOC = YES;
}


- (BOOL) windowShouldClose: (id)sender
{
	[[NSApplication sharedApplication ] terminate: self];
	return YES;
}


//
//
//
- (void) buildInterface
{
	NSRect		frame;
	unsigned int    style = NSTitledWindowMask | NSClosableWindowMask |
				NSMiniaturizableWindowMask;
	NSBundle	*bundle = [NSBundle mainBundle];
	NSImage		*image;
	NSString	*path;


	frame = NSMakeRect(100, 100, 160, 73);
	window = [[NSWindow alloc] initWithContentRect: frame
					     styleMask: style
					       backing: NSBackingStoreRetained
					         defer: NO];
	[window setTitle: @"CDPlayer"];
	[window setDelegate: self];

	frame = NSMakeRect(5, 38, 150, 30);
	led = [[LED alloc]  initWithFrame: frame];

	[led setNoCD];
	[led display];
	[[window contentView] addSubview: led];

	path = [bundle pathForResource: @"prev" ofType: @"tiff"];
	image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease];
	if(image == nil)    NSLog(@"cannot load prev.tiff");
	frame = NSMakeRect( 5, 5, 30, 30);
	prev = [[NSButton alloc] initWithFrame: frame];
	[prev setButtonType: NSMomentaryPushButton];
	[prev setImagePosition: NSImageOnly];
	[prev setImage: image];
	[prev setTarget: self];
	[prev setAction: @selector(prev:)];
	[[window contentView] addSubview: prev];

	path = [bundle pathForResource: @"play" ofType: @"tiff"];
	image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease];
	if(image == nil)    NSLog(@"cannot load play.tiff");
	frame = NSMakeRect( 35, 5, 30, 30);
	play = [[NSButton alloc] initWithFrame: frame];
	[play setButtonType: NSMomentaryPushButton];
	[play setImagePosition: NSImageOnly];
	[play setImage: image];
	[play setTarget: self];
	[play setAction: @selector(play:)];
	[[window contentView] addSubview: play];

	path = [bundle pathForResource: @"pause" ofType: @"tiff"];
	image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease];
	if(image == nil)    NSLog(@"cannot load pause.tiff");
	frame = NSMakeRect( 65, 5, 30, 30);
	pause = [[NSButton alloc] initWithFrame: frame];
	[pause setButtonType: NSMomentaryPushButton];
	[pause setImagePosition: NSImageOnly];
	[pause setImage: image];
	[pause setTarget: self];
	[pause setAction: @selector(pause:)];
	[[window contentView] addSubview: pause];

	path = [bundle pathForResource: @"next" ofType: @"tiff"];
	image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease];
	if(image == nil)    NSLog(@"cannot load next.tiff");
	frame = NSMakeRect(95, 5, 30, 30);
	next = [[NSButton alloc] initWithFrame: frame];
	[next setButtonType: NSMomentaryPushButton];
	[next setImagePosition: NSImageOnly];
	[next setImage: image];
	[next setTarget: self];
	[next setAction: @selector(next:)];                                          
	[[window contentView] addSubview: next];

	path = [bundle pathForResource: @"stop" ofType: @"tiff"];
	image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease];
	if(image == nil)    NSLog(@"cannot load stop.tiff");
	frame = NSMakeRect( 125, 5, 30, 30);
	stop = [[NSButton alloc] initWithFrame: frame];
	[stop setButtonType: NSMomentaryPushButton];
	[stop setImagePosition: NSImageOnly];
	[stop setImage: image];
	[stop setTarget: self];
	[stop setAction: @selector(stop:)];
	[[window contentView] addSubview: stop];

	[window orderFront: self];
    [window setFrameAutosaveName: @"CDPlayerWindow"];
    [window setFrameUsingName: @"CDPlayerWindow"];
}

//
// services methods
//

- (void) getTOC: (NSPasteboard *) pboard
	   userData: (NSString *) userData
		  error: (NSString **) error
{
	TrackList *tl = [TrackList sharedTrackList];
	int i, rows = [tl numberOfTracksInTOC];
	NSMutableArray *array;

	/*
	 * If we don't have any rows, there is probably no CD.
	 */
	if (rows == 0) {
		*error = _(@"No Audio CD found.");
		return;
	}

	array = [[NSMutableArray alloc] init];

	for (i = 0; i < rows; i++) {
		[array addObject: [NSNumber numberWithInt: i]];
	}

	/*
	 * This is a small hack, but we can clean this up later.
	 */
	if (![tl tableView: nil writeRows: array toPasteboard: pboard]) {
		*error = _(@"Could not write TOC to pasteboard.");
	} else {
	}

	RELEASE(array);
}

- (void) playCD: (NSPasteboard *) pboard
	   userData: (NSString *) userData
		  error: (NSString **) error
{
	NSArray *types = [pboard types];

	// If CD is currently playing, we reject the request
	if([drive currentState] == AUDIOCD_PLAYING) {
        *error = _(@"Player.alreadyPlaying");
        return;
	}

	/*
	 * Do we have at least one valid pasteboard type?
	 */
	if (![types containsObject: NSFilenamesPboardType] &&
			![types containsObject: NSStringPboardType]) {
        *error = _(@"Player.noValidPboardType");
        return;
    }

	/*
	 * Try to add as much as possible, i.e. even if one pasteboard
	 * type fails try the other one (if it exists in the pasteboard).
	 */
	if ([types containsObject: NSFilenamesPboardType] ||
			[types containsObject: NSStringPboardType]) {
		// Get the device name from the pboard
		NSString *device = nil;
		NSArray *devices = [pboard propertyListForType: NSFilenamesPboardType];
		if (devices != nil) {
			if ([devices count] != 1) {
				*error = _(@"Player.tooManyFileNames");
				return;
			}
			device = [devices objectAtIndex: 0];
		} else {
			device = [pboard propertyListForType: NSStringPboardType];
		}
		if (device == nil) {
			*error = _(@"Player.noDeviceNameFound");
			return;
		}
		autoPlay = YES;
		[drive stopPolling];
		[drive startPollingWithPreferredDevice: device];
	}
}

//
// class methods
//
+ (Player *) sharedPlayer
{
	if (!sharedPlayer) {
		sharedPlayer = [[Player alloc] init];
	}
	return sharedPlayer;
}


@end
