//
// Query.cpp
//
// Library: JSON
// Package: JSON
// Module:  Query
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/JSON/Query.h"
#include "Poco/StringTokenizer.h"
#include "Poco/RegularExpression.h"
#include "Poco/NumberParser.h"
#include <sstream>


using Poco::Dynamic::Var;


namespace Poco {
namespace JSON {


Query::Query(const Var& source): _source(source)
{
	if (!source.isEmpty() &&
		source.type() != typeid(Object) &&
		source.type() != typeid(Object::Ptr) &&
		source.type() != typeid(Array) &&
		source.type() != typeid(Array::Ptr))
		throw InvalidArgumentException("Only JSON Object, Array or pointers thereof allowed.");
}


Query::~Query() = default;


Object::Ptr Query::findObject(const std::string& path) const
{
	Var result = find(path);

	if (result.type() == typeid(Object::Ptr))
		return result.extract<Object::Ptr>();
	else if (result.type() == typeid(Object))
		return new Object(result.extract<Object>());

	return nullptr;
}


Object& Query::findObject(const std::string& path, Object& obj) const
{
	obj.clear();

	Var result = find(path);

	if (result.type() == typeid(Object::Ptr))
		obj = *result.extract<Object::Ptr>();
	else if (result.type() == typeid(Object))
		obj = result.extract<Object>();

	return obj;
}


Array::Ptr Query::findArray(const std::string& path) const
{
	Var result = find(path);

	if (result.type() == typeid(Array::Ptr))
		return result.extract<Array::Ptr>();
	else if (result.type() == typeid(Array))
		return new Array(result.extract<Array>());

	return nullptr;
}


Array& Query::findArray(const std::string& path, Array& arr) const
{
	arr.clear();

	Var result = find(path);

	if (result.type() == typeid(Array::Ptr))
		arr = *result.extract<Array::Ptr>();
	else if (result.type() == typeid(Array))
		arr = result.extract<Array>();

	return arr;
}


Var Query::find(const std::string& path) const
{
	Var result = _source;
	if (path.empty()) return result;
	bool found = false;
	StringTokenizer tokenizer(path, ".");
	for (const auto& token: tokenizer)
	{
		if (!result.isEmpty())
		{
			std::vector<int> indexes;
			RegularExpression::MatchVec matches;
			int firstOffset = -1;
			int offset = 0;
			RegularExpression regex("\\[([0-9]+)\\]");
			while (regex.match(token, offset, matches) > 0)
			{
				if (firstOffset == -1)
				{
					firstOffset = static_cast<int>(matches[0].offset);
				}
				std::string num(token, matches[1].offset, matches[1].length);
				indexes.push_back(NumberParser::parse(num));
				offset = static_cast<int>(matches[0].offset + matches[0].length);
			}

			std::string name(token);
			if (firstOffset != -1)
			{
				name = name.substr(0, firstOffset);
			}

			if (name.length() > 0)
			{
				if (result.type() == typeid(Object::Ptr))
				{
					Object::Ptr o = result.extract<Object::Ptr>();
					result = o->get(name);
					found = true;
				}
				else if (result.type() == typeid(Object))
				{
					Object o = result.extract<Object>();
					result = o.get(name);
					found = true;
				}
				else result.clear();

			}

			if (!result.isEmpty() && !indexes.empty())
			{
				for (const auto& i: indexes)
				{
					if (result.type() == typeid(Array::Ptr))
					{
						Array::Ptr array = result.extract<Array::Ptr>();
						result = array->get(i);
						if (result.isEmpty()) break;
					}
					else if (result.type() == typeid(Array))
					{
						Array array = result.extract<Array>();
						result = array.get(i);
						if (result.isEmpty()) break;
					}
				}
			}
		}
	}
	if (!found) result.clear();
	return result;
}


} } // namespace Poco::JSON
