/*
 * Copyright 2003-2007, Haiku. All Rights Reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		jonas.sundstrom@kirilla.com
 */
 
 
#include <TranslationKit.h>
#include <Application.h>
#include <String.h>
#include <File.h>
#include <Path.h>
#include <Entry.h>
#include <Mime.h>
#include <NodeInfo.h>
 
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
 
 
extern const char *__progname;
const char *gProgramName = __progname;
bool gVerbose = false;
 
 
class TypeList {
	public:
		void Add(uint32 type);
		bool Remove(uint32 type);
		bool FindType(uint32 type);
 
		void SetTo(TypeList &types);
		int32 Count();
		uint32 TypeAt(int32 index);
 
	private:
		BList	fList;
};
 
class RemovingFile : public BFile {
	public:
		RemovingFile(const char *path);
		~RemovingFile();
		
		void Keep();
	
	private:
		BEntry	fEntry;
};
 
class Translator {
	public:
		Translator(const char *inPath, const char *outPath, uint32 outFormat);
		~Translator();
 
		status_t Translate();
 
	private:
		status_t Directly(BFile &input, translator_info &info, BFile &output);
		status_t Indirectly(BFile &input, BFile &output);
		status_t FindPath(const translation_format *info, BPositionIO &stream,
					TypeList &typesSeen, TypeList &path, double &pathQuality);
		status_t GetMimeTypeFromCode(uint32 type, char *mimeType);
 
	private:
		BTranslatorRoster *fRoster;
		BPath	fInputPath, fOutputPath;
		uint32	fOutputFormat;
};
 
class TranslateApp : public BApplication {
	public:
		TranslateApp(void);
		virtual ~TranslateApp();
 
		virtual void ReadyToRun(void);
		virtual void ArgvReceived(int32 argc, char **argv);
 
	private:
		void PrintUsage(void);
		void ListTranslators(uint32 type);
 
		uint32 GetTypeCodeForOutputMime(const char *mime);
		uint32 GetTypeCodeFromString(const char *string);
		status_t Translate(BFile &input, translator_info &translator, BFile &output, uint32 type);
		status_t Translate(BFile &input, BFile &output, uint32 type);
		status_t Translate(const char *inPath, const char *outPath, uint32 type);
 
