/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */



#include "XalanParsedURI.hpp"



#include "DOMStringHelper.hpp"
#include "XalanUnicode.hpp"



XALAN_CPP_NAMESPACE_BEGIN


#if defined(XALAN_INLINE_INITIALIZATION) && !defined(XALAN_INLINE_INITIALIZATION_IS_DEFINITION_BUG)
const int	XalanParsedURI::d_scheme;
const int	XalanParsedURI::d_authority;
const int	XalanParsedURI::d_query;
const int	XalanParsedURI::d_fragment;
#endif



/* Merge the components back into a complete URI string */
XalanDOMString& XalanParsedURI::make(XalanDOMString&		uri) const
{
    uri.erase();

	if (m_defined & d_scheme)
	{
		uri += m_scheme;
		uri += XalanUnicode::charColon;
	}
	if (m_defined & d_authority)
	{
		uri += XalanUnicode::charSolidus;
		uri += XalanUnicode::charSolidus;
		uri += m_authority;
	}
	uri += m_path;
	if (m_defined & d_query)
	{
		uri += XalanUnicode::charQuestionMark;
		uri += m_query;
	}
	if (m_defined & d_fragment)
	{
		uri += XalanUnicode::charNumberSign;
		uri += m_fragment;
	}
	return uri;
}

/* Parse a URI into component parts.
   Essentially implements the regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 */
void XalanParsedURI::parse(
	const XalanDOMChar*			uriString,
	XalanDOMString::size_type	uriStringLen
)
{
	XalanDOMString::size_type index = 0;
	
	// Clear the components present mask
	m_defined = 0;

	// Scheme portion
	while (index < uriStringLen && 
				uriString[index] != XalanUnicode::charColon && 
				uriString[index] != XalanUnicode::charSolidus && 
				uriString[index] != XalanUnicode::charQuestionMark && 
				uriString[index] != XalanUnicode::charNumberSign)
	{
		++index;
	}
	
	if (index > 0 && uriString[index] == XalanUnicode::charColon)
	{
		m_scheme = XalanDOMString(uriString, getMemoryManager(), index);
		++index;
		m_defined |= d_scheme;
	}
	else
	{
		index = 0;
		m_scheme.clear();
	}

	// Authority portion
	if (index < uriStringLen - 1 &&
		uriString[index] == XalanUnicode::charSolidus && 
		uriString[index+1] == XalanUnicode::charSolidus) 
	{
		index += 2;
		XalanDOMString::size_type authority = index;

		while (index < uriStringLen &&
				uriString[index] != XalanUnicode::charSolidus && 
				uriString[index] != XalanUnicode::charQuestionMark && 
				uriString[index] != XalanUnicode::charNumberSign)
		{
			++index;
		}
		m_authority = XalanDOMString(uriString + authority, getMemoryManager(), index - authority);
		m_defined |= d_authority;
	}
	else
	{
		m_authority.clear();
	}

	// Path portion
	XalanDOMString::size_type path = index;
	while (index < uriStringLen &&
			uriString[index] != XalanUnicode::charQuestionMark && 
			uriString[index] != XalanUnicode::charNumberSign)
	{
		++index;
	}
	m_path = XalanDOMString(uriString + path,getMemoryManager(), index - path);

	// Query portion
	if (index < uriStringLen && uriString[index] == XalanUnicode::charQuestionMark)
	{
		++index;
		XalanDOMString::size_type query = index;

		while (index < uriStringLen &&
				uriString[index] != XalanUnicode::charNumberSign)
		{
			++index;
		}
		m_query = XalanDOMString(uriString + query,getMemoryManager(), index - query);
		m_defined |= d_query;
	}
	else
	{
		m_query.clear();
	}

	// Fragment portion
	if (index < uriStringLen && uriString[index] == XalanUnicode::charNumberSign)
	{
		++index;
		m_fragment = XalanDOMString(uriString + index, getMemoryManager(), uriStringLen - index);
		m_defined |= d_fragment;
	}
	else
	{
		m_fragment.clear();
	}
}

