//
// See the file LICENSE for redistribution information.
//
// Copyright (c) 2002-2003
//	Sleepycat Software.  All rights reserved.
//

static const char revid[] = "$Id: IndexSpecification.cpp,v 1.78 2003/10/13 16:40:55 merrells Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "dbxml/XmlException.hpp"
#include "dbxml/XmlNamespace.hpp"
#include "IndexSpecification.hpp"
#include "SyntaxManager.hpp"
#include "tokenizer.hpp"
#include "Name.hpp"
#include "Container.hpp"
#include "ScopedDbt.hpp"
#include "Cursor.hpp"

#include <map>
#include <algorithm>

using namespace std;
using namespace DbXml;

// Index

typedef map<string, unsigned long> nameToNumber;
static nameToNumber *n2n = 0;

Index::Index()
	: index_(Index::NONE)
{
	if (n2n == 0)
		init();
}

Index::Index(unsigned long index)
	: index_(index)
{
	if (n2n == 0)
		init();
}

Index::Index(const std::string &s)
	: index_(0)
{
	if (n2n == 0)
		init();
	set(s);
}

void Index::init() const
{
	n2n = new nameToNumber;
	(*n2n)["none"] = NONE;

	(*n2n)["node"] = PATH_NODE;
	(*n2n)["edge"] = PATH_EDGE;

	(*n2n)["element"] = NODE_ELEMENT;
	(*n2n)["attribute"] = NODE_ATTRIBUTE;

	(*n2n)["equality"] = KEY_EQUALITY;
	(*n2n)["presence"] = KEY_PRESENCE;
	(*n2n)["substring"] = KEY_SUBSTRING;
}

bool Index::set(const std::string &s)
{
	bool r = true;
	index_ = 0;
	size_t start = 0;
	size_t end = 0;
	while (end != string::npos) {
		end = s.find_first_of('-', start);
		string s2(s, start, (end == string::npos ? s.length() : end) - start);
		start = end + 1;
		map<string, unsigned long>::const_iterator i = (*n2n).find(s2);
		if (i == (*n2n).end()) {
			const Syntax *syntax = SyntaxManager::getInstance()->getSyntax(s2);
			if (syntax != 0) {
				index_ |= syntax->getType();
			} else {
				r = false; // ERROR
			}
		} else {
			index_ |= i->second;
		}
	}
	if (r) {
		// The indexing strategy didn't contain any unknown words, but
		// did we end up with an indexing strategy that makes sense?
		//
		r = isValidIndex();
	}
	return r;
}

bool Index::set(unsigned long index)
{
	if (index&INDEXER_MASK)
		index_ = (index_ & ~INDEXER_MASK) | (index & INDEXER_MASK);
	if (index&PATH_MASK)
		index_ = (index_ & ~PATH_MASK) | (index & PATH_MASK);
	if (index&NODE_MASK)
		index_ = (index_ & ~NODE_MASK) | (index & NODE_MASK);
	if (index&KEY_MASK)
		index_ = (index_ & ~KEY_MASK) | (index & KEY_MASK);
	if (index&SYNTAX_MASK)
		index_ = (index_ & ~SYNTAX_MASK) | (index & SYNTAX_MASK);
	return true;
}

bool Index::equalsMask(Index::Type test, Index::Type mask) const
{
	return (test & mask) == (index_ & mask);
}

bool Index::indexerAdd() const
{
	return equalsMask(INDEXER_ADD, INDEXER_MASK);
}

bool Index::isNoneIndex() const
{
	return equalsMask(NONE, PNKS_MASK);
}

bool Index::isValidIndex() const
{
	// A valid index is (none or x-x-presence-none or x-x-x-x)
	return
	    isNoneIndex() ||
	    (((index_ & PATH_MASK) != 0) &&
	     ((index_ & NODE_MASK) != 0) &&
	     ((index_ & KEY_MASK) != 0) &&
	     (equalsMask(KEY_PRESENCE, KEY_MASK) || ((index_ & SYNTAX_MASK) != 0)));
}

void Index::setFromPrefix(unsigned char prefix)
{
	index_ =
	    ((((unsigned long)prefix) & 0xC0) << 18) |
	    ((((unsigned long)prefix) & 0x30) << 12) |
	    ((((unsigned long)prefix) & 0x0F) << 8);
}

unsigned char Index::getKeyPrefix() const
{
	return (unsigned char)
	       (((index_&PATH_MASK) >> 18) |
		((index_&NODE_MASK) >> 12) |
		((index_&KEY_MASK) >> 8));
}

