/*********
*
* This file is part of BibleTime's source code, http://www.bibletime.info/.
*
* Copyright 1999-2006 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License version 2.0.
*
**********/



#include "creferencemanager.h"
#include "cswordversekey.h"

#include "../frontend/cbtconfig.h"

//TQt includes
#include <tqregexp.h>

//stl includes
#include <algorithm>       // STL algorithms class library

/** Returns a hyperlink used to be imbedded in the display windows. At the moment the format is sword://module/key */
const TQString CReferenceManager::encodeHyperlink( const TQString moduleName, const TQString key, const CReferenceManager::Type type) {
	TQString ret = TQString();

	switch (type) {

		case Bible:
		ret.setLatin1("sword://Bible/");
		break;

		case Commentary:
		ret.setLatin1("sword://Commentary/");
		break;

		case Lexicon:
		ret.setLatin1("sword://Lexicon/");
		break;

		case GenericBook:
		ret.setLatin1("sword://Book/");
		break;

		case MorphHebrew:
		ret.setLatin1("morph://Hebrew/");
		break;

		case MorphGreek:
		ret.setLatin1("morph://Greek/");
		break;

		case StrongsHebrew:
		ret.setLatin1("strongs://Hebrew/");
		break;

		case StrongsGreek:
		ret.setLatin1("strongs://Greek/");
		break;

		default:
		break;
	}

	if (!moduleName.isEmpty()) {
		ret.append( moduleName ).append('/');
	}
	else { //if module is empty use fallback module
		ret.append( preferredModule(type) ).append('/');
	}

	if (type == GenericBook) {
		const TQString s = (!key.isEmpty() ? key : TQString());
		TQString newKey = TQString();
		//replace all / of the key (e.g. of a CSwordTreeKey) with
		// the escape sequence \/ so we know it's a link internal divider (e.g. of CSwordTreeKey)!

		TQChar c;

		for(unsigned int i = 0; i < s.length(); ++i) {
			c = s.at(i);

			if (c == '/') {
				newKey.append("\\/");
			}
			else {
				newKey.append(c);
			}
		}

		ret.append( newKey );
	}
	else { //slashes do not appear in verses and dictionary entries

		switch (type) {

			case Bible: //bibles or commentary keys need parsing

			case Commentary: {
/*  				CSwordModuleInfo* mod = CPointers::backend()->findModuleByName(moduleName);

  				ParseOptions options;
  				options.refDestinationModule = mod->name();
  				options.refBase =
  				options.sourceLanguage = mod->module()->Lang();
  				options.destinationLanguage = "en";

				ret.append( parseVerseReference(key, options) ); //we add the english key, so drag and drop will work in all cases*/
				ret.append(key);
				break;
			}

			default:
				ret.append( key ); //use the standard key, no parsing required
				break;
		}
	}

	return ret;
}

