/*********
*
* 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.
*
**********/



//BibleTime includes
#include "cswordbackend.h"
#include "centrydisplay.h"
#include "cbookdisplay.h"
#include "cchapterdisplay.h"
#include "cswordbiblemoduleinfo.h"
#include "cswordcommentarymoduleinfo.h"
#include "cswordlexiconmoduleinfo.h"
#include "cswordbookmoduleinfo.h"

#include "bt_thmlhtml.h"
#include "bt_thmlplain.h"
#include "bt_osishtml.h"
#include "bt_gbfhtml.h"
#include "bt_plainhtml.h"
#include "osismorphsegmentation.h"

#include "frontend/cbtconfig.h"

#include <dirent.h>
#include <unistd.h>
#include <ctype.h>

//TQt includes
#include <tqdir.h>
#include <tqfileinfo.h>

//KDE includes
#include <tdelocale.h>
#include <kstringhandler.h>

//Sword includes
#include <swdisp.h>
#include <swfiltermgr.h>
#include <encfiltmgr.h>
#include <rtfhtml.h>
#include <filemgr.h>
#include <utilstr.h>
#include <swfilter.h>

using std::string;

using namespace Filters;

using namespace Rendering;

CSwordBackend::CSwordBackend()
: sword::SWMgr(0, 0, false, new sword::EncodingFilterMgr( sword::ENC_UTF8 ), true) {
	m_displays.entry = 0;
	m_displays.chapter = 0;
	m_displays.book = 0;

	m_filters.gbf = 0;
	m_filters.thml = 0;
	m_filters.osis = 0;
	m_filters.plain = 0;

	filterInit();
}

CSwordBackend::CSwordBackend(const TQString& path, const bool augmentHome)
: sword::SWMgr(!path.isEmpty() ? (const char*)path.local8Bit() : 0, false, new sword::EncodingFilterMgr( sword::ENC_UTF8 ), false, augmentHome) // don't allow module renaming, because we load from a path
{
	tqDebug("CSwordBackend::CSwordBackend for %s, using %s", path.latin1(), configPath);
	m_displays.entry = 0;
	m_displays.chapter = 0;
	m_displays.book = 0;

	m_filters.gbf = 0;
	m_filters.thml = 0;
	m_filters.osis = 0;
	m_filters.plain = 0;

	filterInit();
}

CSwordBackend::~CSwordBackend() {
	shutdownModules();

	delete m_filters.gbf;
	delete m_filters.plain;
	delete m_filters.thml;
	delete m_filters.osis;

	delete m_displays.book;
	delete m_displays.chapter;
	delete m_displays.entry;
}

/** Initializes the Sword modules. */
const CSwordBackend::LoadError CSwordBackend::initModules() {
	//  tqWarning("globalSwordConfigPath is %s", globalConfPath);
	LoadError ret = NoError;

	shutdownModules(); //remove previous modules
	m_moduleList.clear();

	sword::ModMap::iterator end = Modules.end();
	ret = LoadError( Load() );

	for (sword::ModMap::iterator it = Modules.begin(); it != end; it++) {
		sword::SWModule* const curMod = (*it).second;
		CSwordModuleInfo* newModule = 0;

		if (!strcmp(curMod->Type(), "Biblical Texts")) {
			newModule = new CSwordBibleModuleInfo(curMod, this);
			newModule->module()->Disp(
				m_displays.chapter
				? m_displays.chapter
				: (m_displays.chapter = new CChapterDisplay)
			);
		}
		else if (!strcmp(curMod->Type(), "Commentaries")) {
			newModule = new CSwordCommentaryModuleInfo(curMod, this);
			newModule->module()->Disp(
				m_displays.entry
				? m_displays.entry
				: (m_displays.entry = new CEntryDisplay)
			);
		}
		else if (!strcmp(curMod->Type(), "Lexicons / Dictionaries")) {
			newModule = new CSwordLexiconModuleInfo(curMod, this);
			newModule->module()->Disp(
				m_displays.entry
				? m_displays.entry
				: (m_displays.entry = new CEntryDisplay)
			);
		}
		else if (!strcmp(curMod->Type(), "Generic Books")) {
			newModule = new CSwordBookModuleInfo(curMod, this);
			newModule->module()->Disp(
				m_displays.book
				? m_displays.book
				: (m_displays.book = new CBookDisplay)
			);
		}

		if (newModule) { 
			//append the new modules to our list, but only if it's supported
			//the constructor of CSwordModuleInfo prints a waring on stdout
			if (!newModule->hasVersion() || (newModule->minimumSwordVersion() <= sword::SWVersion::currentVersion)) {
				m_moduleList.append( newModule );	
			}
		}
	}

	ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		// for (m_moduleList.first(); m_moduleList.current(); m_moduleList.next()) {
		m_moduleDescriptionMap.insert( (*it)->config(CSwordModuleInfo::Description), (*it)->name() );
	}

	//unlock modules if keys are present
	//  ListCSwordModuleInfo::iterator end_it = m_moduleList.end();
	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		//  for (m_moduleList.first(); m_moduleList.current(); m_moduleList.next()) {

		if ( (*it)->isEncrypted() ) {
			const TQString unlockKey = CBTConfig::getModuleEncryptionKey( (*it)->name() ).latin1();

			if (!unlockKey.isNull()) {
				setCipherKey( (*it)->name().latin1(), unlockKey.latin1() );
			}
		}
	}

	return ret;
}

