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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.RestMethodConfig;
import com.eviware.soapui.config.RestRequestConfig;
import com.eviware.soapui.config.RestResourceRepresentationConfig;
import com.eviware.soapui.impl.rest.RestRepresentation.Type;
import com.eviware.soapui.impl.rest.support.OverlayRestParamsPropertyHolder;
import com.eviware.soapui.impl.rest.support.RestParamProperty;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder;
import com.eviware.soapui.impl.rest.support.XmlBeansRestParamsTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Attachment;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestPropertyListener;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.types.StringList;
import com.eviware.soapui.ui.desktop.AbstractSoapUIDesktop;

public class RestMethod extends AbstractWsdlModelItem<RestMethodConfig> implements MutableTestPropertyHolder,
		PropertyChangeListener
{
	private List<RestRequest> requests = new ArrayList<RestRequest>();
	private List<RestRepresentation> representations = new ArrayList<RestRepresentation>();
	private RestResource resource;
	private XmlBeansRestParamsTestPropertyHolder params;
	private RestParamsPropertyHolder overlayParams;

	private PropertyChangeListener representationPropertyChangeListener = new RepresentationPropertyChangeListener();
	private TestPropertyListener testPropertyListener = new InternalTestPropertyListener();

	public RestMethod( RestResource service, RestMethodConfig methodConfig )
	{
		super( methodConfig, service, "/"
				+ ( StringUtils.isNullOrEmpty( methodConfig.getMethod() ) ? "get" : methodConfig.getMethod().toLowerCase() )
				+ "_method.gif" );
		this.resource = service;

		if( methodConfig.getParameters() == null )
			methodConfig.addNewParameters();

		params = new XmlBeansRestParamsTestPropertyHolder( this, methodConfig.getParameters() );

		for( RestResourceRepresentationConfig config : methodConfig.getRepresentationList() )
		{
			RestRepresentation representation = new RestRepresentation( this, config );
			representations.add( representation );
			notifyPropertyChanged( "representations", null, representation );
		}

		for( RestRequestConfig config : methodConfig.getRequestList() )
		{
			RestRequest request = new RestRequest( this, config, false );
			requests.add( request );
			notifyPropertyChanged( "childRequests", null, request );
		}

		addTestPropertyListener( testPropertyListener );
	}

	public RestParamsPropertyHolder getOverlayParams()
	{
		if( overlayParams == null )
			overlayParams = new OverlayRestParamsPropertyHolder( buildOverlay( getResource() ), params );
		return overlayParams;
	}

	private RestParamsPropertyHolder buildOverlay( RestResource resource )
	{
		return resource.getParentResource() == null ? resource.getParams() : new OverlayRestParamsPropertyHolder(
				buildOverlay( resource.getParentResource() ), resource.getParams() );
	}

	public RestResource getOperation()
	{
		return resource;
	}

	public RestParamProperty addProperty( String name )
	{
		return params.addProperty( name );
	}

	public void moveProperty( String propertyName, int targetIndex )
	{
		params.moveProperty( propertyName, targetIndex );
	}

	public RestParamProperty removeProperty( String propertyName )
	{
		return params.removeProperty( propertyName );
	}

	public boolean renameProperty( String name, String newName )
	{
		return params.renameProperty( name, newName );
	}

	public void addTestPropertyListener( TestPropertyListener listener )
	{
		params.addTestPropertyListener( listener );
	}

	public RestParamsPropertyHolder getParams()
	{
		return params;
	}

	public ModelItem getModelItem()
	{
		return this;
	}

	public Map<String, TestProperty> getProperties()
	{
		return params.getProperties();
	}

	public RestParamProperty getProperty( String name )
	{
		return params.getProperty( name );
	}

	public RestParamProperty getPropertyAt( int index )
	{
		return params.getPropertyAt( index );
	}

	public int getPropertyCount()
	{
		return params.getPropertyCount();
	}

	public String[] getPropertyNames()
	{
		return params.getPropertyNames();
	}

	public String getPropertyValue( String name )
	{
		return params.getPropertyValue( name );
	}

	public boolean hasProperty( String name )
	{
		return params.hasProperty( name );
	}

	public void removeTestPropertyListener( TestPropertyListener listener )
	{
		params.removeTestPropertyListener( listener );
	}

	public void setPropertyValue( String name, String value )
	{
		params.setPropertyValue( name, value );
	}

	public String getPropertiesLabel()
	{
		return "Method Params";
	}

	public boolean hasRequestBody()
	{
		RestRequestInterface.RequestMethod method = getMethod();
		return method == RestRequestInterface.RequestMethod.POST || method == RestRequestInterface.RequestMethod.PUT
				|| method == RestRequestInterface.RequestMethod.PATCH;
	}

	public void propertyChange( PropertyChangeEvent arg0 )
	{
		// TODO Auto-generated method stub

	}

	public PropertyExpansion[] getPropertyExpansions()
	{
		return params.getPropertyExpansions();
	}

	public RestRequestInterface.RequestMethod getMethod()
	{
		String method = getConfig().getMethod();
		return method == null ? null : RestRequestInterface.RequestMethod.valueOf( method );
	}

	public void setMethod( RestRequestInterface.RequestMethod method )
	{
		RestRequestInterface.RequestMethod old = getMethod();
		getConfig().setMethod( method.toString() );
		setIcon( UISupport.createImageIcon( "/" + method.toString().toLowerCase() + "_method.gif" ) );
		notifyPropertyChanged( "method", old, method );
	}

	public String getDefaultRequestMediaType()
	{
		RestRepresentation[] representations = getRepresentations( RestRepresentation.Type.REQUEST, null );
		if( representations.length >= 1 )
			return representations[0].getMediaType();
		return "application/xml";
	}

	public RestRepresentation[] getRepresentations()
	{
		return getRepresentations( null, null );
	}

	public RestRepresentation[] getRepresentations( RestRepresentation.Type type, String mediaType )
	{
		List<RestRepresentation> result = new ArrayList<RestRepresentation>();
		Set<String> addedTypes = new HashSet<String>();

		for( RestRepresentation representation : representations )
		{
			if( ( type == null || type == representation.getType() )
					&& ( mediaType == null || mediaType.equals( representation.getMediaType() ) ) )
			{
				result.add( representation );
				addedTypes.add( representation.getMediaType() );
			}
		}

		if( type == RestRepresentation.Type.REQUEST )
		{
			for( RestRequest request : requests )
			{
				for( Attachment attachment : request.getAttachments() )
				{
					if( ( mediaType == null || mediaType.equals( attachment.getContentType() ) )
							&& !addedTypes.contains( attachment.getContentType() ) )
					{
						RestRepresentation representation = new RestRepresentation( this,
								RestResourceRepresentationConfig.Factory.newInstance() );
						representation.setType( RestRepresentation.Type.REQUEST );
						representation.setMediaType( attachment.getContentType() );
						result.add( representation );
					}
				}
			}
		}

		return result.toArray( new RestRepresentation[result.size()] );
	}

	public String[] getResponseMediaTypes()
	{
		StringList result = new StringList();

		for( RestRepresentation representation : getRepresentations( Type.RESPONSE, null ) )
		{
			if( !result.contains( representation.getMediaType() ) )
				result.add( representation.getMediaType() );
		}

		return result.toStringArray();
	}

	public RestRepresentation addNewRepresentation( Type type )
	{
		RestRepresentation representation = new RestRepresentation( this, getConfig().addNewRepresentation() );
		representation.setType( type );

		representation.addPropertyChangeListener( representationPropertyChangeListener );

		representations.add( representation );

		notifyPropertyChanged( "representations", null, representation );

		return representation;
	}

	public void removeRepresentation( RestRepresentation representation )
	{
		int ix = representations.indexOf( representation );

		representations.remove( ix );
		representation.removePropertyChangeListener( representationPropertyChangeListener );

		notifyPropertyChanged( "representations", representation, null );
		getConfig().removeRepresentation( ix );
		representation.release();
	}

	public void removeRequest( RestRequest request )
	{
		int ix = requests.indexOf( request );
		requests.remove( ix );

		try
		{
			( getInterface() ).fireRequestRemoved( request );
			notifyPropertyChanged( "childRequests", request, null );
		}
		finally
		{
			request.release();
			getConfig().removeRequest( ix );
		}
	}

	public RestResource getResource()
	{
		return resource;
	}

	public List<RestRequest> getRequestList()
	{
		return new ArrayList<RestRequest>( requests );
	}

	public RestRequest getRequestAt( int index )
	{
		return requests.get( index );
	}

	public RestRequest getRequestByName( String name )
	{
		return ( RestRequest )getWsdlModelItemByName( requests, name );
	}

	public int getRequestCount()
	{
		return requests.size();
	}

	public RestRequest addNewRequest( String name )
	{
		RestRequestConfig requestConfig = getConfig().addNewRequest();
		requestConfig.setName( name );

		RestRequest request = new RestRequest( this, requestConfig, false );
		requests.add( request );
		request.resetPropertyValues();

		/*
		 * for (RestParamProperty prop : getDefaultParams()) { if
		 * (!request.hasProperty(prop.getName()))
		 * request.addProperty(prop.getName()).setValue(prop.getDefaultValue()); }
		 */

		String[] endpoints = getInterface().getEndpoints();
		if( endpoints.length > 0 )
			request.setEndpoint( endpoints[0] );

		// getInterface().fireRequestAdded(request);
		notifyPropertyChanged( "childRequests", null, request );
		return request;
	}

	public RestRequest cloneRequest( RestRequest request, String name )
	{
		RestRequestConfig requestConfig = ( RestRequestConfig )getConfig().addNewRequest().set( request.getConfig() );
		requestConfig.setName( name );

		RestRequest newRequest = new RestRequest( this, requestConfig, false );
		requests.add( newRequest );

		// getInterface().fireRequestAdded(newRequest);
		notifyPropertyChanged( "childRequests", null, newRequest );
		return newRequest;
	}

	public RestParamProperty[] getDefaultParams()
	{
		List<RestParamProperty> result = new ArrayList<RestParamProperty>();
		Set<String> names = new HashSet<String>();

		result.addAll( Arrays.asList( resource.getDefaultParams() ) );

		for( int c = 0; c < getPropertyCount(); c++ )
		{
			if( names.contains( getPropertyAt( c ).getName() ) )
				continue;

			result.add( getPropertyAt( c ) );
			names.add( getPropertyAt( c ).getName() );
		}

		return result.toArray( new RestParamProperty[result.size()] );
	}

	public RestService getInterface()
	{
		return resource.getInterface();
	}

	public List<? extends ModelItem> getChildren()
	{
		return getRequestList();
	}

	@Override
	public void release()
	{
		( ( AbstractSoapUIDesktop )SoapUI.getDesktop() ).closeDependantPanels( this );
		super.release();
		for( int i = requests.size(); i > 0; i-- )
		{
			// removeRequest(requests.get(i-1));
			requests.get( i - 1 ).release();
		}
		getOperation().removePropertyChangeListener( this );
		params.release();

		removeTestPropertyListener( testPropertyListener );
	}

	public List<TestProperty> getPropertyList()
	{
		return params.getPropertyList();
	}

	private class RepresentationPropertyChangeListener implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			if( evt.getPropertyName().equals( "mediaType" )
					&& ( ( RestRepresentation )evt.getSource() ).getType() == Type.RESPONSE )
			{
				RestMethod.this.notifyPropertyChanged( "responseMediaTypes", null, getResponseMediaTypes() );
			}
		}
	}

	private class InternalTestPropertyListener implements TestPropertyListener
	{
		public void propertyAdded( String name )
		{
		}

		public void propertyMoved( String name, int oldIndex, int newIndex )
		{
		}

		public void propertyRemoved( String name )
		{
		}

		public void propertyRenamed( String oldName, String newName )
		{
		}

		public void propertyValueChanged( String name, String oldValue, String newValue )
		{
			getProperty( name ).setDefaultValue( newValue );
		}

	}

}