/** Decodes the given hyperlink to module and key. */
const bool CReferenceManager::decodeHyperlink( const TQString& hyperlink, TQString& module, TQString& key, CReferenceManager::Type& type ) {
	/**
	* We have to decide between three types of URLS: sword://Type/Module/Key, morph://Testament/key and strongs://Testament/Key
	*/
	module = TQString();
	key = TQString();

	type = Unknown; //not yet known
	TQString ref = hyperlink;
	//remove the trailing slash

	if (ref.right(1)=="/" && ref.right(2) != "\\/") //trailing slash, but not escaped
		ref = ref.left(ref.length()-1);

	//find out which type we have by looking at the beginning (protocoll section of URL)
	if (ref.left(8).lower() == "sword://") { //Bible, Commentary or Lexicon
		ref = ref.mid(8);

		if (ref.left(5).lower() == "bible") { //a bible hyperlink
			type = CReferenceManager::Bible;
			ref = ref.mid(6); //inclusive trailing slash
		}
		else if (ref.left(10).lower() == "commentary") { // a Commentary hyperlink
			type = CReferenceManager::Commentary;
			ref = ref.mid(11); //inclusive trailing slash
		}
		else if (ref.left(7).lower() == "lexicon") { // a Lexicon hyperlink
			type = CReferenceManager::Lexicon;
			ref = ref.mid(8); //inclusive trailing slash
		}
		else if (ref.left(4).lower() == "book") { // a Book hyperlink
			type = CReferenceManager::GenericBook;
			ref = ref.mid(5); //inclusive trailing slash
		}

		// string up to next slash is the modulename
		if (ref.at(0) != '/' ) { //we have a module given

			while (true) {
				const int pos = ref.find("/");

				if ((pos>0) && ref.at(pos-1) != '\\') { //found a slash which is not escaped
					module = ref.mid(0,pos);
					ref = ref.mid(pos+1);
					break;
				}
				else if (pos == -1) {
					break;
				}
			}

			// the rest is the key
			key = ref;
		}
		else {
			key = ref.mid(1);
		}

		//the key may be an osis key like "NASBLex:Moses", which sets the module, too
		//   const int modPos = key.find(":");
		//   if (modPos != -1 && key.at(modPos-1).isLetter() && key.at(modPos+1).isLetter()) {
		//    module = key.left(modPos);
		//    key = key.mid(modPos+1);
		//
		//    tqWarning("found the module name %s with key %s", module.latin1(), key.latin1());
		//   }

		//replace \/ escapes with /
		key.replace(TQRegExp("\\\\/"), "/");
	}
	else if (ref.left(8).lower() == "morph://" || ref.left(10).lower() == "strongs://") { //strongs or morph URL have the same format
		enum PreType {IsMorph, IsStrongs};
		PreType preType = IsMorph;

		if (ref.left(8).lower() == "morph://") { //morph code hyperlink
			ref = ref.mid(8);
			preType = IsMorph;
		}
		else if (ref.left(10).lower() == "strongs://") {
			ref = ref.mid(10);
			preType = IsStrongs;
		}

		//part up to next slash is the language
		const int pos = ref.find("/");

		if (pos>0) { //found
			const TQString language = ref.mid(0,pos);

			if (language.lower() == "hebrew") {
				switch (preType) {

					case IsMorph:
					type = CReferenceManager::MorphHebrew;
					break;

					case IsStrongs:
					type = CReferenceManager::StrongsHebrew;
					break;
				}
			}
			else if (language.lower() == "greek") {
				switch (preType) {

					case IsMorph:
					type = CReferenceManager::MorphGreek;
					break;

					case IsStrongs:
					type = CReferenceManager::StrongsGreek;
					break;
				}
			}

			ref = ref.mid(pos+1);
			key = ref; //the remaining part is the key

			module = preferredModule(type);
		}
	}

	if (key.isEmpty() && module.isEmpty())
		return false;

	return true;
}

const TQString CReferenceManager::encodeReference(const TQString &module, const TQString &reference) {
	//return TQString("(%1)%2").arg(module).arg(reference);
	return TQString("(").append(module).append(")").append(reference);
}

void CReferenceManager::decodeReference(TQString &dragreference, TQString &module, TQString &reference) {
	const int pos = dragreference.find(")");
	const TQString fallbackModule = dragreference.mid( 1, pos - 1);
	dragreference = dragreference.mid(pos+1);

	module = fallbackModule;
	reference = dragreference;
}

/** Returns true if the parameter is a hyperlink. */
const bool CReferenceManager::isHyperlink( const TQString& hyperlink ) {
	return (    hyperlink.left(8)  == "sword://")
		   || (hyperlink.left(10) == "strongs://")
		   || (hyperlink.left(8)  == "morph://");
}

/** Returns the preferred module name for the given type. */
const TQString CReferenceManager::preferredModule( const CReferenceManager::Type type ) {
	TQString moduleName = TQString();
	CSwordModuleInfo* module = 0;

	switch (type) {

		case CReferenceManager::Bible:

		module = CBTConfig::get
					 ( CBTConfig::standardBible );

		break;

		case CReferenceManager::Commentary:
		module = CBTConfig::get
					 ( CBTConfig::standardCommentary );

		break;

		case CReferenceManager::Lexicon:
		module = CBTConfig::get
					 ( CBTConfig::standardLexicon );

		break;

		case CReferenceManager::StrongsHebrew:
		module = CBTConfig::get
					 ( CBTConfig::standardHebrewStrongsLexicon );

		break;

		case CReferenceManager::StrongsGreek:
		module = CBTConfig::get
					 ( CBTConfig::standardGreekStrongsLexicon );

		break;

		case CReferenceManager::MorphHebrew:
		module = CBTConfig::get
					 ( CBTConfig::standardHebrewMorphLexicon );

		break;

		case CReferenceManager::MorphGreek:
		module = CBTConfig::get
					 ( CBTConfig::standardGreekMorphLexicon );

		break;

		default:
		module = 0;

		break;
	}

	return module ? module->name() : TQString();
}

