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

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Node;

import com.eviware.soapui.config.DefinitionCacheConfig;
import com.eviware.soapui.config.DefinitionCacheTypeConfig;
import com.eviware.soapui.config.DefintionPartConfig;
import com.eviware.soapui.impl.support.AbstractInterface;
import com.eviware.soapui.impl.wsdl.support.Constants;
import com.eviware.soapui.impl.wsdl.support.PathUtils;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlUtils;

/**
 * WsdlLoader for cached definitions
 * 
 * @author ole.matzura
 */

public class CachedWsdlLoader extends WsdlLoader
{
	private final DefinitionCacheConfig config;
	private String rootInConfig = "";

	public CachedWsdlLoader( DefinitionCacheConfig config )
	{
		super( config.getRootPart() );
		this.config = config;
	}

	public CachedWsdlLoader( AbstractInterface<?> iface ) throws Exception
	{
		this( WsdlUtils.cacheWsdl( new UrlWsdlLoader( PathUtils.expandPath( iface.getDefinition(), iface ), iface ) ) );
	}

	public InputStream load( String url ) throws Exception
	{
		XmlObject xmlObject = loadXmlObject( url, null );
		return xmlObject == null ? null : xmlObject.newInputStream();
	}

	public XmlObject loadXmlObject( String url, XmlOptions options ) throws Exception
	{
		// required for backwards compatibility when the entire path was stored
		if( url.endsWith( config.getRootPart() ) )
		{
			rootInConfig = url.substring( 0, url.length() - config.getRootPart().length() );
		}

		List<DefintionPartConfig> partList = config.getPartList();
		for( DefintionPartConfig part : partList )
		{
			if( ( rootInConfig + part.getUrl() ).equalsIgnoreCase( url ) )
			{
				return getPartContent( config, part );
			}
		}

		// hack: this could be due to windows -> unix, try again with replaced '/'
		if( File.separatorChar == '/' )
		{
			url = url.replace( '/', '\\' );

			for( DefintionPartConfig part : partList )
			{
				if( ( rootInConfig + part.getUrl() ).equalsIgnoreCase( url ) )
				{
					return getPartContent( config, part );
				}
			}
		}
		// or the other way around..
		else if( File.separatorChar == '\\' )
		{
			url = url.replace( '\\', '/' );

			for( DefintionPartConfig part : partList )
			{
				if( ( rootInConfig + part.getUrl() ).equalsIgnoreCase( url ) )
				{
					return getPartContent( config, part );
				}
			}
		}

		return null;
	}

	public static XmlObject getPartContent( DefinitionCacheConfig config, DefintionPartConfig part ) throws XmlException
	{
		if( config.getType() == DefinitionCacheTypeConfig.TEXT )
		{
			Node domNode = part.getContent().getDomNode();
			String nodeValue = XmlUtils.getNodeValue( domNode );
			// return XmlObject.Factory.parse( nodeValue, new
			// XmlOptions().setLoadLineNumbers() );
			return XmlUtils.createXmlObject( nodeValue, new XmlOptions().setLoadLineNumbers() );
		}

		// return XmlObject.Factory.parse( part.getContent().toString(), new
		// XmlOptions().setLoadLineNumbers() );
		return XmlUtils.createXmlObject( part.getContent().toString(), new XmlOptions().setLoadLineNumbers() );
	}

	/**
	 * Saves the complete definition to the specified folder, returns path to
	 * root part
	 * 
	 * @param folderName
	 * @return
	 * @throws Exception
	 */

	public String saveDefinition( String folderName ) throws Exception
	{
		File outFolder = new File( folderName );
		if( !outFolder.exists() && !outFolder.mkdirs() )
			throw new Exception( "Failed to create directory [" + folderName + "]" );

		Map<String, String> urlToFileMap = new HashMap<String, String>();

		setFilenameForUrl( config.getRootPart(), Constants.WSDL11_NS, urlToFileMap, null );

		List<DefintionPartConfig> partList = config.getPartList();
		for( DefintionPartConfig part : partList )
		{
			setFilenameForUrl( part.getUrl(), part.getType(), urlToFileMap, null );
		}

		for( DefintionPartConfig part : partList )
		{
			XmlObject obj = null;
			if( config.getType() == DefinitionCacheTypeConfig.TEXT )
			{
				// obj = XmlObject.Factory.parse( XmlUtils.getNodeValue(
				// part.getContent().getDomNode() ) );
				obj = XmlUtils.createXmlObject( XmlUtils.getNodeValue( part.getContent().getDomNode() ) );
			}
			else
			{
				// obj = XmlObject.Factory.parse( part.getContent().toString() );
				obj = XmlUtils.createXmlObject( part.getContent().toString() );
			}

			replaceImportsAndIncludes( obj, urlToFileMap, part.getUrl() );
			obj.save( new File( outFolder, urlToFileMap.get( part.getUrl() ) ) );
		}

		return folderName + File.separatorChar + urlToFileMap.get( config.getRootPart() );
	}