/* Resolve this URI relative to another according to RFC2396, section 5.2 */
void XalanParsedURI::resolve(
	const XalanParsedURI &base
)
{
    if (base.isSchemeDefined() == false)
    {
        // Protect against a base URI that is relative...
        // This might become an assert or an exception.
    }
	// Handle references to the current document (step 2)
	else if ((m_defined & (d_scheme | d_authority | d_query)) == 0 &&
		m_path.empty())
	{
		m_scheme	= base.m_scheme;
		m_authority = base.m_authority;
		m_path		= base.m_path;
		m_query		= base.m_query;

		// There is an error/unclarity in the specification in step 2 in that
		// it doesn't state that the fragment should be inherited; however
		// it is clear from the examples that it should be
		if (!(m_defined & d_fragment))
		{
			m_fragment = base.m_fragment;
		}

		m_defined |= base.m_defined;
	}
	// A defined scheme component implies that this is an absolute URI (step 3)
	// Also allow a scheme without authority that matches the base scheme to be 
	// interpreted as a relative URI
	else if (!(m_defined & d_scheme) || ( 
			(base.m_defined & d_scheme) && !(m_defined & d_authority) 
			&& equalsIgnoreCaseASCII(m_scheme, base.m_scheme)))
	{
		// Inherit the base scheme
		m_scheme = base.m_scheme;
	    m_defined |= d_scheme;

		// Step 4: If the authority is unm_defined then inherit it, otherwise skip to step 7
		if (!(m_defined & d_authority))
		{
			// Inherit the base authority
			m_authority = base.m_authority;
            m_defined |= d_authority;

			// Step 5: if the path starts with a / then it is absolute
			if (!(m_path.length() > 0 && m_path[0] == XalanUnicode::charSolidus))
			{
				// Step 6: merge relative path components

				// a) strip off characters after the right most slash in the base path
				XalanDOMString::size_type pathEnd = base.m_path.length();
				while (pathEnd > 0 && base.m_path[pathEnd - 1] != XalanUnicode::charSolidus)
				{
					--pathEnd;
				}

				if (pathEnd > 0) 
				{
					// b) append relative path
					// This inserts the path portion from base...
					m_path.insert(0, base.m_path, 0, pathEnd);
				}
				else
				{
					// TODO, maybe raise an error here as this
					// is a severely wonky looking URI
				}

				// c)->g remove various "./" and "../" segments
				for (XalanDOMString::size_type index = 0; index < m_path.length(); ) 
				{
					// remove '<segment>/../' and ./
					if (m_path[index] == XalanUnicode::charFullStop) 
					{
						if (index < m_path.length()-1 && 
							m_path[index+1] == XalanUnicode::charSolidus) // ./
						{
							m_path.erase(index,2);
							continue;
						} 
						else if (index == m_path.length()-1) // trailing /.
						{
							m_path.erase(index,1);
							continue;
						} 
						// Note: also strips leading ../ in an attempt to get 
						// something out of a bad m_path
						else if (index < m_path.length()-2 && 
									m_path[index+1] == XalanUnicode::charFullStop && 
									m_path[index+2] == XalanUnicode::charSolidus) // ../
						{ 
							const XalanDOMString::size_type		end = index + 2;
							if (index > 0) --index;
							for ( ; index > 0 && m_path[index-1] != XalanUnicode::charSolidus; index--) 
								;
							if (index > 0) --index;
							m_path.erase(index, end - index);
							continue;
						} 
						else if (index == m_path.length()-2 && 
									m_path[index+1] == XalanUnicode::charFullStop) // trailing /..
						{
							const XalanDOMString::size_type		end = index + 2;
							if (index > 0) --index;
							for ( ; index > 0 && m_path[index-1] != XalanUnicode::charSolidus; index--) 
								;
							m_path.erase(index, end - index);
							continue;
						}
					}
					for ( ; index < m_path.length() && m_path[index] != XalanUnicode::charSolidus ; ++index)
					{
					}
					++index;
				}
			}
		}
	}
}

/* Static helper function to perform a resolve without mucking about with this class */
XalanDOMString& XalanParsedURI::resolve(
	const XalanDOMChar			*relative,
	XalanDOMString::size_type	relativeLen,
	const XalanDOMChar			*base,
	XalanDOMString::size_type	baseLen,
    XalanDOMString&		        theResult
)
{
    XalanParsedURI relativeURI(relative, relativeLen, theResult.getMemoryManager());
    XalanParsedURI baseURI(base, baseLen, theResult.getMemoryManager());

	relativeURI.resolve(baseURI);
	return relativeURI.make(theResult);
}

XALAN_CPP_NAMESPACE_END