		bool				fGotArguments;
		BTranslatorRoster	*fTranslatorRoster;
		int32				fTranslatorCount;
		translator_id		*fTranslatorArray; 
};
 
 
static void
print_tupel(const char *format, uint32 value)
{
	char tupel[5];
 
	for (int32 i = 0; i < 4; i++) {
		tupel[i] = (value >> (24 - i * 8)) & 0xff;
		if (!isprint(tupel[i]))
			tupel[i] = '.';
	}
	tupel[4] = '\0';
 
	printf(format, tupel);
}
 
 
static void
print_translation_format(const translation_format &format)
{
	print_tupel("'%s' ", format.type);
	print_tupel("(%s) ", format.group);
 
	printf("%.1f %.1f %s ; %s\n", format.quality, format.capability,
		format.MIME, format.name);
}
 
 
//	#pragma mark -
 
 
void
TypeList::Add(uint32 type)
{
	fList.AddItem((void *)(addr_t)type, 0);
}
 
 
bool
TypeList::Remove(uint32 type)
{
	return fList.RemoveItem((void *)(addr_t)type);
}
 
 
bool
TypeList::FindType(uint32 type)
{
	return fList.IndexOf((void *)(addr_t)type) >= 0;
}
 
 
void
TypeList::SetTo(TypeList &types)
{
	fList.MakeEmpty();
 
	for (int32 i = 0; i < types.Count(); i++)
		fList.AddItem((void *)(addr_t)types.TypeAt(i));
}
 
 
int32
TypeList::Count()
{
	return fList.CountItems();
}
 
 
uint32
TypeList::TypeAt(int32 index)
{
	return (uint32)(addr_t)fList.ItemAt(index);
}
 
 
//	#pragma mark -
 
 
RemovingFile::RemovingFile(const char *path)
	: BFile(path, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE),
	fEntry(path)
{
}
 
 
RemovingFile::~RemovingFile()
{
	fEntry.Remove();
}
 
 
void
RemovingFile::Keep()
{
	fEntry.Unset();
}
 
 
//	#pragma mark -
 
 
Translator::Translator(const char *inPath, const char *outPath, uint32 outputFormat)
	:
	fRoster(BTranslatorRoster::Default()),
	fInputPath(inPath),
	fOutputPath(outPath),
	fOutputFormat(outputFormat)
{
}
 
 
Translator::~Translator()
{
}
 
 
status_t
Translator::Translate()
{
	// input file
	BFile input;
	status_t status = input.SetTo(fInputPath.Path(), B_READ_ONLY);
	if (status != B_OK) {
		fprintf(stderr, "%s: could not open \"%s\": %s\n",
			gProgramName, fInputPath.Path(), strerror(status));
		return status;
	}
 
	// find a direct translator
	bool direct = true;
	translator_info translator;
	status = fRoster->Identify(&input, NULL, &translator, 0, NULL, fOutputFormat);
	if (status < B_OK) {
		// no direct translator found - let's try with something else
		status = fRoster->Identify(&input, NULL, &translator);
		if (status < B_OK) {
			fprintf(stderr, "%s: identifying \"%s\" failed: %s\n",
				gProgramName, fInputPath.Path(), strerror(status));
			return status;
		}
 
		direct = false;
	}
 
	// output file
	RemovingFile output(fOutputPath.Path());
	if ((status = output.InitCheck()) != B_OK) {
		fprintf(stderr, "%s: Could not create \"%s\": %s\n",
			gProgramName, fOutputPath.Path(), strerror(status));
		return status;
	}
 
	if (direct)
		status = Directly(input, translator, output);
	else
		status = Indirectly(input, output);
 
	if (status == B_OK) {
		output.Keep();
 
		// add filetype attribute
		update_mime_info(fOutputPath.Path(), false, true, false);
 
		char mimeType[B_ATTR_NAME_LENGTH];
		BNode node(fOutputPath.Path());
		BNodeInfo info(&node);
		if (info.GetType(mimeType) != B_OK || !strcasecmp(mimeType, B_FILE_MIME_TYPE)) {
			// the Registrar couldn't find a type for this file
			// so let's use the information we have from the
			// translator
			if (GetMimeTypeFromCode(fOutputFormat, mimeType) == B_OK)
				info.SetType(mimeType);
		}
	} else {
		fprintf(stderr, "%s: translating failed: %s\n",
			gProgramName, strerror(status));
	}
 
	return status;
}
 
 
/** Converts the input file to the output file using the
 *	specified translator.
 */
 
status_t
Translator::Directly(BFile &input, translator_info &info, BFile &output)
{
	if (gVerbose)
		printf("Direct translation from \"%s\"\n", info.name);
 
	return fRoster->Translate(&input, &info, NULL, &output, fOutputFormat);
}
 
 
/**	Converts the input file to the output file by computing the best
 *	quality path between the input type and the output type, and then
 *	applies as many translators as needed.
 */
 
