/*
 * Copyright 2004-2006, JĆ©rĆ´me Duval. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "ExpanderRules.h"

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

#include <FindDirectory.h>
#include <NodeInfo.h>
#include <Path.h>

#include <compat/sys/stat.h>

#include "ExpanderSettings.h"


static const char* const kRulesDirectoryPath = "expander/rules";
static const char* const kUserRulesFileName = "rules";


// #pragma mark - ExpanderRule


ExpanderRule::ExpanderRule(const char* mimeType,
	const BString& filenameExtension, const BString& listingCommand,
	const BString& expandCommand)
	:
	fMimeType(mimeType),
	fFilenameExtension(filenameExtension),
	fListingCmd(listingCommand),
	fExpandCmd(expandCommand)
{
}


// #pragma mark - ExpanderRules


ExpanderRules::ExpanderRules()
{
	// Load the rules files first, then add the built-in rules. This way the
	// built-ins can be overridden, if the files contain matching rules.
	_LoadRulesFiles();

	_AddRule("", ".tar.gz", "tar -ztvf %s", "tar -zxf %s");
	_AddRule("", ".tar.bz2", "tar -jtvf %s", "tar -jxf %s");
	_AddRule("", ".tar.Z", "tar -Ztvf %s", "tar -Zxf %s");
	_AddRule("", ".tgz", "tar -ztvf %s", "tar -zxf %s");
	_AddRule("application/x-tar", ".tar", "tar -tvf %s", "tar -xf %s");
	_AddRule("application/x-gzip", ".gz", "echo %s | sed 's/.gz$//g'",
		"gunzip -c %s > `echo %s | sed 's/.gz$//g'`");
	_AddRule("application/x-bzip2", ".bz2", "echo %s | sed 's/.bz2$//g'",
		"bunzip2 -k %s");
	_AddRule("application/zip", ".zip", "unzip -l %s", "unzip -o %s");
	_AddRule("application/x-zip-compressed", ".zip", "unzip -l %s",
		"unzip -o %s");
	_AddRule("application/x-rar", ".rar", "unrar v %s", "unrar x -y %s");
	_AddRule("application/x-vnd.haiku-package", ".hpkg", "package list %s",
		"package extract %s");
}


ExpanderRules::~ExpanderRules()
{
	void* item;
	while ((item = fList.RemoveItem((int32)0)))
		delete (ExpanderRule*)item;
}


ExpanderRule*
ExpanderRules::MatchingRule(BString& fileName, const char* filetype)
{
	int32 count = fList.CountItems();
	int32 length = fileName.Length();
	for (int32 i = 0; i < count; i++) {
		ExpanderRule* rule = (ExpanderRule*)fList.ItemAt(i);
		if (rule->MimeType().IsValid() && rule->MimeType() == filetype)
			return rule;

		int32 extensionPosition = fileName.FindLast(rule->FilenameExtension());
		if (extensionPosition != -1 && extensionPosition
				== (length - rule->FilenameExtension().Length())) {
			return rule;
		}
	}

	return NULL;
}


ExpanderRule*
ExpanderRules::MatchingRule(const entry_ref* ref)
{
	BEntry entry(ref, true);
	BNode node(&entry);
	BNodeInfo nodeInfo(&node);
	char type[B_MIME_TYPE_LENGTH];
	nodeInfo.GetType(type);
	BString fileName(ref->name);

	return MatchingRule(fileName, type);
}


void
ExpanderRules::_LoadRulesFiles()
{
	// load the user editable rules first
	BPath path;
	if (ExpanderSettings::GetSettingsDirectoryPath(path) == B_OK
		&& path.Append(kUserRulesFileName) == B_OK) {
		_LoadRulesFile(path.Path());
	}

	// load the rules files from the data directories
	const directory_which kDirectories[] = {
		B_USER_NONPACKAGED_DATA_DIRECTORY,
		B_USER_DATA_DIRECTORY,
		B_SYSTEM_NONPACKAGED_DATA_DIRECTORY,
		B_SYSTEM_DATA_DIRECTORY
	};

	for (size_t i = 0; i < sizeof(kDirectories) / sizeof(kDirectories[0]);
			i++) {
		BDirectory directory;
		if (find_directory(kDirectories[i], &path) != B_OK
			|| path.Append(kRulesDirectoryPath) != B_OK
			|| directory.SetTo(path.Path()) != B_OK) {
			continue;
		}

		entry_ref entry;
		while (directory.GetNextRef(&entry) == B_OK) {
			BPath filePath;
			if (filePath.SetTo(path.Path(), entry.name) == B_OK)
				_LoadRulesFile(filePath.Path());
		}
	}
}


void
ExpanderRules::_LoadRulesFile(const char* path)
{
	FILE* file = fopen(path, "r");
	if (file == NULL)
		return;

	char buffer[1024];
	BString strings[4];
	while (fgets(buffer, 1024 - 1, file) != NULL) {
		int32 i = 0, j = 0;
		int32 firstQuote = -1;
		while (buffer[i] != '#' && buffer[i] != '\n' && j < 4) {
			if ((j == 0 || j > 1) && buffer[i] == '"') {
				if (firstQuote >= 0) {
					strings[j++].SetTo(&buffer[firstQuote+1],
						i - firstQuote - 1);
					firstQuote = -1;
				} else
					firstQuote = i;
			} else if (j == 1 && (buffer[i] == ' ' || buffer[i] == '\t')) {
				if (firstQuote >= 0) {
					if (firstQuote + 1 != i) {
						strings[j++].SetTo(&buffer[firstQuote+1],
							i - firstQuote - 1);
						firstQuote = -1;
					} else
						firstQuote = i;
				} else
					firstQuote = i;
			}
			i++;
		}

		if (j == 4)
			_AddRule(strings[0], strings[1], strings[2], strings[3]);
	}

	fclose(file);
}


bool
ExpanderRules::_AddRule(const char* mimeType, const BString& filenameExtension,
	const BString& listingCommand, const BString& expandCommand)
{
	ExpanderRule* rule = new(std::nothrow) ExpanderRule(mimeType,
		filenameExtension, listingCommand, expandCommand);
	if (rule == NULL)
		return false;

	if (!fList.AddItem(rule)) {
		delete rule;
		return false;
	}

	return true;
}


// #pragma mark - RuleRefFilter


RuleRefFilter::RuleRefFilter(ExpanderRules& rules)
	:
	BRefFilter(),
	fRules(rules)
{
}


bool
RuleRefFilter::Filter(const entry_ref* ref, BNode* node, struct stat_beos* stat,
	const char* filetype)
{
	if (node->IsDirectory() || node->IsSymLink())
		return true;

	BString fileName(ref->name);
	return fRules.MatchingRule(fileName, filetype) != NULL;
}