void CSwordBackend::AddRenderFilters(sword::SWModule *module, sword::ConfigEntMap &section) {
	sword::SWBuf moduleDriver;
	sword::SWBuf sourceformat;
	sword::ConfigEntMap::iterator entry;
	bool noDriver = true;

	sourceformat = ((entry = section.find("SourceType")) != section.end()) ? (*entry).second : (sword::SWBuf) "";
	moduleDriver = ((entry = section.find("ModDrv")) != section.end()) ? (*entry).second : (sword::SWBuf) "";

	if (sourceformat == "GBF") {
		if (!m_filters.gbf) {
			m_filters.gbf = new BT_GBFHTML();
		}
		module->AddRenderFilter(m_filters.gbf);
		noDriver = false;
	}
	else if (sourceformat == "PLAIN") {
		if (!m_filters.plain) {
			m_filters.plain = new BT_PLAINHTML();
		}
		module->AddRenderFilter(m_filters.plain);
		noDriver = false;
	}
	else if (sourceformat == "ThML") {
		if (!m_filters.thml) {
			m_filters.thml = new BT_ThMLHTML();
		}
		module->AddRenderFilter(m_filters.thml);
		noDriver = false;
	}
	else if (sourceformat == "OSIS") {
		if (!m_filters.osis) {
			m_filters.osis = new BT_OSISHTML();
		}

		module->AddRenderFilter(m_filters.osis);
		noDriver = false;
	}

	if (noDriver) { //no driver found
		if ( (moduleDriver == "RawCom") || (moduleDriver == "RawLD") ) {
			if (!m_filters.plain) {
				m_filters.plain = new BT_PLAINHTML();
			}
			module->AddRenderFilter(m_filters.plain);
			noDriver = false;
		}
	}
}

/** This function deinitializes the modules and deletes them. */
const bool CSwordBackend::shutdownModules() {
	ListCSwordModuleInfo::iterator it = m_moduleList.begin();
	ListCSwordModuleInfo::iterator end = m_moduleList.end();

	while (it != end) {
		CSwordModuleInfo* current = (*it);
		it = m_moduleList.remove(it);

		delete current;
	}

	Q_ASSERT(m_moduleList.count() == 0);

	//BT  mods are deleted now, delete Sword mods, too.
	DeleteMods();

	return true;
}

/** Returns true if the given option is enabled. */
const bool CSwordBackend::isOptionEnabled( const CSwordModuleInfo::FilterTypes type) {
	return (strcmp(getGlobalOption( optionName(type).latin1() ), "On") == 0);
}

/** Sets the given options enabled or disabled depending on the second parameter. */
void CSwordBackend::setOption( const CSwordModuleInfo::FilterTypes type, const int state ) {
	sword::SWBuf value;

	switch (type) {

		case CSwordModuleInfo::textualVariants:

			if (state == 0) {
				value = "Primary Reading";
			}
			else if (state == 1) {
				value = "Secondary Reading";
			}
			else {
				value = "All Readings";
			}
	
			break;

		default:
			value = state ? "On": "Off";
			break;
	};

	if (value.length())
		setGlobalOption(optionName(type).latin1(), value.c_str());
}

