/*
 * jid.cpp - class for verifying and manipulating Jabber IDs
 * Copyright (C) 2003  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "xmpp.h"

#include <tqdict.h>
#include <stringprep.h>

using namespace XMPP;

//----------------------------------------------------------------------------
// StringPrepCache
//----------------------------------------------------------------------------
class StringPrepCache
{
public:
	static bool nameprep(const TQString &in, int maxbytes, TQString *out)
	{
		if(in.isEmpty())
		{
			if(out)
				*out = TQString();
			return true;
		}

		StringPrepCache *that = get_instance();

		Result *r = that->nameprep_table.find(in);
		if(r)
		{
			if(!r->norm)
				return false;
			if(out)
				*out = *(r->norm);
			return true;
		}

		TQCString cs = in.utf8();
		cs.resize(maxbytes);
		if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0)
		{
			that->nameprep_table.insert(in, new Result);
			return false;
		}

		TQString norm = TQString::fromUtf8(cs);
		that->nameprep_table.insert(in, new Result(norm));
		if(out)
			*out = norm;
		return true;
	}

	static bool nodeprep(const TQString &in, int maxbytes, TQString *out)
	{
		if(in.isEmpty())
		{
			if(out)
				*out = TQString();
			return true;
		}

		StringPrepCache *that = get_instance();

		Result *r = that->nodeprep_table.find(in);
		if(r)
		{
			if(!r->norm)
				return false;
			if(out)
				*out = *(r->norm);
			return true;
		}

		TQCString cs = in.utf8();
		cs.resize(maxbytes);
		if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0)
		{
			that->nodeprep_table.insert(in, new Result);
			return false;
		}

		TQString norm = TQString::fromUtf8(cs);
		that->nodeprep_table.insert(in, new Result(norm));
		if(out)
			*out = norm;
		return true;
	}

	static bool resourceprep(const TQString &in, int maxbytes, TQString *out)
	{
		if(in.isEmpty())
		{
			if(out)
				*out = TQString();
			return true;
		}

		StringPrepCache *that = get_instance();

		Result *r = that->resourceprep_table.find(in);
		if(r)
		{
			if(!r->norm)
				return false;
			if(out)
				*out = *(r->norm);
			return true;
		}

		TQCString cs = in.utf8();
		cs.resize(maxbytes);
		if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0)
		{
			that->resourceprep_table.insert(in, new Result);
			return false;
		}

		TQString norm = TQString::fromUtf8(cs);
		that->resourceprep_table.insert(in, new Result(norm));
		if(out)
			*out = norm;
		return true;
	}

private:
	class Result
	{
	public:
		TQString *norm;

		Result() : norm(0)
		{
		}

		Result(const TQString &s) : norm(new TQString(s))
		{
		}

		~Result()
		{
			delete norm;
		}
	};

	TQDict<Result> nameprep_table;
	TQDict<Result> nodeprep_table;
	TQDict<Result> resourceprep_table;

	static StringPrepCache *instance;

	static StringPrepCache *get_instance()
	{
		if(!instance)
			instance = new StringPrepCache;
		return instance;
	}

	StringPrepCache()
	{
		nameprep_table.setAutoDelete(true);
		nodeprep_table.setAutoDelete(true);
		resourceprep_table.setAutoDelete(true);
	}
};

StringPrepCache *StringPrepCache::instance = 0;

//----------------------------------------------------------------------------
// Jid
//----------------------------------------------------------------------------
Jid::Jid()
{
	valid = false;
}

Jid::~Jid()
{
}

Jid::Jid(const TQString &s)
{
	set(s);
}

Jid::Jid(const char *s)
{
	set(TQString(s));
}

Jid & Jid::operator=(const TQString &s)
{
	set(s);
	return *this;
}

Jid & Jid::operator=(const char *s)
{
	set(TQString(s));
	return *this;
}

void Jid::reset()
{
	f = TQString();
	b = TQString();
	d = TQString();
	n = TQString();
	r = TQString();
	valid = false;
}

void Jid::update()
{
	// build 'bare' and 'full' jids
	if(n.isEmpty())
		b = d;
	else
		b = n + '@' + d;
	
	b=b.lower(); // JID are not case sensitive
	
	if(r.isEmpty())
		f = b;
	else
		f = b + '/' + r;
	if(f.isEmpty())
		valid = false;
}

void Jid::set(const TQString &s)
{
	TQString rest, domain, node, resource;
	TQString norm_domain, norm_node, norm_resource;
	int x = s.find('/');
	if(x != -1) {
		rest = s.mid(0, x);
		resource = s.mid(x+1);
	}
	else {
		rest = s;
		resource = TQString();
	}
	if(!validResource(resource, &norm_resource)) {
		reset();
		return;
	}

	x = rest.find('@');
	if(x != -1) {
		node = rest.mid(0, x);
		domain = rest.mid(x+1);
	}
	else {
		node = TQString();
		domain = rest;
	}
	if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) {
		reset();
		return;
	}

	valid = true;
	d = norm_domain;
	n = norm_node;
	r = norm_resource;
	update();
}

void Jid::set(const TQString &domain, const TQString &node, const TQString &resource)
{
	TQString norm_domain, norm_node, norm_resource;
	if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) {
		reset();
		return;
	}
	valid = true;
	d = norm_domain;
	n = norm_node;
	r = norm_resource;
	update();
}

void Jid::setDomain(const TQString &s)
{
	if(!valid)
		return;
	TQString norm;
	if(!validDomain(s, &norm)) {
		reset();
		return;
	}
	d = norm;
	update();
}

void Jid::setNode(const TQString &s)
{
	if(!valid)
		return;
	TQString norm;
	if(!validNode(s, &norm)) {
		reset();
		return;
	}
	n = norm;
	update();
}

void Jid::setResource(const TQString &s)
{
	if(!valid)
		return;
	TQString norm;
	if(!validResource(s, &norm)) {
		reset();
		return;
	}
	r = norm;
	update();
}

Jid Jid::withNode(const TQString &s) const
{
	Jid j = *this;
	j.setNode(s);
	return j;
}

Jid Jid::withResource(const TQString &s) const
{
	Jid j = *this;
	j.setResource(s);
	return j;
}

bool Jid::isValid() const
{
	return valid;
}

bool Jid::isEmpty() const
{
	return f.isEmpty();
}

bool Jid::compare(const Jid &a, bool compareRes) const
{
	// only compare valid jids
	if(!valid || !a.valid)
		return false;

	if(compareRes ? (f != a.f) : (b != a.b))
		return false;

	return true;
}

bool Jid::validDomain(const TQString &s, TQString *norm)
{
	/*TQCString cs = s.utf8();
	cs.resize(1024);
	if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0)
		return false;
	if(norm)
		*norm = TQString::fromUtf8(cs);
	return true;*/
	return StringPrepCache::nameprep(s, 1024, norm);
}

bool Jid::validNode(const TQString &s, TQString *norm)
{
	/*TQCString cs = s.utf8();
	cs.resize(1024);
	if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0)
		return false;
	if(norm)
		*norm = TQString::fromUtf8(cs);
	return true;*/
	return StringPrepCache::nodeprep(s, 1024, norm);
}

bool Jid::validResource(const TQString &s, TQString *norm)
{
	/*TQCString cs = s.utf8();
	cs.resize(1024);
	if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0)
		return false;
	if(norm)
		*norm = TQString::fromUtf8(cs);
	return true;*/
	return StringPrepCache::resourceprep(s, 1024, norm);
}
