/* 
 * Copyright 2012 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.tr064.soap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.UnknownHostException;

import javax.net.ssl.SSLException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import android.text.TextUtils;
import android.util.Log;
import de.avm.android.tr064.exceptions.BaseException;
import de.avm.android.tr064.exceptions.SoapException;
import de.avm.android.tr064.exceptions.SslErrorException;
import de.avm.android.tr064.net.GzipHttpRequestInterceptor;
import de.avm.android.tr064.net.GzipHttpResponseInterceptor;
import de.avm.android.tr064.net.SoapSSLClientFactory;

/* Lightweight helper class for SOAP communication.*/

public abstract class AbstractSoapHelper<RESULT>
{
	private static final String TAG = "AbstractSoapHelper";
	
	protected final ISoapCredentials mSoapCredentials;

	/**
	 * Instantiates a new soap helper.
	 * 
	 * @param c
	 *            a valid context
	 */
	public AbstractSoapHelper(ISoapCredentials soapCredentials)
	{
		mSoapCredentials = soapCredentials;
	}

	public abstract String getSoapMethod();

	public abstract RESULT getQualifiedResult()
			throws IOException, BaseException;

	public abstract String getNamespace();

	public abstract String getControlURL();

	public String getSoapMethodParameter()
	{
		return "";
	}

	/**
	 * Fetches the soap body using SSL 
	 * 
	 * @return the soap body
	 * @throws IOException thrown only after
	 * 		setHandleConnectionProblem(false) has been called
	 * @throws BaseException
	 */
	protected String getSoapBody()
			throws IOException, BaseException
	{
		return getSoapBody(true);
	}

	/**
	 * Fetches the soap body 
	 * 
	 * @param useSsl use SSL or not
	 * @return the soap body
	 * @throws IOException thrown only after
	 * 		setHandleConnectionProblem(false) has been called
	 * @throws BaseException
	 */
	protected String getSoapBody(boolean useSsl)
			throws IOException, BaseException
	{
		// no SSL for testing?
		if (useSsl && mSoapCredentials.isSuppressSSL()) useSsl = false;
		
		DefaultHttpClient client = null;
		String ret = "";
		try
		{
			String host = mSoapCredentials.getHost();
			int port = mSoapCredentials.getPort(useSsl);
			if ((port < 1) || TextUtils.isEmpty(host))
				throw new UnknownHostException();

			client = SoapSSLClientFactory.getClientWithDigestAuth(port,
					mSoapCredentials.getUsername(), mSoapCredentials.getPassword(),
					mSoapCredentials.getTrustedServerCertificates());
			client.addRequestInterceptor(new GzipHttpRequestInterceptor());
			client.addResponseInterceptor(new GzipHttpResponseInterceptor());
			
			String soapEndpoint = ((useSsl) ? "https://" : "http://") +
					host + ":" + Integer.toString(port) + getControlURL();
			String requestBody = createRequestBody();
			String soapAction = getNamespace() + "#" + getSoapMethod();
			Log.v("SOAP-Endpoint", soapEndpoint);
			Log.v("SOAP-Action", soapAction);
			Log.v("RequestBody", filterSoapBeforeTrace(requestBody));

			HttpPost post = new HttpPost(soapEndpoint);
			post.setEntity(new StringEntity(requestBody, "utf-8"));
			post.addHeader("User-Agent", "AVM FRITZ!App");
			post.addHeader("SOAPACTION", soapAction);
			post.addHeader("Content-Type", "text/xml; charset=\"utf-8\"");
			post.addHeader("Accept", "text/xml");
			post.addHeader("Connection", "close");

			HttpResponse resp = client.execute(post);
			if (resp.getStatusLine().getStatusCode() ==
					HttpStatus.SC_INTERNAL_SERVER_ERROR)
			{
				SoapException exp = SoapException.create(resp.getEntity());
				if (exp != null)
				{
					Log.v("REPLY", resp.getStatusLine().toString() +
							", SOAP error " + exp.getSoapError() +
							": " + exp.getMessage());
					throw exp;
				}
			}
			if (resp.getStatusLine().getStatusCode() >= 300)
			{
				Log.v("REPLY", resp.getStatusLine().toString());
				throw new HttpResponseException(
						resp.getStatusLine().getStatusCode(),
						resp.getStatusLine().getReasonPhrase());
			}	
			InputStream input = resp.getEntity().getContent();
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					input));
			String str;
			while ((str = reader.readLine()) != null) {
				ret += str;
				Log.v("REPLY", str);
			}
		}
		catch (ClientProtocolException e)
		{
			throw new BaseException("Invalid Response from FRITZ!Box", e);
		}
		catch (SSLException e)
		{
			throw new SslErrorException(e);
		}
		catch (IOException e)
		{
			if (SslErrorException.isSslError(e))
				throw new SslErrorException(e);
			else
				throw e;
		}
		return ret;
	}

	/**
	 * Create the soap request body.
	 * 
	 * @return the string
	 */
	private String createRequestBody()
	{
		String body = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
				+ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
				+ "<s:Body>"
				+ "<u:"
				+ getSoapMethod()
				+ " xmlns:u=\""
				+ getNamespace()
				+ "\">"
				+ getSoapMethodParameter()
				+ "</u:"
				+ getSoapMethod() + ">" + "</s:Body></s:Envelope>";
		return body;
	}
	
	protected static String decodeEntities(String str)
	{
        return str.replace("&lt;", "<")
        		.replace("&apos;", "'")
        		.replace("&quot;", "\"")
        		.replace("&gt;", ">")
        		.replace("&amp;", "&");
	}
	
	/**
	 * Apply a workaround to download URLs if needed
	 * 
	 * @param url the URL
	 * @return the may-be-fixed-URL
	 */
	protected String fixDownloadUrl(String url)
	{
		try
		{
			// should it be applied?
			if (mSoapCredentials.shouldFixDownloadUrl())
			{
				String host = mSoapCredentials.getHost();
				if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(url))
				{
					URI uri = new URI(url);
					if (!host.equalsIgnoreCase(uri.getHost()))
					{
						String result =  new URI(uri.getScheme(),
								uri.getUserInfo(), host, uri.getPort(),
								uri.getPath(), uri.getQuery(),
								uri.getFragment()).toString();
						Log.d(TAG, "fixed download url: " + result);
						return result;
					}
				}
			}
		}
		catch(Exception e)
		{
			Log.e(TAG, "Failed to fix download URL. Might be wrong!", e);
		}
		
		return url;
	}
	
	/**
	 * Filter SOAP body before dumping it it to the trace,
	 * remove/replace sensitive data here
	 * 
	 * @param body
	 * 		body to be sent
	 * @return
	 * 		trace output created from body
	 */
	protected String filterSoapBeforeTrace(String body)
	{
		// don't filter by default
		return body;
	}
}