void CSwordBackend::setFilterOptions( const CSwordBackend::FilterOptions options) {
	setOption( CSwordModuleInfo::footnotes,      options.footnotes );
	setOption( CSwordModuleInfo::strongNumbers,    options.strongNumbers );
	setOption( CSwordModuleInfo::headings,       options.headings );
	setOption( CSwordModuleInfo::morphTags,      options.morphTags );
	setOption( CSwordModuleInfo::lemmas,        options.lemmas );
	setOption( CSwordModuleInfo::hebrewPoints,     options.hebrewPoints );
	setOption( CSwordModuleInfo::hebrewCantillation,  options.hebrewCantillation );
	setOption( CSwordModuleInfo::greekAccents,     options.greekAccents );
	setOption( CSwordModuleInfo::redLetterWords,   options.redLetterWords );
	setOption( CSwordModuleInfo::textualVariants,   options.textualVariants );
	setOption( CSwordModuleInfo::morphSegmentation,  options.morphSegmentation );
	//  setOption( CSwordModuleInfo::transliteration,   options.transliteration );
	setOption( CSwordModuleInfo::scriptureReferences, options.scriptureReferences);
}

void CSwordBackend::setDisplayOptions( const CSwordBackend::DisplayOptions ) {
	/*  if (m_displays.entry) {
	  m_displays.entry->setDisplayOptions(options); 
	 }
	  if (m_displays.chapter) {
	  m_displays.chapter->setDisplayOptions(options); 
	 }
	  if (m_displays.book) {
	  m_displays.book->setDisplayOptions(options);
	 }
	 */
}

/** This function searches for a module with the specified description */
CSwordModuleInfo* const CSwordBackend::findModuleByDescription(const TQString& description) {
	CSwordModuleInfo* ret = 0;
	ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		if ( (*it)->config(CSwordModuleInfo::Description) == description ) {
			ret = *it;
			break;
		}
	}

	return ret;
}

/** This function searches for a module with the specified description */
const TQString CSwordBackend::findModuleNameByDescription(const TQString& description) {
	if (m_moduleDescriptionMap.contains(description)) {
		return m_moduleDescriptionMap[description];
	}

	return TQString();
}

/** This function searches for a module with the specified name */
CSwordModuleInfo* const CSwordBackend::findModuleByName(const TQString& name) {
	CSwordModuleInfo* ret = 0;

	ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		if ( (*it)->name() == name ) {
			ret = *it;
			break;
		}
	}

	return ret;
}

CSwordModuleInfo* const CSwordBackend::findSwordModuleByPointer(const sword::SWModule* const swmodule) {
	CSwordModuleInfo* ret = 0;
	ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		if ( (*it)->module() == swmodule ) {
			ret = *it;
			break;
		}
	}
	
	return ret;
}

CSwordModuleInfo* const CSwordBackend::findModuleByPointer(const CSwordModuleInfo* const module) {
	CSwordModuleInfo* ret = 0;

	ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

	for (ListCSwordModuleInfo::iterator it = m_moduleList.begin() ; it != end_it; ++it) {
		if ( (*it)  == module ) {
			ret = *it;
			break;
		}
	}
	
	return ret;
}

/** Returns our local config object to store the cipher keys etc. locally for each user. The values of the config are merged with the global config. */
const bool CSwordBackend::moduleConfig(const TQString& module, sword::SWConfig& moduleConfig) {
	sword::SectionMap::iterator section;
	DIR *dir = opendir(configPath);

	struct dirent *ent;

	bool foundConfig = false;
	TQString modFile;

	if (dir) {    // find and update .conf file
		rewinddir(dir);

		while ((ent = readdir(dir)) && !foundConfig) {
			if ((strcmp(ent->d_name, ".")) && (strcmp(ent->d_name, ".."))) {
				modFile.setLatin1(configPath);
				modFile.append("/");
				modFile.append( TQString::fromLocal8Bit(ent->d_name) );

				moduleConfig = sword::SWConfig( (const char*)modFile.local8Bit() );
				section = moduleConfig.Sections.find( (const char*)module.local8Bit() );
				foundConfig = ( section != moduleConfig.Sections.end() );
			}
		}

		closedir(dir);
	}
	else { //try to read mods.conf
		moduleConfig = sword::SWConfig("");//global config
		section = config->Sections.find( (const char*)module.local8Bit() );
		foundConfig = ( section != config->Sections.end() );

		sword::ConfigEntMap::iterator entry;

		if (foundConfig) { //copy module section

			for (entry = (*section).second.begin(); entry != (*section).second.end(); entry++) {
				moduleConfig.Sections[(*section).first].insert(sword::ConfigEntMap::value_type((*entry).first, (*entry).second));
			}
		}
	}

	if (!foundConfig && configType != 2) { //search in $HOME/.sword/
		TQString myPath(getenv("HOME"));
		myPath.append("/.sword/mods.d");
		dir = opendir(myPath.latin1());

		if (dir) {
			rewinddir(dir);

			while ((ent = readdir(dir)) && !foundConfig) {
				if ((strcmp(ent->d_name, ".")) && (strcmp(ent->d_name, ".."))) {
					modFile = myPath;
					modFile.append('/');
					modFile.append(ent->d_name);
					moduleConfig = sword::SWConfig( (const char*)modFile.local8Bit() );
					section = moduleConfig.Sections.find( (const char*)module.local8Bit() );
					foundConfig = ( section != moduleConfig.Sections.end() );
				}
			}

			closedir(dir);
		}
	}

	return foundConfig;
}