/** No descriptions */
CReferenceManager::Type CReferenceManager::typeFromModule( const CSwordModuleInfo::ModuleType type) {
	switch (type) {

		case CSwordModuleInfo::Bible:
		return CReferenceManager::Bible;

		case CSwordModuleInfo::Commentary:
		return CReferenceManager::Commentary;

		case CSwordModuleInfo::Lexicon:
		return CReferenceManager::Lexicon;

		case CSwordModuleInfo::GenericBook:
		return CReferenceManager::GenericBook;

		default:
		return CReferenceManager::Unknown;
	}
}

/** Parses the given verse references using the given language and the module.*/
const TQString CReferenceManager::parseVerseReference( const TQString& ref, const CReferenceManager::ParseOptions& options) {

	CSwordModuleInfo* const mod = CPointers::backend()->findModuleByName(options.refDestinationModule);
	Q_ASSERT(mod);

	if (!mod) {
		//parsing of non-verse based references is not supported
		return ref;
	}

	if ((mod->type() != CSwordModuleInfo::Bible) && (mod->type() != CSwordModuleInfo::Commentary)) {
		tqDebug("CReferenceManager: Only verse based modules are supported as ref destination module");
		return TQString();
	}

	TQString sourceLanguage = options.sourceLanguage;
	TQString destinationLanguage = options.destinationLanguage;

 	StringList locales = sword::LocaleMgr::getSystemLocaleMgr()->getAvailableLocales();
 	if (/*options.sourceLanguage == "en" ||*/ std::find(locales.begin(), locales.end(), sourceLanguage.ascii()) == locales.end()) { //sourceLanguage not available
		sourceLanguage = "en_US";
 	}

 	if (/*options.destinationLanguage == "en" ||*/ std::find(locales.begin(), locales.end(), sourceLanguage.ascii()) == locales.end()) { //destination not available
		destinationLanguage = "en_US";
 	}


	TQString ret;
	TQStringList refList = TQStringList::split(";", ref);

	CSwordVerseKey baseKey(0);
	baseKey.setLocale( sourceLanguage.latin1() );
	baseKey.key( options.refBase ); //probably in the sourceLanguage
 	baseKey.setLocale( "en_US" ); //english works in all environments as base

// 	CSwordVerseKey dummy(0);
	//HACK: We have to workaround a Sword bug, we have to set the default locale to the same as the sourceLanguage !
	const TQString oldLocaleName = CPointers::backend()->booknameLanguage();
	CPointers::backend()->booknameLanguage(sourceLanguage);

	VerseKey dummy;
 	dummy.setLocale( sourceLanguage.latin1() );
 	Q_ASSERT( !strcmp(dummy.getLocale(), sourceLanguage.latin1()) );

// 	tqDebug("Parsing '%s' in '%s' using '%s' as base, source lang '%s', dest lang '%s'", ref.latin1(), options.refDestinationModule.latin1(), baseKey.key().latin1(), sourceLanguage.latin1(), destinationLanguage.latin1());

	for (TQStringList::iterator it = refList.begin(); it != refList.end(); it++) {
		//The listkey may contain more than one item, because a ref lik "Gen 1:3,5" is parsed into two single refs
		ListKey lk = dummy.ParseVerseList((const char*)(*it).utf8(), (const char*)baseKey.key().utf8(), true);
		Q_ASSERT(!dummy.Error());

		Q_ASSERT(lk.Count());
		if (!lk.Count()) {
			ret.append( *it ); //don't change the original
			continue;
		}

		for (int i = 0; i < lk.Count(); ++i) {
			if (dynamic_cast<VerseKey*>(lk.getElement(i))) { // a range
				VerseKey* k = dynamic_cast<VerseKey*>(lk.getElement(i));
				Q_ASSERT(k);
 				k->setLocale( destinationLanguage.latin1() );

				ret.append( TQString::fromUtf8(k->getRangeText()) ).append("; ");
			}
			else { // a single ref
				VerseKey vk;
 				vk.setLocale( sourceLanguage.latin1() );
				vk = lk.getElement(i)->getText();
				vk.setLocale( destinationLanguage.latin1() );

				ret.append( TQString::fromUtf8(vk.getText()) ).append("; ");
			}
		}

	}

	CPointers::backend()->booknameLanguage(oldLocaleName);
// 	tqDebug("    %s", ret.latin1());

	return ret;
}
