/**
 * Copyright (C) 2007 Bryan Varner
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * $Id: TRApplication.cpp 4182 2007-12-17 06:30:42Z charles $
 */

#include "TRApplication.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <AppFileInfo.h>
#include <Alert.h>
#include <Bitmap.h>
#include <File.h>
#include <FindDirectory.h>
#include <Messenger.h>
#include <Mime.h>
#include <Path.h>
#include <String.h>
#include <Resources.h>
#include <Roster.h>

#ifdef __BEOS__
	// work-around to get rid of this stupid find_directory_r() on Zeta
#ifdef find_directory
#undef find_directory
#endif
#endif

int main(int, char**) {
	TRApplication *app = new TRApplication();
	if (app->InitCheck() == B_OK) {
		app->Run();
	} else {
		BString errMsg("");
		errMsg << "The following error occurred loading Transmission:\n"
		       << strerror(app->InitCheck());
		BAlert *ohNo = new BAlert("Transmission Error", 
			errMsg.String(),
			"OK", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
		ohNo->Go();
	}
	delete app;
	
	return (0);
}


TRApplication::TRApplication() : BApplication(APP_SIG) {
	status_t err = B_OK;
	torrentDir = NULL;
	
	// Install Mime types if necessary.
	BMimeType torrentType("application/x-bittorrent");
	if (torrentType.InitCheck() == B_OK) {
		if (!torrentType.IsInstalled()) {
			fprintf(stderr, "Installing mime type...\n");
			// Icon
			app_info runningInfo;
			GetAppInfo(&runningInfo);
			BFile appFile(&(runningInfo.ref), B_READ_ONLY);
			BResources res(&appFile, false);
			
			size_t len = 0;
			BBitmap *icon = NULL;
			void *iconBits = NULL;
			
			iconBits = res.FindResource('ICON', "BEOS:L:application/x-bittorrent", &len);
			if (iconBits) {
				icon = new BBitmap(BRect(0, 0, 31, 31), B_CMAP8);
				icon->SetBits(iconBits, len, 0, B_CMAP8);
				torrentType.SetIcon(icon, B_LARGE_ICON);
				delete icon;
				icon = NULL;
				len = 0;
			}
			
			iconBits = res.FindResource('ICON', "BEOS:M:application/x-bittorrent", &len);
			if (iconBits) {
				icon = new BBitmap(BRect(0, 0, 15, 15), B_CMAP8);
				icon->SetBits(iconBits, len, 0, B_CMAP8);
				torrentType.SetIcon(icon, B_MINI_ICON);
				delete icon;
			}
			
			// Extensions
			BMessage extensions;
			extensions.AddString("extensions", "torrent");
			torrentType.SetFileExtensions(&extensions);
			
			torrentType.SetShortDescription("BitTorrent Meta File");
			torrentType.SetLongDescription("BitTorrent Protocol Meta File (http://www.bittorrent.com)");
			
			// Set Preferred Application
			torrentType.SetPreferredApp(APP_SIG, B_OPEN);
			
			torrentType.Install();
		}
	}
	
	// Create Settings folder and structure
	BPath settingsPath;
	err = find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath, false);
	if (err == B_OK) {
		BDirectory settings(settingsPath.Path());
		BDirectory *trSettings = new BDirectory();
		
		err = settings.CreateDirectory("Transmission", trSettings);
		if (err == B_FILE_EXISTS) {
			err = trSettings->SetTo(&settings, "Transmission");
		}
		
		if (err == B_OK) {
			torrentDir = new BDirectory();
			err = trSettings->CreateDirectory("Torrents", torrentDir);
			if (err != B_OK) {
				torrentDir->SetTo(trSettings, "Torrents");
			}
		}
		delete trSettings;
	}
	
	// Create window
	window = new TRWindow();
	window->RescanTorrents();
	settings = new TRPrefsWindow();
	
	openPanel = new BFilePanel();
	openPanel->SetRefFilter(new TRFilter());
}


TRApplication::~TRApplication() {
	if (window->Lock()) {
		window->Quit();
	}
	if (settings->Lock()) {
		settings->Quit();
	}
	
	if (openPanel != NULL) {
		delete openPanel;
	}
	
	if (torrentDir != NULL) {
		delete torrentDir;
	}
}


/**
 * Checks to make sure our Settings directory structure is in place.
 * If this fails, then we need to display an alert and vomit.
 */
status_t TRApplication::InitCheck() {
	if (torrentDir != NULL) {
		return torrentDir->InitCheck();
	}
	return B_ERROR;
}


void TRApplication::AboutRequested() {
	// Read the application version info
	app_info runningInfo;
	GetAppInfo(&runningInfo);
	BFile appFile(&(runningInfo.ref), B_READ_ONLY);
	BAppFileInfo appInfo(&appFile);
	version_info vInfo;
	appInfo.GetVersionInfo(&vInfo, B_APP_VERSION_KIND);
	
	BString aboutMsg("");
	aboutMsg << "Transmission : "	         
	         << "Version " << LONG_VERSION_STRING << '\n'
	         << "GUI : "
	         << "Version " << vInfo.major << "." << vInfo.middle << "." << vInfo.minor << "\n"
	         << "\nMade for BeOS/Haiku/Zeta by\n" 
	         << "===========================\n"
	         << "Eric Petit & Bryan Varner\n";
	BAlert *aboutBox = new BAlert("About Transmission", aboutMsg.String(), "Close");
	aboutBox->Go();
}