/** Returns the text used for the option given as parameter. */
const TQString CSwordBackend::optionName( const CSwordModuleInfo::FilterTypes option ) {
	switch (option) {

		case CSwordModuleInfo::footnotes:
		return TQString("Footnotes");

		case CSwordModuleInfo::strongNumbers:
		return TQString("Strong's Numbers");

		case CSwordModuleInfo::headings:
		return TQString("Headings");

		case CSwordModuleInfo::morphTags:
		return TQString("Morphological Tags");

		case CSwordModuleInfo::lemmas:
		return TQString("Lemmas");

		case CSwordModuleInfo::hebrewPoints:
		return TQString("Hebrew Vowel Points");

		case CSwordModuleInfo::hebrewCantillation:
		return TQString("Hebrew Cantillation");

		case CSwordModuleInfo::greekAccents:
		return TQString("Greek Accents");

		case CSwordModuleInfo::redLetterWords:
		return TQString("Words of Christ in Red");

		case CSwordModuleInfo::textualVariants:
		return TQString("Textual Variants");

		case CSwordModuleInfo::scriptureReferences:
		return TQString("Cross-references");

		case CSwordModuleInfo::morphSegmentation:
		return TQString("Morph Segmentation");
		//   case CSwordModuleInfo::transliteration:
		//    return TQString("Transliteration");
	}

	return TQString();
}

/** Returns the translated name of the option given as parameter. */
const TQString CSwordBackend::translatedOptionName(const CSwordModuleInfo::FilterTypes option) {
	switch (option) {

		case CSwordModuleInfo::footnotes:
		return i18n("Footnotes");

		case CSwordModuleInfo::strongNumbers:
		return i18n("Strong's numbers");

		case CSwordModuleInfo::headings:
		return i18n("Headings");

		case CSwordModuleInfo::morphTags:
		return i18n("Morphological tags");

		case CSwordModuleInfo::lemmas:
		return i18n("Lemmas");

		case CSwordModuleInfo::hebrewPoints:
		return i18n("Hebrew vowel points");

		case CSwordModuleInfo::hebrewCantillation:
		return i18n("Hebrew cantillation marks");

		case CSwordModuleInfo::greekAccents:
		return i18n("Greek accents");

		case CSwordModuleInfo::redLetterWords:
		return i18n("Red letter words");

		case CSwordModuleInfo::textualVariants:
		return i18n("Textual variants");

		case CSwordModuleInfo::scriptureReferences:
		return i18n("Scripture cross-references");

		case CSwordModuleInfo::morphSegmentation:
		return i18n("Morph segmentation");
		//   case CSwordModuleInfo::transliteration:
		//    return i18n("Transliteration between scripts");
	}

	return TQString();
}


