package org.bodington.util;

import java.io.UnsupportedEncodingException;

public class URLDecoder
{
    
    /**
     * Decodes a &quot;x-www-form-urlencoded&quot;
     * to a <tt>String</tt>.
     * @param s the <code>String</code> to decode
     * @return the newly decoded <code>String</code>
     */
    public static String decode( String s, String encoding )
    throws UnsupportedEncodingException
    {
	if ( "bodington_underscore".equalsIgnoreCase( encoding ) )
	    return decodeFromBodURL_2_1( s );
	if ( "bodington_escape".equalsIgnoreCase( encoding ) )
	    return decodeFromBodURL_2_0( s );
	return java.net.URLDecoder.decode( s, encoding );
    }
    
    
    private static String decodeFromBodURL_2_1( String url )
    {
	try
	{
	    // URLDecode then find four digit hexadecimal coded
	    // UTF16 characters escaped by 0x1b
	    String utf16escaped = URLDecoder.decode( url, "US-ASCII" );
	    
	    // if it doesn't have underscore at start then it's a normal
	    // url.
	    
	    if ( !utf16escaped.startsWith( "_" ) )
		return utf16escaped;
	    
	    StringBuffer name = new StringBuffer( utf16escaped.length() );
	    int c, i, j;
	    String hex;
	    
	    // Find four digit hexadecimal coded
	    // UTF16 characters escaped by underscore
	    for ( i=1; i< utf16escaped.length(); i++ )
	    {
		c=utf16escaped.charAt( i );
		
		if ( c == '_' )
		{
		    // followed immediately by another underscore this is an
		    // escaped underscore.
		    if ( (i+1) < utf16escaped.length() && utf16escaped.charAt( i+1 ) == '_' )
		    {
			name.append( "_" );
			i++;
		    }
		    // otherwise it is an escaped 16 bit character
		    else
		    {
			//find first invalid digit, up to four digits
			for ( j=0; j<4 && (i+j+1)<utf16escaped.length(); j++ )
			{
			    c = utf16escaped.charAt( i+j+1 );
			    if ( c<'0' || (c>'9' && c<'A') || (c>'F' && c<'a') || c>'f' )
				break;
			}
			
			hex = utf16escaped.substring( i+1, i+j+1 );
			// guaranteed to be a valid number now
			if ( hex.length()>0 )
			{
			    try
			    {
				c = Integer.parseInt( hex, 16 );
			    }
			    catch ( NumberFormatException nfex )
			    {
				c = '?';  //should never happen!
			    }
			    name.append( (char)c );
			}
			i+=j;
		    }
		}
		else
		    name.append( (char)c );
	    }
	    
	    return name.toString();
	}
	catch ( UnsupportedEncodingException e )
	{
	    // can never happen with US_ASCII?
	    return null;
	}
    }
    
    
    private static String decodeFromBodURL_2_0( String url )
    {
	try
	{
	    // URLDecode then find four digit hexadecimal coded
	    // UTF16 characters escaped by 0x1b
	    String utf16escaped = URLDecoder.decode( url, "US-ASCII" );
	    StringBuffer name = new StringBuffer( utf16escaped.length() );
	    int c, i, j;
	    String hex;
	    
	    for ( i=0; i< utf16escaped.length(); i++ )
	    {
		c=utf16escaped.charAt( i );
		
		if ( c == 0x1b )
		{
		    //find first invalid digit, up to four digits
		    for ( j=0; j<4 && (i+j+1)<utf16escaped.length(); j++ )
		    {
			c = utf16escaped.charAt( i+j+1 );
			if ( c<'0' || (c>'9' && c<'A') || (c>'F' && c<'a') || c>'f' )
			    break;
		    }
		    
		    hex = utf16escaped.substring( i+1, i+j+1 );
		    // guaranteed to be a valid number now
		    if ( hex.length()>0 )
		    {
			try
			{
			    c = Integer.parseInt( hex, 16 );
			}
			catch ( NumberFormatException nfex )
			{
			    c = '?';  //should never happen!
			}
			name.append( (char)c );
		    }
		    i+=j;
		}
		else
		    name.append( (char)c );
	    }
	    
	    return name.toString();
	}
	catch ( UnsupportedEncodingException e )
	{
	    // can never happen with US_ASCII?
	    return null;
	}
    }
    
}