std::string Index::asString() const
{
	string s;
	s += axisAsName(getPath());
	s += "-";
	s += axisAsName(getNode());
	s += "-";
	s += axisAsName(getKey());
	s += "-";
	const Syntax *syntax = SyntaxManager::getInstance()->getSyntax((Syntax::Type)(getSyntax()));
	s += syntax->getName();
	return s;
}

std::string Index::axisAsName(Index::Type index) const
{
	nameToNumber::const_iterator i;
	for (i = (*n2n).begin();i != (*n2n).end();++i) {
		if (index == i->second) {
			return i->first;
		}
	}
	return "";
}

std::ostream& DbXml::operator<<(std::ostream& s, const Index &index)
{
	return s << index.asString();
}

// IndexVector

IndexVector::IndexVector(const Name &name)
	: name_(name)
{}

IndexVector::IndexVector(const IndexVector &iv)
	: name_(iv.name_),
	iv_(iv.iv_)
{}

IndexVector::~IndexVector()
{}

bool IndexVector::enableIndex(Index index)
{
	// If the index is NONE, then remove all the other indexes first.
	if (index.equals(Index::NONE)) {
		iv_.clear();
	}
	// If the index is not already enabled, then add the index.
	if (!isEnabled((const Index::Type)index, Index::PNKS_MASK)) {
		iv_.push_back(index);
	}
	return true;
}

bool IndexVector::enableIndex(const std::string &s)
{
	bool r = true;
	Index index;
	// Check that the index strategy names are ok.
	tokenizer tok(s, ", ");
	for (std::string indexspec; r && tok.next(indexspec) == 0; ) {
		r = index.set(indexspec);
	}
	if (r) {
		// Enable the index strategy.
		tokenizer tok(s, ", ");
		for (std::string indexspec; tok.next(indexspec) == 0; ) {
			index.set(indexspec);
			enableIndex(index);
		}
	}
	return r;
}

void IndexVector::enableIndex(const IndexVector &iv)
{
	Index::Vector::const_iterator i;
	for (i = iv.iv_.begin();i != iv.iv_.end();++i) {
		enableIndex(*i);
	}
}

bool IndexVector::isEnabled(const Index::Type &test, const Index::Type &mask) const
{
	bool r = false;
	Index::Vector::const_iterator i;
	for (i = iv_.begin();!r && i != iv_.end();++i) {
		r = i->equalsMask(test, mask);
	}
	return r;
}

bool IndexVector::isIndexed() const
{
	bool r = false;
	Index::Vector::const_iterator i;
	for (i = iv_.begin();!r && i != iv_.end();++i) {
		r = !i->isNoneIndex() && i->isValidIndex();
	}
	return r;
}

void IndexVector::getNextIndex(int &i, Index::Type test, Index::Type mask, Index &index) const
{
	if (i > -1) {
		bool done = false;
		int size = (int)iv_.size(); // unsigned to signed
		while (i < size && !done) {
			if (iv_[i].equalsMask(test, mask)) {
				index = iv_[i];
				done = true;
			}
			++i;
		}
		if (i == size) {
			i = -1;
		}
	}
}

const Syntax *IndexVector::getNextSyntax(int &i, Index::Type test, Index::Type mask, Index &index) const
{
	const Syntax *syntax = 0;
	index = 0;
	if (i > -1) {
		getNextIndex(i, test, mask, index);
		if (!index.equals(Index::NONE)) {
			syntax = SyntaxManager::getInstance()->getSyntax((Syntax::Type)index.getSyntax());
		}
	}
	return syntax;
}

std::string IndexVector::asString() const
{
	std::string s;
	bool first = true;
	Index::Vector::const_iterator i;
	for (i = iv_.begin();i != iv_.end();++i) {
		if (first) {
			first = false;
		} else {
			s += " ";
		}
		s += i->asString();
	}
	return s;
}

void IndexVector::disableIndex(const Index &index)
{
	iv_.erase(remove(iv_.begin(), iv_.end(), index), iv_.end());
}

bool IndexVector::disableIndex(const std::string &s)
{
	bool r = true;
	Index index;
	// Check that the index strategy names are ok.
	tokenizer tok(s, ", ");
	for (std::string indexspec; r && tok.next(indexspec) == 0; ) {
		r = index.set(indexspec);
	}
	if (r) {
		// Disable the index strategy.
		tokenizer tok(s, ", ");
		for (std::string indexspec; tok.next(indexspec) == 0; ) {
			index.set(indexspec);
			disableIndex(index);
		}
	}
	return r;
}

