// $Id: Palindrome.java,v 1.1 1999/05/19 17:54:06 schmidt Exp $

// The Nth Positive Integer Palindrome Generator
// Copyright (C) 1999 Eric A. Schmidt
// 
// This applet converts between N and the Nth palindrome.
// Correctness was my main focus.  There is room for optimization.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.math.*;

public class Palindrome
	extends Applet
	implements ActionListener
{
	static BigInteger m_zero = new BigInteger( "0" );
	static BigInteger m_one = new BigInteger( "1" );
	static BigInteger m_two = new BigInteger( "2" );
	static BigInteger m_ten = new BigInteger( "10" );
	static BigInteger m_eleven = new BigInteger( "11" );

	String m_n;

	TextArea m_messageArea = null;
	TextField m_nTextField = null;
	TextField m_sTextField = null;

	public void init()
	{
		m_n = System.getProperty( "line.separator" );

		GridBagLayout gridBag = new GridBagLayout();
		setLayout( gridBag );

		GridBagConstraints c = new GridBagConstraints();

		c.gridwidth = GridBagConstraints.REMAINDER;
		c.fill = GridBagConstraints.BOTH;
		c.weightx = 1.0;
		c.weighty = 1.0;
		m_messageArea = new TextArea(
			"", 10, 50, TextArea.SCROLLBARS_VERTICAL_ONLY );
		m_messageArea.setEditable( false );
		gridBag.setConstraints( m_messageArea, c ); 
		add( m_messageArea );

		c.gridwidth = GridBagConstraints.REMAINDER;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 1.0;
		c.weighty = 0.0;
		m_nTextField = new TextField(
			"Enter integer N here and press ENTER.", 50 );
		m_nTextField.addActionListener( this );
		gridBag.setConstraints( m_nTextField, c ); 
		add( m_nTextField );

		c.gridwidth = GridBagConstraints.REMAINDER;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 1.0;
		c.weighty = 0.0;
		m_sTextField = new TextField(
			"Or enter palindrome P here and press ENTER.", 50 );
		m_sTextField.addActionListener( this );
		gridBag.setConstraints( m_sTextField, c ); 
		add( m_sTextField );

		displayMessage(
			"Enter any positive integer, N, in the first field below " +
			"and press ENTER to calculate the Nth palindrome." );
		displayMessage(
			"Or, enter any positive palindrome, P, in the second field below " +
			"and press ENTER to calculate the corresponding integer, N." );
		displayMessage(
			"For example, input 10 for N.  Press ENTER and you will see 11 " +
			"in the palindrome field because 11 is the 10th palindrome." );
		displayMessage(
			"Conversely, input 101 for P.  Press ENTER and you will see 19 " +
			"in the integer field because the 19th palindrome is 101." );
		displayMessage(
			"The larger the input, the longer the calculation time." );
	}

    public void actionPerformed( ActionEvent event )
	{
		TextField field = (TextField)event.getSource();
		String input = field.getText();
		BigInteger i = m_zero;

		if ( input.length() == 0 )
		{
			if ( field == m_nTextField )
				displayMessage(
					"Please enter a positive integer, N " +
					"and press ENTER." );
			else
				displayMessage(
					"Please enter a positive integer palindrome, P, " +
					"and press ENTER." );

			return;
		}

		try
		{
			i = new BigInteger( input );
		}
		catch ( NumberFormatException e )
		{
			displayMessage( "Please enter only numerical characters." );
			return;
		}

		// A way to strip off any leading zeros.
		input = i.toString();
		field.setText( input );

		if ( i.compareTo( m_zero ) <= 0 )
		{
			displayMessage( "Input must be positive." );
			return;
		}

		displayMessage( "Calculating..." );

		if ( field == m_nTextField )
		{
			BigInteger s = ntos( i );
			m_sTextField.setText( s.toString() );
		}
		else
		{
			// Make sure it's a palindrome.
			int j = input.length() / 2 - 1;
			int k = j + 1 + ( input.length() % 2 );
			for ( ; j >= 0; --j, ++k )
				if ( input.charAt( j ) != input.charAt( k ) )
				{
					displayMessage( "Please enter a palindrome." );
					return;
				}

			BigInteger n = ston( i );
			m_nTextField.setText( n.toString() );
		}

		displayMessage( "...done." );
	}

	public void displayMessage( String message )
	{
		m_messageArea.append( ":: " + message + m_n );
	}

	// num_digits( x ) == (BigInteger)floor( log10( x ) ).
	private int num_digits( BigInteger x )
	{
		int count = 0;

		// Don't modify input.
		BigInteger x_ = x;

		while ( x_.compareTo( m_ten ) >= 0 )
		{
			x_ = x_.divide( m_ten );
			++count;
		}

		return count;
	}

	// int_pow( x, y ) == (BigInteger)pow( x, y ).
	private BigInteger int_pow( BigInteger x, int y )
	{
		BigInteger result = m_one;

		// Don't modify input.
		int y_ = y;

		// FIXME - This can be optimized by breaking y down into binary.
		while ( y_ > 0 )
		{
			--y_;
			result = result.multiply( x );
		}

		return result;
	}

	// Convert integer, n, to palindrome, s.
	public BigInteger ntos( BigInteger n )
	{
		int logn = num_digits( n );

		int c =
			n.compareTo(
				m_eleven.
				multiply( int_pow( m_ten, logn - 1 ) ).
				subtract( m_one ) ) >= 0 &&
			n.compareTo(
				m_two.
				multiply( int_pow( m_ten, logn ) ).
				subtract( m_one ) ) < 0 ?
			1 : 0;

		BigInteger q = n;
		q = q.add( m_one );
		q = q.subtract( int_pow( m_ten, num_digits( n.add( m_one ).subtract(
			int_pow( m_ten, logn - 1 ) ) ) ) );

		int qdigits = num_digits( q );

		BigInteger s = q.mod( m_ten ).multiply(
			int_pow( m_eleven, c ).multiply( int_pow( m_ten, qdigits ) ) );

		for ( int k = 1; k <= qdigits; ++k )
			s = s.add( ( q.mod( int_pow( m_ten, k + 1 ) ).divide(
				int_pow( m_ten, k ) ) ).multiply(
					int_pow( m_ten, qdigits - k ) ).multiply(
						( int_pow( m_ten, 2 * k + c ).add( m_one ) ) ) );

		return s;
	}

	// Convert palindrome, s, to integer, n.
	public BigInteger ston( BigInteger s )
	{
		int temp1 = num_digits( s );

		int temp2 = 0;

		if ( ( temp1 % 2 ) == 1 )
		{
			++temp1;
			temp2 = 1;
		}

		temp1 /= 2;

		BigInteger q = s.divide( int_pow( m_ten, temp1 ) );

		BigInteger n = q;
		n = n.subtract( m_one );
		n = n.add( int_pow( m_ten, num_digits( q ) + temp2 ) );

		return n;
	}
}

