/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI 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 GNU Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wsdl;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.CredentialsConfig;
import com.eviware.soapui.config.WsaVersionTypeConfig;
import com.eviware.soapui.config.WsdlRequestConfig;
import com.eviware.soapui.config.WsrmVersionTypeConfig;
import com.eviware.soapui.impl.rest.RestRequestInterface;
import com.eviware.soapui.impl.support.AbstractHttpRequest;
import com.eviware.soapui.impl.wsdl.submit.RequestTransportRegistry;
import com.eviware.soapui.impl.wsdl.submit.transports.http.WsdlResponse;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.attachments.AttachmentUtils;
import com.eviware.soapui.impl.wsdl.submit.transports.jms.JMSHeader;
import com.eviware.soapui.impl.wsdl.support.jms.header.JMSHeaderConfig;
import com.eviware.soapui.impl.wsdl.support.jms.property.JMSPropertiesConfig;
import com.eviware.soapui.impl.wsdl.support.wsa.WsaConfig;
import com.eviware.soapui.impl.wsdl.support.wsa.WsaContainer;
import com.eviware.soapui.impl.wsdl.support.wsrm.WsrmConfig;
import com.eviware.soapui.impl.wsdl.support.wsrm.WsrmContainer;
import com.eviware.soapui.impl.wsdl.teststeps.HttpTestRequestStep;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.iface.MessagePart;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContainer;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionsResult;
import com.eviware.soapui.model.support.InterfaceListenerAdapter;
import com.eviware.soapui.settings.WsdlSettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.types.StringToStringsMap;

/**
 * Request implementation holding a SOAP request
 * 
 * @author Ole.Matzura
 */