status_t
Translator::Indirectly(BFile &input, BFile &output)
{
	TypeList translatorPath;
	TypeList handledTypes;
	double quality;
 
	if (FindPath(NULL, input, handledTypes, translatorPath, quality) != B_OK)
		return B_NO_TRANSLATOR;
 
	// The initial input stream is the input file which gets translated into
	// the first buffer. After that, the two buffers fill each other, until
	// the end is reached and the output file is the translation target
 
	BMallocIO buffer[2];
	BPositionIO *inputStream = &input;
	BPositionIO *outputStream = &buffer[0];
 
	for (int32 i = 0; i < translatorPath.Count(); i++) {
		uint32 type = translatorPath.TypeAt(i);
		if (type == fOutputFormat)
			outputStream = &output;
		else
			outputStream->SetSize(0);
 
		inputStream->Seek(0, SEEK_SET);
			// rewind the input stream
 
		status_t status = fRoster->Translate(inputStream, NULL, NULL, outputStream, type);
		if (status != B_OK)
			return status;
 
		// switch buffers
		inputStream = &buffer[i % 2];
		outputStream = &buffer[(i + 1) % 2];
 
		if (gVerbose)
			print_tupel("  '%s'\n", type);
	}
 
	return B_OK;
}
 
 
status_t
Translator::FindPath(const translation_format *format, BPositionIO &stream,
	TypeList &typesSeen, TypeList &path, double &pathQuality)
{
	translator_info *infos = NULL;
	translator_id *ids = NULL;
	int32 count;
	uint32 inFormat = 0;
	uint32 group = 0;
	status_t status;
 
	// Get a list of capable translators (or all of them)
 
	if (format == NULL) {
		status = fRoster->GetTranslators(&stream, NULL, &infos, &count);
		if (status == B_OK && count > 0) {
			inFormat = infos[0].type;
			group = infos[0].group;
 
			if (gVerbose) {
				puts("Indirect translation:");
				print_tupel("  '%s', ", inFormat);
				printf("%s\n", infos[0].name);
			}
		}
	} else {
		status = fRoster->GetAllTranslators(&ids, &count);
		inFormat = format->type;
		group = format->group;
	}
	if (status != B_OK || count == 0) {
		delete[] infos;
		delete[] ids;
		return status;
	}
 
	// build the best path to get from here to fOutputFormat recursively
	// (via depth search, best quality/capability wins)
 
	TypeList bestPath;
	double bestQuality = -1;
	status = B_NO_TRANSLATOR;
 
	for (int32 i = 0; i < count; i++) {
		const translation_format *formats;
		int32 formatCount;
		bool matches = false;
		int32 id = ids ? ids[i] : infos[i].translator;
		if (fRoster->GetInputFormats(id, &formats, &formatCount) != B_OK)
			continue;
 
		// see if this translator is good enough for us
		for (int32 j = 0; j < formatCount; j++) {
			if (formats[j].type == inFormat) {
				matches = true;
				break;
			}
		}
 
		if (!matches)
			continue;
 
		// search output formats
 
		if (fRoster->GetOutputFormats(id, &formats, &formatCount) != B_OK)
			continue;
 
		typesSeen.Add(inFormat);
 
		for (int32 j = 0; j < formatCount; j++) {
			if (formats[j].group != group || typesSeen.FindType(formats[j].type))
				continue;
 
			double formatQuality = formats[j].quality * formats[j].capability;
 
			if (formats[j].type == fOutputFormat) {
				// we've found our target type, so we can stop the path here
				bestPath.Add(formats[j].type);
				bestQuality = formatQuality;
				status = B_OK;
				continue;
			}
 
			TypeList path;
			double quality;
			if (FindPath(&formats[j], stream, typesSeen, path, quality) == B_OK) {
				if (bestQuality < quality * formatQuality) {
					bestQuality = quality * formatQuality;
					bestPath.SetTo(path);
					bestPath.Add(formats[j].type);
					status = B_OK;
				}
			}
		}
 
		typesSeen.Remove(inFormat);
	}
 
	if (status == B_OK) {
		pathQuality = bestQuality;
		path.SetTo(bestPath);
	}
	delete[] infos;
	delete[] ids;
	return status;
}
 
 
status_t
Translator::GetMimeTypeFromCode(uint32 type, char *mimeType)
{
	translator_id *ids = NULL;
	int32 count;
	status_t status = fRoster->GetAllTranslators(&ids, &count);
	if (status != B_OK)
		return status;
 
	status = B_NO_TRANSLATOR;
 
	for (int32 i = 0; i < count; i++) {
		const translation_format *format = NULL;
		int32 formatCount = 0;
		fRoster->GetOutputFormats(ids[i], &format, &formatCount);
 
		for (int32 j = 0; j < formatCount; j++) {
			if (type == format[j].type) {
				strcpy(mimeType, format[j].MIME);
				status = B_OK;
				break;
			}
		}
	}
 
	delete[] ids;
	return status;
}
 
 
//	#pragma mark -
 
 
TranslateApp::TranslateApp(void)
	: BApplication("application/x-vnd.haiku-translate"),
	fGotArguments(false),
	fTranslatorRoster(BTranslatorRoster::Default()),
	fTranslatorCount(0),
	fTranslatorArray(NULL)
{
	fTranslatorRoster->GetAllTranslators(&fTranslatorArray, &fTranslatorCount);
}
 
 
TranslateApp::~TranslateApp()
{
	delete[] fTranslatorArray;
}
 
 
void
TranslateApp::ArgvReceived(int32 argc, char **argv)
{
	if (argc < 2
		|| !strcmp(argv[1], "--help"))
		return;
 
	if (!strcmp(argv[1], "--list")) {
		fGotArguments = true;
		
		uint32 type = B_TRANSLATOR_ANY_TYPE;
		if (argc > 2)
			type = GetTypeCodeFromString(argv[2]);
 
		ListTranslators(type);
		return;
	}
 
	if (!strcmp(argv[1], "--verbose")) {
		fGotArguments = true;
		argc--;
		argv++;
		gVerbose = true;
	}
 
	if (argc != 4)
		return;
 
	fGotArguments = true;
 
	// get typecode of output format
	uint32 outputFormat = 0;
	BMimeType mime(argv[3]);
 
	if (mime.IsValid() && !mime.IsSupertypeOnly()) {
		// MIME-string
		outputFormat = GetTypeCodeForOutputMime(argv[3]);
	} else
		outputFormat = GetTypeCodeFromString(argv[3]);
 
	if (outputFormat == 0) {
		fprintf(stderr, "%s: bad format: %s\nformat is 4-byte type code or full MIME type\n",
			gProgramName, argv[3]);
		exit(-1);
	}
 
	Translator translator(argv[1], argv[2], outputFormat);
	status_t status = translator.Translate();
	if (status < B_OK)
		exit(-1);
}
 
 
void
TranslateApp::ReadyToRun(void)
{
	if (fGotArguments == false)
		PrintUsage();
 
	PostMessage(B_QUIT_REQUESTED);
}
 
 
void
TranslateApp::PrintUsage(void)
{
	printf("usage: %s { --list [type] | input output format }\n"
		"\t\"format\" can expressed as 4-byte type code (ie. 'TEXT') or as MIME type.\n",
		gProgramName);
}
 
 
void
TranslateApp::ListTranslators(uint32 type)
{
	for (int32 i = 0; i < fTranslatorCount; i++) { 
		const char *name = NULL;
		const char *info = NULL; 
		int32 version = 0;
		if (fTranslatorRoster->GetTranslatorInfo(fTranslatorArray[i], &name, &info, &version) != B_OK)
			continue;
 
		const translation_format *inputFormats = NULL;
		const translation_format *outputFormats = NULL;
		int32 inCount = 0, outCount = 0;
		fTranslatorRoster->GetInputFormats(fTranslatorArray[i], &inputFormats, &inCount);
		fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &outputFormats, &outCount);
 
		// test if the translator has formats of the specified type
		
		if (type != B_TRANSLATOR_ANY_TYPE) {
			bool matches = false;
 
			for (int32 j = 0; j < inCount; j++) {
				if (inputFormats[j].group == type || inputFormats[j].type == type) {
					matches = true;
					break;
				}
			}
 
			for (int32 j = 0; j < outCount; j++) {
				if (outputFormats[j].group == type || outputFormats[j].type == type) {
					matches = true;
					break;
				}
			}
			
			if (!matches)
				continue;
		}
 
		printf("name: %s\ninfo: %s\nversion: %" B_PRId32 ".%" B_PRId32 ".%"
			B_PRId32 "\n", name, info,
			B_TRANSLATION_MAJOR_VERSION(version),
			B_TRANSLATION_MINOR_VERSION(version),
			B_TRANSLATION_REVISION_VERSION(version)); 
 
		for (int32 j = 0; j < inCount; j++) {
			printf("  input:\t");
			print_translation_format(inputFormats[j]);
		}
 
		for (int32 j = 0; j < outCount; j++) {
			printf("  output:\t");
			print_translation_format(outputFormats[j]);
		}
 
		printf("\n");
	}
}
 
 
uint32
TranslateApp::GetTypeCodeForOutputMime(const char *mime)
{
	for (int32 i = 0; i < fTranslatorCount; i++) {
		const translation_format *format = NULL;
		int32 count = 0;
		fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &format, &count);
 
		for (int32 j = 0; j < count; j++) {
			if (!strcmp(mime, format[j].MIME))
				return format[j].type;
		}
	}
 
	return 0;
}
 
 
uint32
TranslateApp::GetTypeCodeFromString(const char *string)
{
	size_t length = strlen(string);
	if (length > 4)
		return 0;
 
	uint8 code[4] = {' ', ' ', ' ', ' '};
 
	for (uint32 i = 0; i < length; i++)
		code[i] = (uint8)string[i];
 
	return B_HOST_TO_BENDIAN_INT32(*(uint32 *)code);
}
 
 
//	#pragma mark -
 
 
int
main()
{
	new TranslateApp();
	be_app->Run();
 
	return B_OK;
}
 

V672 There is probably no need in creating the new 'path' variable here. One of the function's arguments possesses the same name and this argument is a reference. Check lines: 348, 429.