void IndexVector::disableIndex(const IndexVector &iv)
{
	Index::Vector::const_iterator i;
	for (i = iv.iv_.begin();i != iv.iv_.end();++i) {
		disableIndex(*i);
	}
}

void IndexVector::disableIndex(Index::Type test, Index::Type mask)
{
	bool done= false;
	while(!done)
	{
		Index::Vector::const_iterator i;
		for (i = iv_.begin();!i->equalsMask(test, mask) && i!=iv_.end();++i);
		done= (i==iv_.end());
		if (!done) disableIndex(*i);
	}
}

void IndexVector::set(unsigned long index)
{
	Index::Vector::iterator i;
	for (i = iv_.begin();i != iv_.end();++i) {
		i->set(index);
	}
}

// IndexSpecification

IndexSpecification::IndexSpecification()
	: defaultIndex_(enableIndex(DbXml::metaDataName_uri_default, 0, "none-none-none-none"))
{}

IndexSpecification::IndexSpecification(const IndexSpecification &is)
	: defaultIndex_(0)
{
	IndexMap::const_iterator i;
	for (i = is.indexMap_.begin(); i != is.indexMap_.end(); ++i) {
		indexMap_[::strdup(i->first)] = new IndexVector(*i->second);
	}
}

IndexSpecification::~IndexSpecification()
{
	clear();
}

void IndexSpecification::addIndex(const std::string &uri, const std::string &name, const std::string &index)
{
	Name n(uri, "", name);
	string s(n.getURIName());
	enableIndex(s.c_str(), 0, index);
}

void IndexSpecification::deleteIndex(const std::string &uri, const std::string &name, const std::string &index)
{
	Name n(uri, "", name);
	string s(n.getURIName());
	disableIndex(s.c_str(), 0, index);
}

const IndexVector *IndexSpecification::getIndexOrDefault(const char *uriname) const
{
	const IndexVector *r = getIndex(uriname);
	if (!r) {
		// Indexing not specified so return the default.
		if(defaultIndex_ && defaultIndex_->isIndexed()) {
			r = defaultIndex_;
		}
	}
	return r;
}

const IndexVector *IndexSpecification::getIndex(const char *uriname) const
{
	const IndexVector *r = 0;
	IndexMap::const_iterator i = indexMap_.find(uriname);
	if (i != indexMap_.end() && i->second->isIndexed()) {
		r= i->second;
	}
	return r;
}

bool IndexSpecification::isIndexed(Index::Type test, Index::Type mask) const
{
	bool r = false;
	IndexMap::const_iterator i;
	for (i = indexMap_.begin();!r && i != indexMap_.end();++i) {
		r = i->second->isEnabled(test, mask);
	}
	return r;
}

IndexVector *IndexSpecification::enableIndex(const char *uriname, unsigned long index, const std::string &indexString)
{
	IndexVector *iv = 0;
	IndexMap::iterator i = indexMap_.find(uriname);
	if (i != indexMap_.end()) {
		iv = i->second;
	} else {
		iv = new IndexVector(uriname);
		indexMap_[::strdup(uriname)] = iv;
	}
	if (!indexString.empty()) {
		if (!iv->enableIndex(indexString)) {
			throw XmlException(XmlException::UNKNOWN_INDEX, "Unknown index specification, '" + indexString + "', for node '" + uriname + "'.");
		} else {
			buffer_.reset();
		}
	} else {
		iv->enableIndex(index);
		buffer_.reset();
	}
	if (::strcmp(uriname, metaDataName_uri_default) == 0) {
		defaultIndex_ = iv;
	}
	return iv;
}

IndexVector *IndexSpecification::enableIndex(const char *uriname, const IndexVector &iv)
{
	IndexMap::iterator i = indexMap_.find(uriname);
	if (i != indexMap_.end()) {
		i->second->enableIndex(iv);
	}
	return 0;
}

IndexVector *IndexSpecification::enableIndex(const IndexSpecification &is)
{
	IndexMap::const_iterator i;
	for (i = is.indexMap_.begin(); i != is.indexMap_.end(); ++i) {
		enableIndex(i->first, *i->second);
	}
	return 0;
}

IndexVector *IndexSpecification::disableIndex(const char *uriname, unsigned long index, const std::string &indexString)
{
	IndexVector *iv = 0;
	IndexMap::iterator i = indexMap_.find(uriname);
	if (i != indexMap_.end()) {
		iv = i->second;
		if (!indexString.empty()) {
			if (!iv->disableIndex(indexString)) {
				throw XmlException(XmlException::UNKNOWN_INDEX, "Unknown index specification, '" + indexString + "', for node '" + uriname + "'.");
			} else {
				buffer_.reset();
			}
		} else {
			iv->disableIndex(index);
			buffer_.reset();
		}
	}
	return iv;
}