public class WsdlRequest extends AbstractHttpRequest<WsdlRequestConfig> implements WsdlAttachmentContainer,
		PropertyExpansionContainer, WsaContainer, WsrmContainer, PropertyChangeListener
{
	public final static Logger log = Logger.getLogger( WsdlRequest.class );

	public static final String RESPONSE_CONTENT_PROPERTY = WsdlRequest.class.getName() + "@response-content";
	public static final String INLINE_RESPONSE_ATTACHMENTS = WsdlRequest.class.getName()
			+ "@inline-response-attachments";
	public static final String EXPAND_MTOM_RESPONSE_ATTACHMENTS = WsdlRequest.class.getName()
			+ "@expand-mtom-attachments";
	public static final String FORCE_MTOM = WsdlRequest.class.getName() + "@force_mtom";
	public static final String ENABLE_INLINE_FILES = WsdlRequest.class.getName() + "@enable_inline_files";
	public static final String SKIP_SOAP_ACTION = WsdlRequest.class.getName() + "@skip_soap_action";
	public static final String ENCODE_ATTACHMENTS = WsdlRequest.class.getName() + "@encode_attachments";
	public static final String WSS_TIMETOLIVE = WsdlRequest.class.getName() + "@wss-time-to-live";
	public static final String OPERATION_PROPERTY = WsdlRequest.class.getName() + "@operation";
	public static final String INCOMING_WSS = WsdlRequest.class.getName() + "@incoming-wss";
	public static final String OUGOING_WSS = WsdlRequest.class.getName() + "@outgoing-wss";

	public final static String PW_TYPE_NONE = "None";
	public final static String PW_TYPE_DIGEST = "PasswordDigest";
	public final static String PW_TYPE_TEXT = "PasswordText";

	private WsdlOperation operation;
	private List<HttpAttachmentPart> definedAttachmentParts;
	private InternalInterfaceListener interfaceListener = new InternalInterfaceListener();

	private WsaConfig wsaConfig;
	private WsrmConfig wsrmConfig;

	public WsdlRequest( WsdlOperation operation, WsdlRequestConfig callConfig )
	{
		this( operation, callConfig, false );
	}

	public WsdlRequest( WsdlOperation operation, WsdlRequestConfig callConfig, boolean forLoadTest )
	{
		super( callConfig, operation, null, forLoadTest );

		this.operation = operation;

		initEndpoints();

		// ensure encoding
		if( callConfig.getEncoding() == null || callConfig.getEncoding().length() == 0 )
		{
			callConfig.setEncoding( "UTF-8" );
		}

		if( !forLoadTest )
		{
			operation.getInterface().addPropertyChangeListener( interfaceListener );
			operation.getInterface().addInterfaceListener( interfaceListener );
		}
	}

	public void updateConfig( WsdlRequestConfig request )
	{
		setConfig( request );

		if( wsaConfig != null )
		{
			wsaConfig.setConfig( request.getWsaConfig() );
		}

		if( wsrmConfig != null )
		{
			wsrmConfig.setWsrmConfig( request.getWsrmConfig() );
		}

		if( jmsHeaderConfig != null )
		{
			jmsHeaderConfig.setJMSHeaderConfConfig( request.getJmsConfig() );
		}

		if( jmsPropertyConfig != null )
		{
			jmsPropertyConfig.setJmsPropertyConfConfig( request.getJmsPropertyConfig() );
		}
	}

	protected void initEndpoints()
	{
		if( getEndpoint() == null )
		{
			String[] endpoints = operation.getInterface().getEndpoints();
			if( endpoints.length > 0 )
			{
				setEndpoint( endpoints[0] );
			}
		}
	}

	public boolean isInlineResponseAttachments()
	{
		return getSettings().getBoolean( INLINE_RESPONSE_ATTACHMENTS );
	}

	public void setInlineResponseAttachments( boolean inlineResponseAttachments )
	{
		boolean old = getSettings().getBoolean( INLINE_RESPONSE_ATTACHMENTS );
		getSettings().setBoolean( INLINE_RESPONSE_ATTACHMENTS, inlineResponseAttachments );
		notifyPropertyChanged( INLINE_RESPONSE_ATTACHMENTS, old, inlineResponseAttachments );
	}

	public boolean isExpandMtomResponseAttachments()
	{
		return getSettings().getBoolean( EXPAND_MTOM_RESPONSE_ATTACHMENTS );
	}

	public void setExpandMtomResponseAttachments( boolean expandMtomResponseAttachments )
	{
		boolean old = getSettings().getBoolean( EXPAND_MTOM_RESPONSE_ATTACHMENTS );
		getSettings().setBoolean( EXPAND_MTOM_RESPONSE_ATTACHMENTS, expandMtomResponseAttachments );
		notifyPropertyChanged( EXPAND_MTOM_RESPONSE_ATTACHMENTS, old, expandMtomResponseAttachments );
	}

	/**
	 * Use getResponse().getContentAsString();
	 * 
	 * @deprecated
	 */

	@Deprecated
	public String getResponseContent()
	{
		return getResponse() == null ? null : getResponse().getContentAsString();
	}

	public WsdlResponse getResponse()
	{
		return ( WsdlResponse )super.getResponse();
	}

	public WsdlOperation getOperation()
	{
		return operation;
	}

	public void setOperation( WsdlOperation wsdlOperation )
	{
		WsdlOperation oldOperation = operation;
		this.operation = wsdlOperation;

		definedAttachmentParts = null;
		notifyPropertyChanged( OPERATION_PROPERTY, oldOperation, operation );
	}

	public void setRequestContent( String request )
	{
		definedAttachmentParts = null;
		super.setRequestContent( request );
	}

	// public void setResponse( WsdlResponse response, SubmitContext context )
	// {
	// WsdlResponse oldResponse = getResponse();
	// this.response = response;
	//
	// notifyPropertyChanged( RESPONSE_PROPERTY, oldResponse, response );
	// }

	public WsdlSubmit<WsdlRequest> submit( SubmitContext submitContext, boolean async ) throws SubmitException
	{
		String endpoint = PropertyExpander.expandProperties( submitContext, getEndpoint() );
		if( endpoint == null || endpoint.trim().length() == 0 )
		{
			UISupport.showErrorMessage( "Missing endpoint for request [" + getName() + "]" );
			return null;
		}

		try
		{
			WsdlSubmit<WsdlRequest> submitter = new WsdlSubmit<WsdlRequest>( this, getSubmitListeners(),
					RequestTransportRegistry.getTransport( endpoint, submitContext ) );
			submitter.submitRequest( submitContext, async );
			return submitter;
		}
		catch( Exception e )
		{
			throw new SubmitException( e.toString() );
		}
	}

	private class InternalInterfaceListener extends InterfaceListenerAdapter implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			if( evt.getPropertyName().equals( Interface.ENDPOINT_PROPERTY ) )
			{
				String endpoint = getEndpoint();
				if( evt.getOldValue() != null && evt.getOldValue().equals( endpoint ) )
				{
					setEndpoint( ( String )evt.getNewValue() );
				}
			}
		}
	}

	public String getWssPasswordType()
	{
		String wssPasswordType = getConfig().getWssPasswordType();
		return StringUtils.isNullOrEmpty( wssPasswordType ) || PW_TYPE_NONE.equals( wssPasswordType ) ? null
				: wssPasswordType;
	}

	public void setWssPasswordType( String wssPasswordType )
	{
		if( wssPasswordType == null || wssPasswordType.equals( PW_TYPE_NONE ) )
		{
			if( getConfig().isSetWssPasswordType() )
				getConfig().unsetWssPasswordType();
		}
		else
		{
			getConfig().setWssPasswordType( wssPasswordType );
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.eviware.soapui.impl.wsdl.AttachmentContainer#getDefinedAttachmentParts
	 * ()
	 */

	public synchronized HttpAttachmentPart[] getDefinedAttachmentParts()
	{
		if( definedAttachmentParts == null )
		{
			try
			{
				UISupport.setHourglassCursor();
				definedAttachmentParts = AttachmentUtils.extractAttachmentParts( operation, getRequestContent(), true,
						false, isMtomEnabled() );
			}
			catch( Exception e )
			{
				log.warn( e.toString() );
				definedAttachmentParts = new ArrayList<HttpAttachmentPart>();
			}
			finally
			{
				UISupport.resetCursor();
			}
		}

		return definedAttachmentParts.toArray( new HttpAttachmentPart[definedAttachmentParts.size()] );
	}

	public RestRequestInterface.RequestMethod getMethod()
	{
		return RestRequestInterface.RequestMethod.POST;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.eviware.soapui.impl.wsdl.AttachmentContainer#getAttachmentPart(java
	 * .lang.String)
	 */
	public HttpAttachmentPart getAttachmentPart( String partName )
	{
		HttpAttachmentPart[] parts = getDefinedAttachmentParts();
		for( HttpAttachmentPart part : parts )
		{
			if( part.getName().equals( partName ) )
				return part;
		}

		return null;
	}

	public void copyTo( WsdlRequest newRequest, boolean copyAttachments, boolean copyHeaders )
	{
		newRequest.setEncoding( getEncoding() );
		newRequest.setEndpoint( getEndpoint() );
		newRequest.setRequestContent( getRequestContent() );
		newRequest.setWssPasswordType( getWssPasswordType() );

		CredentialsConfig credentials = getConfig().getCredentials();
		if( credentials != null )
			newRequest.getConfig().setCredentials( ( CredentialsConfig )credentials.copy() );

		if( copyAttachments )
			copyAttachmentsTo( newRequest );

		if( copyHeaders )
			newRequest.setRequestHeaders( getRequestHeaders() );

		// ((DefaultWssContainer)newRequest.getWssContainer()).updateConfig( (
		// WSSConfigConfig ) getConfig().getWssConfig().copy() );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.eviware.soapui.impl.wsdl.AttachmentContainer#isMtomEnabled()
	 */
	public boolean isMtomEnabled()
	{
		return getSettings().getBoolean( WsdlSettings.ENABLE_MTOM );
	}

	public void setMtomEnabled( boolean mtomEnabled )
	{
		getSettings().setBoolean( WsdlSettings.ENABLE_MTOM, mtomEnabled );
		definedAttachmentParts = null;
	}

	public boolean isInlineFilesEnabled()
	{
		return getSettings().getBoolean( WsdlRequest.ENABLE_INLINE_FILES );
	}

	public void setInlineFilesEnabled( boolean inlineFilesEnabled )
	{
		getSettings().setBoolean( WsdlRequest.ENABLE_INLINE_FILES, inlineFilesEnabled );
	}

	public boolean isSkipSoapAction()
	{
		return getSettings().getBoolean( WsdlRequest.SKIP_SOAP_ACTION );
	}

	public void setSkipSoapAction( boolean skipSoapAction )
	{
		getSettings().setBoolean( WsdlRequest.SKIP_SOAP_ACTION, skipSoapAction );
	}

	@Override
	public void release()
	{
		super.release();

		getOperation().getInterface().removeInterfaceListener( interfaceListener );
		getOperation().getInterface().removePropertyChangeListener( interfaceListener );
	}

	public MessagePart[] getRequestParts()
	{
		try
		{
			List<MessagePart> result = new ArrayList<MessagePart>();
			result.addAll( Arrays.asList( getOperation().getDefaultRequestParts() ) );
			result.addAll( Arrays.asList( getDefinedAttachmentParts() ) );

			return result.toArray( new MessagePart[result.size()] );
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
			return new MessagePart[0];
		}
	}

	public MessagePart[] getResponseParts()
	{
		try
		{
			List<MessagePart> result = new ArrayList<MessagePart>();
			result.addAll( Arrays.asList( getOperation().getDefaultResponseParts() ) );

			if( getResponse() != null )
				result.addAll( AttachmentUtils.extractAttachmentParts( getOperation(), getResponse().getContentAsString(),
						true, true, isMtomEnabled() ) );

			return result.toArray( new MessagePart[result.size()] );
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
			return new MessagePart[0];
		}
	}

	public String getWssTimeToLive()
	{
		return getSettings().getString( WSS_TIMETOLIVE, null );
	}

	public void setWssTimeToLive( String ttl )
	{
		getSettings().setString( WSS_TIMETOLIVE, ttl );
	}

	public long getContentLength()
	{
		return getRequestContent().length();
	}

	public boolean isForceMtom()
	{
		return getSettings().getBoolean( FORCE_MTOM );
	}

	public void setForceMtom( boolean forceMtom )
	{
		boolean old = getSettings().getBoolean( FORCE_MTOM );
		getSettings().setBoolean( FORCE_MTOM, forceMtom );
		notifyPropertyChanged( FORCE_MTOM, old, forceMtom );
	}

	public boolean isEncodeAttachments()
	{
		return getSettings().getBoolean( ENCODE_ATTACHMENTS );
	}

	public void setEncodeAttachments( boolean encodeAttachments )
	{
		boolean old = getSettings().getBoolean( ENCODE_ATTACHMENTS );
		getSettings().setBoolean( ENCODE_ATTACHMENTS, encodeAttachments );
		notifyPropertyChanged( ENCODE_ATTACHMENTS, old, encodeAttachments );
	}

	public String getIncomingWss()
	{
		return getConfig().getIncomingWss();
	}

	public void setIncomingWss( String incomingWss )
	{
		String old = getIncomingWss();
		getConfig().setIncomingWss( incomingWss );
		notifyPropertyChanged( INCOMING_WSS, old, incomingWss );
	}

	public String getOutgoingWss()
	{
		return getConfig().getOutgoingWss();
	}

	public void setOutgoingWss( String outgoingWss )
	{
		String old = getOutgoingWss();
		getConfig().setOutgoingWss( outgoingWss );
		notifyPropertyChanged( OUGOING_WSS, old, outgoingWss );
	}

	public boolean isWsAddressing()
	{
		return getConfig().getUseWsAddressing();
	}

	public void setWsAddressing( boolean wsAddressing )
	{
		boolean old = getConfig().getUseWsAddressing();
		getConfig().setUseWsAddressing( wsAddressing );
		notifyPropertyChanged( "wsAddressing", old, wsAddressing );
	}

	public PropertyExpansion[] getPropertyExpansions()
	{
		PropertyExpansionsResult result = new PropertyExpansionsResult( this, this );
		result.addAll( super.getPropertyExpansions() );

		StringToStringsMap requestHeaders = getRequestHeaders();
		for( String key : requestHeaders.keySet() )
		{
			for( String value : requestHeaders.get( key ) )
				result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this,
						new HttpTestRequestStep.RequestHeaderHolder( key, value, this ), "value" ) );
		}
		addWsaPropertyExpansions( result, getWsaConfig(), this );
		addJMSHeaderExpansions( result, getJMSHeaderConfig(), this );
		return result.toArray();
	}

	public void addWsaPropertyExpansions( PropertyExpansionsResult result, WsaConfig wsaConfig, ModelItem modelItem )
	{
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "action" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "from" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "to" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "replyTo" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "replyToRefParams" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "faultTo" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "faultToRefParams" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "relatesTo" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "relationshipType" ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, wsaConfig, "messageID" ) );
	}

	public void addJMSHeaderExpansions( PropertyExpansionsResult result, JMSHeaderConfig jmsHeaderConfig,
			ModelItem modelItem )
	{
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig,
				JMSHeader.JMSCORRELATIONID ) );
		result.addAll( PropertyExpansionUtils
				.extractPropertyExpansions( modelItem, jmsHeaderConfig, JMSHeader.JMSREPLYTO ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig, JMSHeader.JMSTYPE ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig,
				JMSHeader.JMSPRIORITY ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig,
				JMSHeader.DURABLE_SUBSCRIPTION_NAME ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig, JMSHeader.CLIENT_ID ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig,
				JMSHeader.SEND_AS_BYTESMESSAGE ) );
		result.addAll( PropertyExpansionUtils.extractPropertyExpansions( modelItem, jmsHeaderConfig,
				JMSHeader.SOAP_ACTION_ADD ) );
	}

	public AttachmentEncoding getAttachmentEncoding( String partName )
	{
		HttpAttachmentPart attachmentPart = getAttachmentPart( partName );
		if( attachmentPart == null )
			return AttachmentUtils.getAttachmentEncoding( getOperation(), partName, false );
		else
			return AttachmentUtils.getAttachmentEncoding( getOperation(), attachmentPart, false );
	}

	public WsaConfig getWsaConfig()
	{
		if( wsaConfig == null )
		{
			if( !getConfig().isSetWsaConfig() )
			{
				getConfig().addNewWsaConfig();
			}
			wsaConfig = new WsaConfig( getConfig().getWsaConfig(), this );
		}
		return wsaConfig;
	}

	public ModelItem getModelItem()
	{
		return this;
	}

	public boolean isWsaEnabled()
	{
		return isWsAddressing();
	}

	public void setWsaEnabled( boolean arg0 )
	{
		setWsAddressing( arg0 );
	}

	public boolean isWsReliableMessaging()
	{
		return getConfig().getUseWsReliableMessaging();
	}

	public void setWsReliableMessaging( boolean wsReliableMessaging )
	{
		boolean old = getConfig().getUseWsReliableMessaging();
		getConfig().setUseWsReliableMessaging( wsReliableMessaging );
		notifyPropertyChanged( "wsReliableMessaging", old, wsReliableMessaging );
	}

	public WsrmConfig getWsrmConfig()
	{
		if( wsrmConfig == null )
		{
			if( !getConfig().isSetWsrmConfig() )
			{
				getConfig().addNewWsrmConfig();
			}
			wsrmConfig = new WsrmConfig( getConfig().getWsrmConfig(), this );
			wsrmConfig.addPropertyChangeListener( "version", this );
		}
		return wsrmConfig;
	}

	public boolean isWsrmEnabled()
	{
		return isWsReliableMessaging();
	}

	public void setWsrmEnabled( boolean arg0 )
	{
		setWsReliableMessaging( arg0 );
	}

	public String getResponseContentAsXml()
	{
		return getResponse() == null ? null : getResponse().getContentAsString();
	}

	private JMSHeaderConfig jmsHeaderConfig;
	private JMSPropertiesConfig jmsPropertyConfig;

	public JMSHeaderConfig getJMSHeaderConfig()
	{
		if( jmsHeaderConfig == null )
		{
			if( !getConfig().isSetJmsConfig() )
			{
				getConfig().addNewJmsConfig();
			}
			jmsHeaderConfig = new JMSHeaderConfig( getConfig().getJmsConfig(), this );
		}
		return jmsHeaderConfig;
	}

	public JMSPropertiesConfig getJMSPropertiesConfig()
	{
		if( jmsPropertyConfig == null )
		{
			if( !getConfig().isSetJmsPropertyConfig() )
			{
				getConfig().addNewJmsPropertyConfig();
			}
			jmsPropertyConfig = new JMSPropertiesConfig( getConfig().getJmsPropertyConfig(), this );
		}
		return jmsPropertyConfig;
	}

	public String getAction()
	{
		if( isWsaEnabled() && StringUtils.hasContent( getWsaConfig().getAction() ) )
		{
			return getWsaConfig().getAction();
		}

		return getOperation().getAction();
	}

	@Override
	public void propertyChange( PropertyChangeEvent evt )
	{
		if( evt.getSource() == wsrmConfig && evt.getPropertyName().equals( "version" ) )
		{
			if( evt.getNewValue().equals( WsrmVersionTypeConfig.X_1_0.toString() ) )
			{
				getWsaConfig().setVersion( WsaVersionTypeConfig.X_200408.toString() );
			}
		}
	}
}
