/*
 * ====================================================================
 * ======== The Apache Software License, Version 1.1
 * ==================
 * ==========================================================
 * Copyright (C) 2002 The Apache Software Foundation. All rights
 * reserved. Redistribution and use in source and binary forms, with
 * or without modifica- tion, are permitted provided that the
 * following conditions are met: 1. Redistributions of source code
 * must retain the above copyright notice, this list of conditions and
 * the following disclaimer. 2. Redistributions in binary form must
 * reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other
 * materials provided with the distribution. 3. The end-user
 * documentation included with the redistribution, if any, must
 * include the following acknowledgment: "This product includes
 * software developed by SuperBonBon Industries
 * (http://www.sbbi.net/)." Alternately, this acknowledgment may
 * appear in the software itself, if and wherever such third-party
 * acknowledgments normally appear. 4. The names "UPNPLib" and
 * "SuperBonBon Industries" must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@sbbi.net.
 * 5. Products derived from this software may not be called
 * "SuperBonBon Industries", nor may "SBBI" appear in their name,
 * without prior written permission of SuperBonBon Industries. THIS
 * SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE. This software consists of voluntary contributions made
 * by many individuals on behalf of SuperBonBon Industries. For more
 * information on SuperBonBon Industries, please see
 * <http://www.sbbi.net/>.
 */

package net.tomp2p.upnp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;


import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This class is used to create state variable messages to communicate
 * with the device
 * 
 * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a>
 * @version 1.0
 */
public class StateVariableMessage
{
	private Service service;

	private StateVariable serviceStateVar;

	StateVariableMessage( Service service, StateVariable serviceStateVar )
	{
		this.service = service;
		this.serviceStateVar = serviceStateVar;
	}

	/**
	 * Executes the state variable query and returns the UPNP device
	 * response, according to the UPNP specs, this method could take up
	 * to 30 secs to process ( time allowed for a device to respond to
	 * a request )
	 * 
	 * @return a state variable response object containing the variable
	 *         value
	 * @throws IOException
	 *            if some IOException occurs during message send and
	 *            reception process
	 * @throws UPNPResponseException
	 *            if an UPNP error message is returned from the server
	 *            or if some parsing exception occurs ( detailErrorCode
	 *            = 899, detailErrorDescription = SAXException message
	 *            )
	 */
	public StateVariableResponse service() throws IOException, UPNPResponseException
	{
		StateVariableResponse rtrVal = null;
		UPNPResponseException upnpEx = null;
		IOException ioEx = null;
		StringBuilder body = new StringBuilder( 256 );

		body.append( "<?xml version=\"1.0\"?>\r\n" );
		body.append( "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" );
		body.append( " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" );
		body.append( "<s:Body>" );
		body.append( "<u:QueryStateVariable xmlns:u=\"urn:schemas-upnp-org:control-1-0\">" );
		body.append( "<u:varName>" ).append( serviceStateVar.name ).append( "</u:varName>" );
		body.append( "</u:QueryStateVariable>" );
		body.append( "</s:Body>" );
		body.append( "</s:Envelope>" );

		HttpURLConnection conn = ( HttpURLConnection ) service.controlURL.openConnection();
		conn.setDoInput( true );
		conn.setDoOutput( true );
		conn.setUseCaches( false );
		conn.setRequestMethod( "POST" );
		HttpURLConnection.setFollowRedirects( false );
		// conn.setConnectTimeout( 30000 );
		conn.setRequestProperty( "HOST", service.controlURL.getHost() + ":"
				+ service.controlURL.getPort() );
		conn.setRequestProperty( "SOAPACTION",
				"\"urn:schemas-upnp-org:control-1-0#QueryStateVariable\"" );
		conn.setRequestProperty( "CONTENT-TYPE", "text/xml; charset=\"utf-8\"" );
		conn.setRequestProperty( "CONTENT-LENGTH", Integer.toString( body.length() ) );
		OutputStream out = conn.getOutputStream();
		out.write( body.toString().getBytes() );
		out.flush();
		conn.connect();
		InputStream input = null;

		try
		{
			input = conn.getInputStream();
		}
		catch( IOException ex )
		{
			// java can throw an exception if he error code is 500 or 404
			// or something else than 200 but the device sends 500 error
			// message with content that is required this content is
			// accessible with the getErrorStream
			input = conn.getErrorStream();
		}

		if( input != null )
		{
			int response = conn.getResponseCode();
			String responseBody = getResponseBody( input );

			SAXParserFactory saxParFact = SAXParserFactory.newInstance();
			saxParFact.setValidating( false );
			saxParFact.setNamespaceAware( true );
			StateVariableResponseParser msgParser = new StateVariableResponseParser( serviceStateVar );
			StringReader stringReader = new StringReader( responseBody );
			InputSource src = new InputSource( stringReader );
			try
			{
				SAXParser parser = saxParFact.newSAXParser();
				parser.parse( src, msgParser );
			}
			catch( ParserConfigurationException confEx )
			{
				// should never happen
				// we throw a runtimeException to notify the env problem
				throw new RuntimeException(
						"ParserConfigurationException during SAX parser creation, please check your env settings:"
								+ confEx.getMessage() );
			}
			catch( SAXException saxEx )
			{
				// kind of tricky but better than nothing..
				upnpEx = new UPNPResponseException( 899, saxEx.getMessage() );
			}
			finally
			{
				try
				{
					input.close();
				}
				catch( IOException ex )
				{
					// ignoring
				}
			}
			if( upnpEx == null )
			{
				if( response == HttpURLConnection.HTTP_OK )
				{
					rtrVal = msgParser.getStateVariableResponse();
				}
				else if( response == HttpURLConnection.HTTP_INTERNAL_ERROR )
				{
					upnpEx = msgParser.getUPNPResponseException();
				}
				else
				{
					ioEx = new IOException( "Unexpected server HTTP response:" + response );
				}
			}
		}
		try
		{
			out.close();
		}
		catch( IOException ex )
		{
			// ignore
		}
		conn.disconnect();
		if( upnpEx != null )
		{
			throw upnpEx;
		}
		if( rtrVal == null && ioEx == null )
		{
			ioEx = new IOException( "Unable to receive a response from the UPNP device" );
		}
		if( ioEx != null )
		{
			throw ioEx;
		}
		return rtrVal;
	}

	private String getResponseBody( InputStream in ) throws IOException
	{
		byte[] buffer = new byte[ 256 ];
		int readen = 0;
		StringBuilder content = new StringBuilder( 256 );
		while( ( readen = in.read( buffer ) ) != -1 )
		{
			content.append( new String( buffer, 0, readen ) );
		}
		// some devices add \0 chars at XML message end
		// which causes XML parsing errors...
		int len = content.length();
		while( content.charAt( len - 1 ) == '\0' )
		{
			len--;
			content.setLength( len );
		}
		return content.toString().trim();
	}
}
