/* 
 * 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;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.text.TextUtils;

import de.avm.android.tr064.exceptions.BaseException;
import de.avm.android.tr064.exceptions.DataMisformatException;
import de.avm.android.tr064.net.GzipHttpRequestInterceptor;
import de.avm.android.tr064.net.GzipHttpResponseInterceptor;
import de.avm.android.tr064.sax.SAXDeviceConfigScpdHandler;
import de.avm.android.tr064.sax.SAXDeviceInfoScpdHandler;
import de.avm.android.tr064.sax.SAXLanConfigSecurityScpdHandler;
import de.avm.android.tr064.sax.SAXMyFritzScpdHandler;
import de.avm.android.tr064.sax.SAXOnTelScpdHandler;
import de.avm.android.tr064.sax.SAXTamScpdHandler;
import de.avm.android.tr064.sax.SAXTr064DescHandler;
import de.avm.android.tr064.sax.SAXVoIPConfScpdHandler;
import de.avm.android.tr064.sax.SAXWanPPPConnection;
import de.avm.android.tr064.sax.SAXWlanConfScpdHandler;

/**
 * Wrapper for jason_boxinfo.xml
 */
public class Tr064Boxinfo
{
	private static final String DEFAULT_SCHEME = "http";
	private static final int DEFAULT_PORT = 49000;
	private static final String DEFAULT_FILEPATH_TR64 = "/tr64desc.xml";
	
	private URI mUri = null;
	private String mLocalHostsAddress = null;
	private Tr064Capabilities mTr064Capabilities = new Tr064Capabilities();
	private String mUdn = "";
	private String mManufacturer = "";
	private String mModel = "";
	private String mFriendlyName = "";
	private String mWlanSignalRangeMin = "";
	private String mWlanSignalRangeMax = "";
	private int mWlanInterfacesCount = 0;

	private AbstractHttpClient mHttpClient = null;
	
	/**
	 * Creates instance from info retrieved from box
	 * @param uri URI of box' TR-064 description
	 * @param timeout timeout for HTTP requests (0 for default)
	 * @return URI to info file on host
	 * @throws URISyntaxException host is invalid
	 * @throws BaseException
	 * @throws DataMisformatException
	 */
	public static Tr064Boxinfo createInstance(URI uri, int timeout)
		throws  URISyntaxException, BaseException, DataMisformatException
	{
		return new Tr064Boxinfo(uri, timeout, Tr064Capabilities.ALL);
	}
	
	/**
	 * Creates instance from info retrieved from box
	 * @param uri URI of box' TR-064 description
	 * @param timeout timeout for HTTP requests (0 for default)
	 * @param capabilities check only for these capabilities
	 * (reduces SCDP loading/parsing)
	 * @return URI to info file on host
	 * @throws URISyntaxException host is invalid
	 * @throws BaseException
	 * @throws DataMisformatException
	 */
	public static Tr064Boxinfo createInstance(URI uri, int timeout,
			Tr064Capabilities capabilities)
		throws  URISyntaxException, BaseException, DataMisformatException
	{
		return new Tr064Boxinfo(uri, timeout, capabilities);
	}
	
	/**
	 * Creates default URI of box' TR-064 description for
	 * 'manual discovery'
	 * 
	 * @param host
	 * 		a host name or IP address
	 * @return
	 * 		the URI
	 * @throws IllegalArgumentException
	 * 		failed to create URI from host parameter
	 */
	public static URI createDefaultUri(String host)
	{
		try
		{
			return new URI(DEFAULT_SCHEME, "", host, DEFAULT_PORT,
					DEFAULT_FILEPATH_TR64, "", "");
		}
		catch (URISyntaxException e)
		{
			throw new IllegalArgumentException(e);
		}
	}

	/**
	 * Gets URI of loaded description with IP address in it
	 * 
	 * @return the URI
	 */
	public URI getUri()
	{
		return mUri;
	}
	
	/**
	 * Gets the IP address the loaded description has been requested from
	 * (my IP address)
	 * 
	 * @return the IP address
	 */
	public String getLocalHostsAddress()
	{
		return mLocalHostsAddress;
	}
	
