/**
 * \file trans_mgr.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Lars Gullik Bjønnes
 * \author Matthias Ettrich
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

#include "trans_mgr.h"

#include "BufferView.h"
#include "CutAndPaste.h"
#include "cursor.h"
#include "debug.h"
#include "lyxrc.h"
#include "lyxtext.h"
#include "trans.h"

#include "insets/insetlatexaccent.h"

#include "support/lstrings.h"

using lyx::support::split;

using std::endl;
using std::string;
using std::pair;


extern string const DoAccent(string const &, tex_accent);
extern string const DoAccent(char, tex_accent);


// TransFSMData
TransFSMData::TransFSMData()
{
	deadkey_ = deadkey2_ = 0;
	deadkey_info_.accent = deadkey2_info_.accent = TEX_NOACCENT;
}


// TransState
char const TransState::TOKEN_SEP = 4;


// TransInitState
TransInitState::TransInitState()
{
	init_state_ = this;
}


string const TransInitState::normalkey(char c)
{
	string res;
	res = c;
	return res;
}


string const TransInitState::deadkey(char c, KmodInfo d)
{
	deadkey_ = c;
	deadkey_info_ = d;
	currentState = deadkey_state_;
	return string();
}


// TransDeadkeyState
TransDeadkeyState::TransDeadkeyState()
{
	deadkey_state_ = this;
}


string const TransDeadkeyState::normalkey(char c)
{
	string res;

	KmodException::iterator it = deadkey_info_.exception_list.begin();
	KmodException::iterator end = deadkey_info_.exception_list.end();

	for (; it != end; ++it) {
		if (it->c == c) {
			res = it->data;
			break;
		}
	}
	if (it == end) {
		res = DoAccent(c, deadkey_info_.accent);
	}
	currentState = init_state_;
	return res;
}


string const TransDeadkeyState::deadkey(char c, KmodInfo d)
{
	string res;

	// Check if the same deadkey was typed twice
	if (deadkey_ == c) {
		res = deadkey_;
		deadkey_ = 0;
		deadkey_info_.accent = TEX_NOACCENT;
		currentState = init_state_;
		return res;
	}

	// Check if it is a combination or an exception
	KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
	KmodException::const_iterator end = deadkey_info_.exception_list.end();
	for (; cit != end; ++cit) {
		if (cit->combined == true && cit->accent == d.accent) {
			deadkey2_ = c;
			deadkey2_info_ = d;
			comb_info_ = (*cit);
			currentState = combined_state_;
			return string();
		}
		if (cit->c == c) {
			res = cit->data;
			deadkey_ = 0;
			deadkey_info_.accent = TEX_NOACCENT;
			currentState = init_state_;
			return res;
		}
	}

	// Not a combination or an exception.
	// Output deadkey1 and keep deadkey2

	if (deadkey_!= 0)
		res = deadkey_;
	deadkey_ = c;
	deadkey_info_ = d;
	currentState = deadkey_state_;
	return res;
}


TransCombinedState::TransCombinedState()
{
	combined_state_ = this;
}


string const TransCombinedState::normalkey(char c)
{
	string const temp = DoAccent(c, deadkey2_info_.accent);
	string const res = DoAccent(temp, deadkey_info_.accent);
	currentState = init_state_;
	return res;
}


string const TransCombinedState::deadkey(char c, KmodInfo d)
{
	// Third key in a row. Output the first one and
	// reenter with shifted deadkeys
	string res;
	if (deadkey_ != 0)
		res = deadkey_;
	res += TOKEN_SEP;
	deadkey_ = deadkey2_;
	deadkey_info_ = deadkey2_info_;
	res += deadkey_state_->deadkey(c, d);
	return res;
}


// TransFSM
TransFSM::TransFSM():
	TransFSMData(),
	TransInitState(),
	TransDeadkeyState(),
	TransCombinedState()
{
	currentState = init_state_;
}


// TransManager

// Initialize static member.
Trans TransManager::default_;


TransManager::TransManager()
	: active_(0), t1_(new Trans), t2_(new Trans)
{}


// For the sake of boost::scoped_ptr.
TransManager::~TransManager()
{}


int TransManager::SetPrimary(string const & language)
{
	if (t1_->GetName() == language)
		return 0;

	return t1_->Load(language);
}


int TransManager::SetSecondary(string const & language)
{
	if (t2_->GetName() == language)
		return 0;

	return t2_->Load(language);
}


bool TransManager::setCharset(string const & str)
{
	return chset_.loadFile(str);
}


void TransManager::EnablePrimary()
{
	if (t1_->IsDefined())
		active_ = t1_.get();

	lyxerr[Debug::KBMAP] << "Enabling primary keymap" << endl;
}


void TransManager::EnableSecondary()
{
	if (t2_->IsDefined())
		active_ = t2_.get();
	lyxerr[Debug::KBMAP] << "Enabling secondary keymap" << endl;
}


void TransManager::DisableKeymap()
{
	active_ = &default_;
	lyxerr[Debug::KBMAP] << "Disabling keymap" << endl;
}


void  TransManager::TranslateAndInsert(char c, LyXText * text)
{
	string res = active_->process(c, *this);

	// Process with tokens
	string temp;

	while (res.length() > 0) {
		res = split(res, temp, TransState::TOKEN_SEP);
		insert(temp, text);
	}
}


void TransManager::insertVerbatim(string const & str, LyXText * text)
{
	for (string::size_type i = 0, n = str.size(); i < n; ++i)
		text->insertChar(text->bv()->cursor(), str[i]);
}


void TransManager::insert(string const & str, LyXText * text)
{
	// Go through the character encoding only if the current
	// encoding (chset_->name()) matches the current font_norm
	// (lyrxc->font_norm)

	// Is false to speak about "only if" the current encoding will
	// almost always be equal to font_norm.
	pair<bool, int> enc = chset_.encodeString(str);
	if (chset_.getName() != lyxrc.font_norm ||
	    !enc.first) {
		// Could not find an encoding
		InsetLatexAccent ins(str);
		if (ins.canDisplay()) {
			LCursor & cur = text->bv()->cursor();
			lyx::cap::replaceSelection(cur);
			cur.insert(new InsetLatexAccent(ins));
			cur.posRight();
		} else {
			insertVerbatim(str, text);
		}
		return;
	}
	string const tmp(1, static_cast<char>(enc.second));
	insertVerbatim(tmp, text);
}


void TransManager::deadkey(char c, tex_accent accent, LyXText * t)
{
	if (c == 0 && active_ != &default_) {
		// A deadkey was pressed that cannot be printed
		// or a accent command was typed in the minibuffer
		KmodInfo i;
		if (active_->isAccentDefined(accent, i) == true) {
			string const res = trans_fsm_
				.currentState->deadkey(c, i);
			insert(res, t);
			return;
		}
	}

	if (active_ == &default_ || c == 0) {
		KmodInfo i;
		i.accent = accent;
		i.data.erase();
		string res = trans_fsm_.currentState->deadkey(c, i);
		insert(res, t);
	} else {
		// Go through the translation
		TranslateAndInsert(c, t);
	}
}