void TRApplication::ReadyToRun() {
	SetPulseRate(500000);
	window->Show();
}


void TRApplication::Pulse() {
	window->UpdateList(-1, false);
}


/**
 * When a ref is received, we copy each file that is to be opened into
 * B_USER_SETTINGS/Transmission/Torrents
 * 
 * Our window node_monitors this folder for added / removed torrent meta files.
 *
 * Each copy is performed in a seperate thread to avoid blocking / locking the app.
 */
void TRApplication::RefsReceived(BMessage *message) {
	int32 count;
	type_code code;
	message->GetInfo("refs", &code, &count);
	for (int i = 0; i < count; i++) {
		entry_ref *ref = (entry_ref*)calloc(sizeof(entry_ref), 1);
		if (message->FindRef("refs", i, ref) == B_OK) {
			thread_id cp_thread = spawn_thread(TRApplication::Copy, "TorrentDuper",
			                                   B_NORMAL_PRIORITY, (void *)ref);
			if (!((cp_thread) < B_OK)) {
				resume_thread(cp_thread);
			}
		}
	}
}

/**
 * Needed for browsers or command line interaction
 */
void TRApplication::ArgvReceived(int32 _argc, char** _argv) 
{
	entry_ref ref;
	BMessage refs(B_REFS_RECEIVED);
	for( int32 i = 0; i < _argc; ++i )
	{
		if( B_OK == get_ref_for_path(_argv[i], &ref) )
		{
			refs.AddRef("refs", &ref);
		}
	}
	
	be_app_messenger.SendMessage(&refs);
}

/**
 * BMessage handling. 
 */
void TRApplication::MessageReceived(BMessage *message) {
	BApplication::MessageReceived(message);
	/* 
	 * When the copy of a torrent file is complete, we get a message
	 * signaling that we should add the now copied .torrent file to our Transfer Window.
	 */
 	if (message->what == TR_ADD) {
		// Add the torrent to the window.
		entry_ref torrentRef;
		if (message->FindRef("torrent", &torrentRef) == B_OK) {
			BEntry *entry = new BEntry(&torrentRef, true);
			window->AddEntry(entry);
			delete entry;
		}
	/* Show the Open Dialog */
	} else if (message->what == TR_OPEN) {
		openPanel->Show();
	} else if (message->what == TR_SETTINGS) {
		settings->MoveTo(window->Frame().left + (window->Frame().Width() - settings->Frame().Width()) / 2, 
		                 window->Frame().top + (window->Frame().Height() - settings->Frame().Height()) / 2);
			
		settings->Show();
	} else if (message->what == TR_RELOAD_SETTINGS) {
		window->LoadSettings();
	}
}

bool TRApplication::QuitRequested() {
	return BApplication::QuitRequested();
}

/**
 * Thread Function.
 * The thread copies the original .torrent file into the torrents folder,
 * then signals the window to load the torrent meta info from the copy.
 * The torrent window will then node_monitor the file so that if it's removed
 * the torrent will cease.
 *
 * Keeping the file copy in a separate thread keeps us from blocking up the
 * rest of our program.
 * 
 * This behavior lets us kill the original .torrent download from disc (which
 * speaking as a user I have a tendency to do) but keep the meta around for
 * Transmission to use later.
 *
 * If the user whacks the .torrent file from our settings folder, then we'll
 * remove it from the list (node_monitor!) and if they remove it from our list
 * we'll remove it from the file system.
 */
int32 TRApplication::Copy(void *data) {
	entry_ref *ref = (entry_ref*)data;
	
	BFile source(ref, B_READ_ONLY);
	BFile target(((TRApplication*)be_app)->TorrentDir(), ref->name, 
	             B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS);
	
	BEntry targetEntry(((TRApplication*)be_app)->TorrentDir(), ref->name);
	entry_ref targetRef;
	targetEntry.GetRef(&targetRef);
	
	// Only perform the copy if the target is freshly created. Everything is B_OK!
	if (source.InitCheck() == B_OK && target.InitCheck() == B_OK) {
		if (target.Lock() == B_OK) {
			char *buffer = (char*)calloc(1, 4096); // 4k data buffer.
			ssize_t read = 0;
			while ((read = source.Read(buffer, 4096)) > 0) {
				target.Write(buffer, read);
			}
			free(buffer);
			target.Unlock();
		}
	}
	
	BMessage msg(TR_ADD);
	msg.AddRef("torrent", &targetRef);
	BMessenger messenger(be_app);
	messenger.SendMessage(&msg);
	
	free(ref);
	return B_OK;
}

/**
 * Filters the FilePanel for torrent files and directories.
 */
bool TRFilter::Filter(const entry_ref *ref, BNode *node, struct stat *, const char *mimetype) {
	return (node->IsDirectory() || 
           (strcmp(mimetype, "application/x-bittorrent") == 0) ||
           (strstr(ref->name, ".torrent") != NULL));

}