IndexVector *IndexSpecification::disableIndex(const char *uriname, const IndexVector &iv)
{
	IndexMap::iterator i = indexMap_.find(uriname);
	if (i != indexMap_.end()) {
		i->second->disableIndex(iv);
	}
	return 0;
}

IndexVector *IndexSpecification::disableIndex(const IndexSpecification &is)
{
	IndexMap::const_iterator i;
	for (i = is.indexMap_.begin(); i != is.indexMap_.end(); ++i) {
		disableIndex(i->first, *i->second);
	}
	return 0;
}

IndexVector *IndexSpecification::disableIndex(Index::Type test, Index::Type mask)
{
	IndexMap::const_iterator i;
	for (i = indexMap_.begin(); i != indexMap_.end(); ++i) {
		i->second->disableIndex(test, mask);
	}
	return 0;
}

void IndexSpecification::set(unsigned long index)
{
	IndexMap::iterator i;
	for (i = indexMap_.begin(); i != indexMap_.end(); ++i) {
		i->second->set(index);
	}
}

void IndexSpecification::clear()
{
	IndexMap::iterator i;
	for (i = indexMap_.begin();i != indexMap_.end();++i) {
		::free((void*)i->first);
		delete i->second;
	}
	indexMap_.clear();
	buffer_.reset();
	defaultIndex_= 0;
}

bool IndexSpecification::find(const std::string &uri, const std::string &name, std::string &index)
{
	bool r = false;
	Name n(uri, "", name);
	string s(n.getURIName());
	IndexVector *iv = 0;
	IndexMap::iterator i = indexMap_.find(s.c_str());
	if (i != indexMap_.end() && i->second->isIndexed()) {
		index = i->second->asString();
		r = true;
	}
	return r;
}

int IndexSpecification::read(const Container &container, DbTxn *txn, bool lock)
{
	// We read the indexing specification into tmpBuffer_, and
	// if it's different than the cached indexing specification
	// stored in buffer_, then we parse tmpBuffer_ and copy tmpBuffer_
	// into buffer_ for that next time around.
	//
	static const char *key = "index";
	static size_t keyLength = strlen(key) + 1;
	tmpBuffer_.reset();
	int err = container.getConfigurationItem(txn, key, keyLength, tmpBuffer_, lock);
	if (err == 0) {
		if (buffer_ != tmpBuffer_) {
			clear();
			const char *p = (const char *)tmpBuffer_.getBuffer();
			while (p != 0 && *p != '\0') {
				const char *uriname = p;
				p += strlen(p) + 1;
				const char *index = p;
				p += strlen(p) + 1;
				enableIndex(uriname, 0, index);
			}
			buffer_ = tmpBuffer_;
		}
	} else if (err == DB_NOTFOUND) {
		err = 0;
	}
	return err;
}

int IndexSpecification::write(Container &container, DbTxn *txn) const
{
	if (buffer_.getOccupancy() == 0) {
		IndexMap::const_iterator i;
		for (i = indexMap_.begin(); i != indexMap_.end(); ++i) {
			const char *uriname = i->first;
			string index = i->second->asString();
			buffer_.write(uriname, strlen(uriname) + 1);
			buffer_.write(index.c_str(), index.length() + 1);
		}
		char null = '\0';
		buffer_.write(&null, sizeof(null));
	}
	return container.putConfigurationItem(txn, "index", buffer_);
}

std::string IndexSpecification::asString() const
{
	string r;
	IndexMap::const_iterator i;
	for (i = indexMap_.begin();i != indexMap_.end();++i) {
		if(i->second->isIndexed()) {
			r += i->first;
			r += "=>";
			r += i->second->asString();
			r += " ";
		}
	}
	return r;
}

IndexSpecificationIterator::IndexSpecificationIterator(const IndexSpecification &is)
	: is_(is),
	i_(is.begin())
{}

bool IndexSpecificationIterator::next(std::string &uri, std::string &name, std::string &index)
{
	bool r = false;
	while (!r && i_ != is_.end()) {
		if (i_->second->isIndexed()) {
			Name n(i_->first);
			uri = n.getURI();
			name = n.getName();
			index = i_->second->asString();
			r = true;
		}
		i_++;
	}
	if (!r) {
		uri.erase();
		name.erase();
		index.erase();
	}
	return r;
}

void IndexSpecificationIterator::reset()
{
	i_ = is_.begin();
}