	public StringToStringMap createFilesForExport( String urlPrefix ) throws Exception
	{
		StringToStringMap result = new StringToStringMap();
		Map<String, String> urlToFileMap = new HashMap<String, String>();

		if( urlPrefix == null )
			urlPrefix = "";

		setFilenameForUrl( config.getRootPart(), Constants.WSDL11_NS, urlToFileMap, urlPrefix );

		List<DefintionPartConfig> partList = config.getPartList();
		for( DefintionPartConfig part : partList )
		{
			if( !part.getUrl().equals( config.getRootPart() ) )
				setFilenameForUrl( part.getUrl(), part.getType(), urlToFileMap, urlPrefix );
		}

		for( DefintionPartConfig part : partList )
		{
			XmlObject obj = CachedWsdlLoader.getPartContent( config, part );
			replaceImportsAndIncludes( obj, urlToFileMap, part.getUrl() );
			String urlString = urlToFileMap.get( part.getUrl() );
			if( urlString.startsWith( urlPrefix ) )
				urlString = urlString.substring( urlPrefix.length() );

			result.put( urlString, obj.xmlText() );

			if( part.getUrl().equals( config.getRootPart() ) )
				result.put( "#root#", obj.xmlText() );
		}

		return result;
	}

	private void setFilenameForUrl( String fileUrl, String type, Map<String, String> urlToFileMap, String urlPrefix )
			throws MalformedURLException
	{

		String path = fileUrl;

		try
		{
			URL url = new URL( fileUrl );
			path = url.getPath();
		}
		catch( MalformedURLException e )
		{
		}

		int ix = path.lastIndexOf( '/' );
		String fileName = ix == -1 ? path : path.substring( ix + 1 );

		ix = fileName.lastIndexOf( '.' );
		if( ix != -1 )
			fileName = fileName.substring( 0, ix );

		if( type.equals( Constants.WSDL11_NS ) )
			fileName += ".wsdl";
		else if( type.equals( Constants.XSD_NS ) )
			fileName += ".xsd";
		else
			fileName += ".xml";

		if( urlPrefix != null )
			fileName = urlPrefix + fileName;

		int cnt = 1;
		while( urlToFileMap.containsValue( fileName ) )
		{
			ix = fileName.lastIndexOf( '.' );
			fileName = fileName.substring( 0, ix ) + "_" + cnt + fileName.substring( ix );
			cnt++ ;
		}

		urlToFileMap.put( fileUrl, fileName );
	}

	private void replaceImportsAndIncludes( XmlObject xmlObject, Map<String, String> urlToFileMap, String baseUrl )
			throws Exception
	{
		XmlObject[] wsdlImports = xmlObject
				.selectPath( "declare namespace s='http://schemas.xmlsoap.org/wsdl/' .//s:import/@location" );

		for( int i = 0; i < wsdlImports.length; i++ )
		{
			SimpleValue wsdlImport = ( ( SimpleValue )wsdlImports[i] );
			replaceLocation( urlToFileMap, baseUrl, wsdlImport );
		}

		XmlObject[] schemaImports = xmlObject
				.selectPath( "declare namespace s='http://www.w3.org/2001/XMLSchema' .//s:import/@schemaLocation" );

		for( int i = 0; i < schemaImports.length; i++ )
		{
			SimpleValue schemaImport = ( ( SimpleValue )schemaImports[i] );
			replaceLocation( urlToFileMap, baseUrl, schemaImport );
		}

		XmlObject[] schemaIncludes = xmlObject
				.selectPath( "declare namespace s='http://www.w3.org/2001/XMLSchema' .//s:include/@schemaLocation" );
		for( int i = 0; i < schemaIncludes.length; i++ )
		{
			SimpleValue schemaInclude = ( ( SimpleValue )schemaIncludes[i] );
			replaceLocation( urlToFileMap, baseUrl, schemaInclude );
		}

		XmlObject[] wadlImports = xmlObject.selectPath( "declare namespace s='" + Constants.WADL10_NS
				+ "' .//s:grammars/s:include/@href" );

		for( int i = 0; i < wadlImports.length; i++ )
		{
			SimpleValue wadlImport = ( ( SimpleValue )wadlImports[i] );
			replaceLocation( urlToFileMap, baseUrl, wadlImport );
		}

		wadlImports = xmlObject.selectPath( "declare namespace s='" + Constants.WADL11_NS
				+ "' .//s:grammars/s:include/@href" );

		for( int i = 0; i < wadlImports.length; i++ )
		{
			SimpleValue wadlImport = ( ( SimpleValue )wadlImports[i] );
			replaceLocation( urlToFileMap, baseUrl, wadlImport );
		}
	}

	private void replaceLocation( Map<String, String> urlToFileMap, String baseUrl, SimpleValue wsdlImport )
			throws Exception
	{
		String location = wsdlImport.getStringValue();
		if( location != null )
		{
			if( location.startsWith( "file:" ) || location.indexOf( "://" ) > 0 )
			{
				String newLocation = urlToFileMap.get( location );
				if( newLocation != null )
					wsdlImport.setStringValue( newLocation );
				else
					throw new Exception( "Missing local file for [" + newLocation + "]" );
			}
			else
			{
				String loc = Tools.joinRelativeUrl( baseUrl, location );
				String newLocation = urlToFileMap.get( loc );
				if( newLocation != null )
					wsdlImport.setStringValue( newLocation );
				else
					throw new Exception( "Missing local file for [" + loc + "]" );
			}
		}
	}

	public void close()
	{
	}
}
