package org.bitseal.network;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import org.bitseal.core.App;
import org.bitseal.data.ServerRecord;
import org.bitseal.database.ServerRecordProvider;
import android.util.Log;
import de.timroes.axmlrpc.XMLRPCClient;
import de.timroes.axmlrpc.XMLRPCException;
/**
 * An object which uses the XMLRPC client class to connect to servers running
 * PyBitmessage and call methods from the PyBitmessage API.
 * 
 * @author Jonathan Coe
 */
public class ApiCaller
{
	private URL url;
	private String username;
	private String password;
	
	private XMLRPCClient newClient;
	private XMLRPCClient client;
	
	private ArrayList<URL> urlList;
	private ArrayList<String> usernameList;
	private ArrayList<String> passwordList;
	
	private int urlCounter;
	private int usernameCounter;
	private int passwordCounter;
	
	private int numberOfServers;
	
	/**
	 * This constant defines the timeout period for API calls.
	 */
	private static final int TIMEOUT_SECONDS = 10;
	
	/**
	 * API command used for connection testing
	 */
	private static final String API_METHOD_ADD = "add";
	
	private static final String TAG = "API_CALLER";
	
	/**
	 * Creates a new ApiCaller object and sets the URL, username, and password values needed
	 * to connect to the PyBitmessage servers.
	 */
	public ApiCaller()
	{	
		// Check if any server records exist in app storage. If not, set up the default list of server records. 
		ServerRecordProvider servProv = ServerRecordProvider.get(App.getContext());
		ArrayList<ServerRecord> retrievedServerRecords = servProv.getAllServerRecords();
		if (retrievedServerRecords.size() == 0)
		{
			Log.i(TAG, "No server records found in app storage. Setting up list of default servers.");
			ServerHelper servHelp = new ServerHelper();
			servHelp.setupDefaultServers();
			// Now the server records should be available from the database
			retrievedServerRecords = servProv.getAllServerRecords();
		}
		numberOfServers = retrievedServerRecords.size();
				
        // Set up ArrayLists for the URLs, usernames, and passwords of the servers
    	urlList = new ArrayList<URL>();
		usernameList = new ArrayList<String>();
		passwordList = new ArrayList<String>();
		// Randomize the order of the server records list in order to avoid servers always being called in 
		// the same order. 
		Collections.shuffle(retrievedServerRecords, new SecureRandom());
		
		int arrayListIndex = 0;
		for(ServerRecord s : retrievedServerRecords)
		{
			try
			{
				urlList.add(arrayListIndex, new URL(s.getURL()));
				usernameList.add(arrayListIndex, s.getUsername());
				passwordList.add(arrayListIndex, s.getPassword());
				
				arrayListIndex ++;
			}
			catch (MalformedURLException e)
			{
				Log.e(TAG, "Malformed URL exception occurred in ApiCaller constructor. We will ignore the ServerRecord that contains this " +
						"url. The String representation of the url was " + s.getURL());
				arrayListIndex ++;
			}
		}
        
		// Start at the beginning of each of the three lists
		urlCounter = 0;
		usernameCounter = 0;
		passwordCounter = 0;
		
		url = urlList.get(urlCounter);
		username = usernameList.get(usernameCounter);
		password = passwordList.get(passwordCounter);
		
		client = setUpClient(url, username, password);
		
		Log.i(TAG, "ApiCaller setup completed");
	}
		
	/**
     * Makes a call to the PyBitmessage XMLRPC API. <br><br>
     * 
     * Attempts to establish a connection to one of the listed servers. The method will attempt to
	 * connect to each server in sequence, until either a connection is successfully established or
	 * all servers have been tested without any successful connection. If a connection is successfully
	 * established, then the API call will be made.
     *
     * @param method - A String which specifies the API method to be called
     * @param params - One or more Objects which provide the parameters for the API call
     * 
     * @return An Object containing the result of the API call
     */  
	public Object call(String method, Object... params)
	{				
		while (urlCounter < urlList.size())
		{
			boolean connectionSuccessful = doConnectionTest();
			
			if (connectionSuccessful)
			{
				Log.i(TAG, "Successfully connected to " + url.toString());
				
				try
				{	
					Log.i(TAG, "About to make an API call to " + url.toString());
					
					Object result = client.call(method, params);
					
					return result;
				}
				
				catch (XMLRPCException e)
				{
					Log.e(TAG, "XMLRPCException occurred in ApiCaller.call() \n" + 
							"Execption message was: " + e.getMessage());
					switchToNextServer();
				}
				catch (IllegalStateException e)
				{
					Log.e(TAG, "IllegalStateException occurred in ApiCaller.call() \n" + 
							"Execption message was: " + e.getMessage());
					switchToNextServer();
				}
				catch (Exception e)
				{
					Log.e(TAG, "An Exception occurred in ApiCaller.call() \n" + 
							"Execption message was: " + e.getMessage());
					switchToNextServer();
				}
			}
			else
			{
				switchToNextServer();
			}
		}
		throw new RuntimeException("API call failed after trying all listed servers. Last attempted URL was " + url.toString());
	}
	
	/**
	 * Sets up the XMLRPC client to use the next server in the list. If the end
	 * of the list has been reached, throws a RuntimeException. 
	 */
	public void switchToNextServer()
	{
		if (urlCounter < (urlList.size() - 1))
		{
			Log.i(TAG, "Currently the URL in use is " + url.toString() + ", about to change to next URL");
			
			urlCounter ++;
			usernameCounter ++;
			passwordCounter ++;
			
			url = urlList.get(urlCounter);
			username = usernameList.get(usernameCounter);
			password = passwordList.get(passwordCounter);
			
			client = setUpClient(url, username, password);
		}
		else
		{
			throw new RuntimeException("API call failed after trying all listed servers. Last attempted URL was " + url.toString());
		}
	}
	
	/**
	 * Returns the number of servers in use. 
	 */
	public int getNumberOfServers()
	{
		return numberOfServers;
	}
		
	/**
     * Performs a connection test by calling the "add" method from the PyBitmessage API and
     * checking if the returned result (if any) is correct. 
     *  
     * @return A boolean indicating whether or not a connection was successfully established
     */
    private boolean doConnectionTest() 
    {   	
    	Object rawResult = null;
    	
    	try 
		{
			Log.i(TAG, "Running doConnectionTest() with server at " + url.toString());
			
			int result = -1; // Explicitly set this value to ensure a meaningful test. The testInt values will always be >=0, so the test should never give a false positive result.
			
			Random rand = new Random();
			int testInt1 = rand.nextInt(5000);


Answer: 			int testInt2 = rand.nextInt(5000);