const TQString CSwordBackend::configOptionName( const CSwordModuleInfo::FilterTypes option ) {
	switch (option) {

		case CSwordModuleInfo::footnotes:
		return TQString("Footnotes");

		case CSwordModuleInfo::strongNumbers:
		return TQString("Strongs");

		case CSwordModuleInfo::headings:
		return TQString("Headings");

		case CSwordModuleInfo::morphTags:
		return TQString("Morph");

		case CSwordModuleInfo::lemmas:
		return TQString("Lemma");

		case CSwordModuleInfo::hebrewPoints:
		return TQString("HebrewPoints");

		case CSwordModuleInfo::hebrewCantillation:
		return TQString("Cantillation");

		case CSwordModuleInfo::greekAccents:
		return TQString("GreekAccents");

		case CSwordModuleInfo::redLetterWords:
		return TQString("RedLetterWords");

		case CSwordModuleInfo::textualVariants:
		return TQString("Variants");

		case CSwordModuleInfo::scriptureReferences:
		return TQString("Scripref");

		case CSwordModuleInfo::morphSegmentation:
		return TQString("MorphSegmentation");

		default:
		return TQString();
	}

	return TQString();
}

const TQString CSwordBackend::booknameLanguage( const TQString& language ) {
	if (!language.isEmpty()) {
		sword::LocaleMgr::getSystemLocaleMgr()->setDefaultLocaleName( language.latin1() );

		//refresh the locale of all Bible and commentary modules!
		const ListCSwordModuleInfo::iterator end_it = m_moduleList.end();

		//use what sword returns, language may be different
		TQString newLocaleName( sword::LocaleMgr::getSystemLocaleMgr()->getDefaultLocaleName()  );

		for (ListCSwordModuleInfo::iterator it = m_moduleList.begin(); it != end_it; ++it) {
			if ( ((*it)->type() == CSwordModuleInfo::Bible) || ((*it)->type() == CSwordModuleInfo::Commentary) ) {
				//Create a new key, it will get the default bookname language
				((sword::VerseKey*)((*it)->module()->getKey()))->setLocale( newLocaleName.latin1() );
			}
		}

	}

	return TQString( sword::LocaleMgr::getSystemLocaleMgr()->getDefaultLocaleName() );
}


/** Reload all Sword modules. */
void CSwordBackend::reloadModules() {
	shutdownModules();

	//delete Sword's config to make Sword reload it!

	if (myconfig) { // force reload on config object because we may have changed the paths
		delete myconfig;
		config = myconfig = 0;
		loadConfigDir(configPath);
	}
	else if (config) {
		config->Load();
	}

	initModules();
}

const TQStringList CSwordBackend::swordDirList() {
	TQStringList ret;
	const TQString home = TQString(getenv("HOME"));

	//return a list of used Sword dirs. Useful for the installer
	TQString configPath = TQString("%1/.sword/sword.conf").arg(home);

	if (!TQFile(configPath).exists()) {
		configPath = globalConfPath; //e.g. /etc/sword.conf, /usr/local/etc/sword.conf


	}


	TQStringList configs = TQStringList::split(":", configPath);

	/*ToDo: Use the const iterator as soon as we switch to TQt > 3.1
	  for (TQStringList::const_iterator it = configs.constBegin(); it != configs.constEnd(); ++it) {*/

	for (TQStringList::const_iterator it = configs.begin(); it != configs.end(); ++it) {
		if (!TQFileInfo(*it).exists()) {
			continue;
		}

		//get all DataPath and AugmentPath entries from the config file and add them to the list
		sword::SWConfig conf( (*it).latin1() );

		ret << conf["Install"]["DataPath"].c_str();

		sword::ConfigEntMap group = conf["Install"];

		sword::ConfigEntMap::iterator start = group.equal_range("AugmentPath").first;

		sword::ConfigEntMap::iterator end = group.equal_range("AugmentPath").second;

		for (sword::ConfigEntMap::const_iterator it = start; it != end; ++it) {
			ret << it->second.c_str(); //added augment path
		}
	}

	if (!home.isEmpty()) {
		ret << home + "/.sword/";
	}

	return ret;
}

void CSwordBackend::filterInit() {
	//  tqWarning("## INIT");

	SWOptionFilter* tmpFilter = new OSISMorphSegmentation();
	optionFilters.insert(OptionFilterMap::value_type("OSISMorphSegmentation", tmpFilter));
	cleanupFilters.push_back(tmpFilter);
	
	//HACK: replace Sword's ThML strip filter with our own version
	//remove this hack as soon as Sword is fixed
	cleanupFilters.remove(thmlplain);
	delete thmlplain;
	thmlplain = new BT_ThMLPlain();
	cleanupFilters.push_back(thmlplain);
}
