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

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.AttachmentConfig;
import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
import com.eviware.soapui.impl.wsdl.teststeps.BeanPathPropertySupport;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.editor.inspectors.attachments.ContentTypeHandler;
import com.eviware.soapui.support.resolver.ResolveContext;

/**
 * Attachments cached locally for each request
 * 
 * @author Ole.Matzura
 */

public abstract class FileAttachment<T extends AbstractWsdlModelItem<?>> implements WsdlAttachment
{
	private AttachmentConfig config;
	private final static Logger log = Logger.getLogger( FileAttachment.class );
	private final T modelItem;
	private BeanPathPropertySupport urlProperty;

	public FileAttachment( T modelItem, AttachmentConfig config )
	{
		this.modelItem = modelItem;
		this.config = config;

		if( config.getTempFilename() != null )
		{
			try
			{
				log.info( "Moving locally cached file [" + config.getTempFilename() + "] to internal cache.." );
				File tempFile = new File( config.getTempFilename() );
				cacheFileLocally( tempFile );
			}
			catch( IOException e )
			{
				if( !config.isSetData() )
				{
					config.setData( new byte[0] );
					config.setSize( 0 );
				}

				SoapUI.logError( e );
			}
		}

		if( isCached() )
		{
			if( config.isSetTempFilename() )
				config.unsetTempFilename();

			if( config.isSetUrl() )
				config.unsetUrl();
		}

		urlProperty = new BeanPathPropertySupport( modelItem, config, "url" );
	}

	public FileAttachment( T modelItem, File file, boolean cache, AttachmentConfig config ) throws IOException
	{
		this( modelItem, config );

		config.setName( file.getName() );
		config.setContentType( ContentTypeHandler.getContentTypeFromFilename( file.getName() ) );
		config.setContentId( file.getName() );
		config.setId( UUID.randomUUID().toString() );

		// cache locally if specified
		if( cache )
		{
			cacheFileLocally( file );
		}

		urlProperty.set( file.getPath(), false );
	}

	public void setName( String value )
	{
		config.setName( value );
	}

	public void setUrl( String url )
	{
		urlProperty.set( url, true );
	}

	public void reload( File file, boolean cache ) throws IOException
	{
		config.setName( file.getName() );
		config.setContentType( ContentTypeHandler.getContentTypeFromFilename( file.getName() ) );
		config.setContentId( file.getName() );

		// cache locally if specified
		if( cache )
		{
			cacheFileLocally( file );
		}
		else
		{
			urlProperty.set( file.getPath(), false );
			config.unsetData();
		}
	}

	public T getModelItem()
	{
		return modelItem;
	}

	public void cacheFileLocally( File file ) throws FileNotFoundException, IOException
	{
		// write attachment-data to tempfile
		ByteArrayOutputStream data = new ByteArrayOutputStream();
		ZipOutputStream out = new ZipOutputStream( data );
		out.putNextEntry( new ZipEntry( config.getName() ) );

		InputStream in = new FileInputStream( file );
		long sz = file.length();
		config.setSize( sz );

		Tools.writeAll( out, in );

		in.close();
		out.closeEntry();
		out.finish();
		out.close();
		data.close();

		config.setData( data.toByteArray() );
	}

	public String getContentType()
	{
		AttachmentEncoding encoding = getEncoding();
		if( encoding == AttachmentEncoding.NONE )
			return config.getContentType();
		else
			return "application/octet-stream";
	}

	public InputStream getInputStream() throws IOException
	{
		BufferedInputStream inputStream = null;

		if( isCached() )
		{
			ZipInputStream zipInputStream = new ZipInputStream( new ByteArrayInputStream( config.getData() ) );
			zipInputStream.getNextEntry();
			inputStream = new BufferedInputStream( zipInputStream );
		}
		else
		{
			String url = urlProperty.expand();
			inputStream = new BufferedInputStream( url == null ? new ByteArrayInputStream( new byte[0] )
					: new FileInputStream( url ) );
		}

		AttachmentEncoding encoding = getEncoding();
		if( encoding == AttachmentEncoding.BASE64 )
		{
			ByteArrayOutputStream data = Tools.readAll( inputStream, Tools.READ_ALL );
			return new ByteArrayInputStream( Base64.encodeBase64( data.toByteArray() ) );
		}
		else if( encoding == AttachmentEncoding.HEX )
		{
			ByteArrayOutputStream data = Tools.readAll( inputStream, Tools.READ_ALL );
			return new ByteArrayInputStream( new String( Hex.encodeHex( data.toByteArray() ) ).getBytes() );
		}

		return inputStream;
	}

	public String getName()
	{
		return config.getName();
	}

	public long getSize()
	{
		if( isCached() )
		{
			return config.getSize();
		}
		else
		{
			String url = urlProperty.expand();
			if( url != null )
			{
				File file = new File( url );
				if( file.exists() )
					return file.length();
			}
		}

		return -1;
	}

	public void release()
	{
		if( isCached() )
			new File( config.getTempFilename() ).delete();
	}

	public String getPart()
	{
		return config.getPart();
	}

	public void setContentType( String contentType )
	{
		config.setContentType( contentType );
	}

	public void setPart( String part )
	{
		config.setPart( part );
	}

	public void setData( byte[] data )
	{
		try
		{
			// write attachment-data to tempfile
			ByteArrayOutputStream tempData = new ByteArrayOutputStream();
			ZipOutputStream out = new ZipOutputStream( tempData );
			out.putNextEntry( new ZipEntry( config.getName() ) );
			config.setSize( data.length );
			out.write( data );
			out.closeEntry();
			out.finish();
			out.close();
			config.setData( tempData.toByteArray() );
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
		}
	}

	public byte[] getData() throws IOException
	{
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		Tools.writeAll( out, getInputStream() );
		return out.toByteArray();
	}

	public String getUrl()
	{
		return urlProperty.get();
	}

	public boolean isCached()
	{
		return config.isSetData();
	}

	abstract public AttachmentType getAttachmentType();

	public void updateConfig( AttachmentConfig config )
	{
		this.config = config;
		urlProperty.setConfig( config );
	}

	public AttachmentConfig getConfig()
	{
		return config;
	}

	public void setContentID( String contentID )
	{
		if( ( contentID == null || contentID.length() == 0 ) && config.isSetContentId() )
			config.unsetContentId();
		else
			config.setContentId( contentID );
	}

	public String getContentID()
	{
		return config.getContentId();
	}

	public void resolve( ResolveContext<?> context )
	{
		if( !isCached() )
			urlProperty.resolveFile( context, "Missing attachment [" + getName() + "]", null, null, false );
	}

	public String getContentEncoding()
	{
		AttachmentEncoding encoding = getEncoding();
		if( encoding == AttachmentEncoding.BASE64 )
			return "base64";
		else if( encoding == AttachmentEncoding.HEX )
			return "hex";
		else
			return "binary";
	}

	public void addExternalDependency( List<ExternalDependency> dependencies )
	{
		if( !isCached() )
			dependencies.add( new PathPropertyExternalDependency( urlProperty ) );
	}
}