	public Tr064Capabilities getTr064Capabilities()
	{
		return mTr064Capabilities;
	}

	public String getUdn()
	{
		return mUdn;
	}

	public String getManufacturer()
	{
		return mManufacturer;
	}

	public String getModel()
	{
		return mModel;
	}
	
	public String getFriendlyName()
	{
		return mFriendlyName;
	}

	public String getWlanSignalRangeMin()
	{
		return mWlanSignalRangeMin;
	}

	public String getWlanSignalRangeMax()
	{
		return mWlanSignalRangeMax;
	}
	
	public int getWlanInterfacesCount()
	{
		return mWlanInterfacesCount;
	}

	/**
	 * Creates instance from XML string
	 * @param tr64Uri URI of box' TR-064 description
	 * @param timeout timout for HTTP requests (0 for default)
	 * @param capabilities capabilities to check for
	 * @throws URISyntaxException host or an uri in a decription files is invalid
	 * @throws BaseException
	 * @throws FactoryConfigurationError Error creating XML parser
	 * @throws DataMisformatException
	 */
	private Tr064Boxinfo(final URI tr64Uri, int timeout, Tr064Capabilities capabilities)
		throws URISyntaxException, BaseException, DataMisformatException
	{
		try
		{
			if (timeout == 0)
			{
				mHttpClient = new DefaultHttpClient();
			}
			else
			{
				// default timeout is far too long for all those boxes not present yet
				HttpParams httpParams = new BasicHttpParams();
				HttpConnectionParams.setSoTimeout(httpParams, timeout);
				HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
				mHttpClient = new DefaultHttpClient(httpParams);
			}

			mHttpClient.addRequestInterceptor(new GzipHttpRequestInterceptor());
			mHttpClient.addResponseInterceptor(new GzipHttpResponseInterceptor());

			mHttpClient.addRequestInterceptor(new HttpRequestInterceptor()
			{
				public void process(HttpRequest request, HttpContext context)
						throws HttpException, IOException
				{
					if (mUri == null)
					{
						// get remote IP address
				        ManagedClientConnection conn = (ManagedClientConnection)context
		        				.getAttribute(ExecutionContext.HTTP_CONNECTION);
				        if (conn != null)
				        {
				        	try
				        	{
								mUri = new URI(tr64Uri.getScheme(),
										tr64Uri.getUserInfo(),
										conn.getRemoteAddress().getHostAddress(),
										tr64Uri.getPort(), tr64Uri.getPath(),
										tr64Uri.getQuery(), tr64Uri.getFragment());
							}
				        	catch (URISyntaxException e)
				        	{
							}
				        	mLocalHostsAddress = conn.getLocalAddress().getHostAddress(); 
				        }
					}
				}
			});
			
			SAXParserFactory factory = SAXParserFactory.newInstance();
			factory.setNamespaceAware(true);
			SAXParser parser = factory.newSAXParser();
			XMLReader reader = parser.getXMLReader();
			SAXTr064DescHandler tr64Handler = new SAXTr064DescHandler();
			reader.setContentHandler(tr64Handler);
			reader.parse(downloadAsInputSource(tr64Uri));
			
			mUdn = tr64Handler.getUdn(); 
			mManufacturer = tr64Handler.getManufacturer(); 
			mModel = tr64Handler.getModel();
			mFriendlyName = tr64Handler.getFriendlyName();
			
			// OnTel1
			if (!TextUtils.isEmpty(tr64Handler.getOnTelPath()))
			{
				try
				{
					SAXOnTelScpdHandler onTelHandler = new SAXOnTelScpdHandler();
					if (onTelHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(onTelHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getOnTelPath())));
						mTr064Capabilities.add(onTelHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// WLANConfiguration
			if (!TextUtils.isEmpty(tr64Handler.getWlanConfPath()))
			{
				try
				{
					SAXWlanConfScpdHandler wlanConfHandler = new SAXWlanConfScpdHandler(); 
					if (wlanConfHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(wlanConfHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getWlanConfPath())));
						mTr064Capabilities.add(wlanConfHandler.getCapabilities());
						mWlanSignalRangeMin = wlanConfHandler.getSignalRangeMin();
						mWlanSignalRangeMax = wlanConfHandler.getSignalRangeMax();
						mWlanInterfacesCount = tr64Handler.getWlanInterfacesCount();
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// VoIP1
			if (!TextUtils.isEmpty(tr64Handler.getVoIPPath()))
			{
				try
				{
					SAXVoIPConfScpdHandler voipConfHandler = new SAXVoIPConfScpdHandler(); 
					if (voipConfHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(voipConfHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getVoIPPath())));
						mTr064Capabilities.add(voipConfHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// TAM1
			if (!TextUtils.isEmpty(tr64Handler.getTamPath()))
			{
				try
				{
					SAXTamScpdHandler tamHandler = new SAXTamScpdHandler(); 
					if (tamHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(tamHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getTamPath())));
						mTr064Capabilities.add(tamHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// DeviceInfo1
			if (!TextUtils.isEmpty(tr64Handler.getDeviceInfoPath()))
			{
				try
				{
					SAXDeviceInfoScpdHandler deviceInfoHandler = new SAXDeviceInfoScpdHandler(); 
					if (deviceInfoHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(deviceInfoHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getDeviceInfoPath())));
						mTr064Capabilities.add(deviceInfoHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// DeviceConfig1
			if (!TextUtils.isEmpty(tr64Handler.getDeviceConfigPath()))
			{
				try
				{
					SAXDeviceConfigScpdHandler deviceConfigHandler = new SAXDeviceConfigScpdHandler(); 
					if (deviceConfigHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(deviceConfigHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getDeviceConfigPath())));
						mTr064Capabilities.add(deviceConfigHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// WANPPPConnection1
			if (!TextUtils.isEmpty(tr64Handler.getWanPPPConnectionPath()))
			{
				try
				{
					SAXWanPPPConnection wanPPPConnectionHandler = new SAXWanPPPConnection();
					if (wanPPPConnectionHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(wanPPPConnectionHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getWanPPPConnectionPath())));
						mTr064Capabilities.add(wanPPPConnectionHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// MyFritz
			if (!TextUtils.isEmpty(tr64Handler.getMyFritzPath()))
			{
				try
				{
					SAXMyFritzScpdHandler myFritzHandler = new SAXMyFritzScpdHandler();
					if (myFritzHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(myFritzHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getMyFritzPath())));
						mTr064Capabilities.add(myFritzHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
			
			// LANConfigSecurity1
			if (!TextUtils.isEmpty(tr64Handler.getLanConfigSecurityPath()))
			{
				try
				{
					SAXLanConfigSecurityScpdHandler lanConfSecHandler = new SAXLanConfigSecurityScpdHandler();
					if (lanConfSecHandler.isCapabilitiyOfInterface(capabilities))
					{
						reader = parser.getXMLReader();
						reader.setContentHandler(lanConfSecHandler);
						reader.parse(downloadAsInputSource(tr64Uri
								.resolve(tr64Handler.getLanConfigSecurityPath())));
						mTr064Capabilities.add(lanConfSecHandler.getCapabilities());
					}
				}
				catch(Exception exp)
				{
					exp.printStackTrace();
				}
			}
		}
		catch (FactoryConfigurationError exp)
		{
			throw new BaseException("Unhandled configuration Exception", exp);
		}
		catch (ParserConfigurationException exp)
		{
			throw new BaseException("Unhandled configuration Exception", exp);
		}
		catch (SAXException exp)
		{
			throw new DataMisformatException("Invalid Interface Description Data", exp);
		}
		catch (IOException exp)
		{
			throw new DataMisformatException("Invalid Interface Description Data", exp);
		}
		finally
		{
			mHttpClient = null;
		}
	}
	
	private InputSource downloadAsInputSource(URI uri)
			throws IOException
	{
		String content = "";
		if (mHttpClient != null)
		{
			HttpResponse httpResponse = mHttpClient.execute(new HttpGet(uri));
			if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
				content = EntityUtils.toString(httpResponse.getEntity());
		}
		return new InputSource(new StringReader(content));
	}
}
