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

import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.List;

import javax.swing.AbstractListModel;

import org.apache.commons.collections.list.TreeList;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.model.security.SecurityScan;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.security.result.SecurityResult;
import com.eviware.soapui.security.result.SecurityScanRequestResult;
import com.eviware.soapui.security.result.SecurityScanResult;
import com.eviware.soapui.security.result.SecurityTestStepResult;
import com.eviware.soapui.security.result.SecurityResult.ResultStatus;
import com.eviware.soapui.security.scan.AbstractSecurityScan;

/**
 * SecurityTestLog
 * 
 * @author soapUI team
 */
@SuppressWarnings( { "serial", "unchecked" } )
public class SecurityTestLogModel extends AbstractListModel
{
	private List<Object> items = Collections.synchronizedList( new TreeList() );
	private List<SoftReference<SecurityResult>> results = Collections.synchronizedList( new TreeList() );
	private int maxSize = 100;
	private int stepCount;
	private int checkCount;
	private int requestCount;
	private int currentCheckEntriesCount;
	private int currentStepEntriesCount;

	public synchronized Object getElementAt( int arg0 )
	{
		try
		{
			return items.get( arg0 );
		}
		catch( Throwable e )
		{
			return null;
		}
	}

	@Override
	public int getSize()
	{
		return items.size();
	}

	public synchronized void addText( String msg )
	{
		items.add( msg );
		results.add( null );
		fireIntervalAdded( this, items.size() - 1, items.size() - 1 );

		enforceMaxSize();
	}

	public synchronized SecurityResult getTestStepResultAt( int index )
	{
		if( index >= results.size() )
			return null;

		SoftReference<SecurityResult> result = results.get( index );
		return result == null ? null : result.get();
	}

	/**
	 * called before TestStep SecurityScans just to mark beginning of TestStep
	 * SecurityLog part to be updated after step scans finish with proper values
	 * 
	 * @return true - if added, false - otherwise
	 */
	public synchronized boolean addSecurityTestStepResult( TestStep testStep )
	{
		stepCount++ ;
		checkCount = 0;

		currentStepEntriesCount = 1;
		int size = items.size();
		if( AbstractSecurityScan.isSecurable( testStep ) )
		{
			SecurityTestStepResult result = new SecurityTestStepResult( testStep, null );
			SoftReference<SecurityResult> stepResultRef = new SoftReference<SecurityResult>( result );
			items.add( "Step " + stepCount + " [" + result.getTestStep().getName() + "] " );
			results.add( stepResultRef );

			fireIntervalAdded( this, size, items.size() - 1 );
			enforceMaxSize();
			return true;
		}
		else
			return false;
	}

	// called after whole security teststep finished to delete start line in case
	// only errors are beeing displayed
	public synchronized void updateSecurityTestStepResult( SecurityTestStepResult result, boolean errorsOnly,
			boolean hasChecksToProcess, boolean startStepLogEntryAdded )
	{
		int startStepIndex = 0;
		if( items.size() > currentStepEntriesCount )
		{
			if( currentStepEntriesCount > 0 )
				startStepIndex = items.size() - currentStepEntriesCount;
			else
				startStepIndex = items.size();
		}
		if( ( errorsOnly && !result.isHasScansWithWarnings() ) || ( startStepLogEntryAdded && !hasChecksToProcess ) )
		{
			// stepCount-- ;
			int size = items.size() - 1;
			while( size >= startStepIndex )
			{
				items.remove( size );
				results.remove( size );
				size-- ;
			}
			if( startStepIndex > 0 && size > 0 )
			{
				fireIntervalRemoved( this, startStepIndex, size );
			}
			else
			{
				fireIntervalRemoved( this, 0, size );
			}
		}
		else if( startStepLogEntryAdded )
		{

			try
			{
				if( startStepIndex > 0 && startStepIndex < maxSize )
				{
					String statusToDisplay = getStatusToDisplay( result.getExecutionProgressStatus() );
					items.set( startStepIndex, "Step " + stepCount + " [" + result.getTestStep().getName() + "] "
							+ statusToDisplay + ": took " + result.getTimeTaken() + " ms" );
					SoftReference<SecurityResult> stepResultRef = new SoftReference<SecurityResult>( result );
					results.set( startStepIndex, stepResultRef );
					fireContentsChanged( this, startStepIndex, startStepIndex );
				}
			}
			catch( IndexOutOfBoundsException e )
			{
				// when log max size is exceeded skip updating the raw since it
				// won't be visible anyway
			}
		}
		currentStepEntriesCount = 0;
	}

	private String getStatusToDisplay( ResultStatus result )
	{
		String statusToDisplay = "";
		switch( result )
		{
		case FAILED :
			statusToDisplay = "Alerts";
			break;
		case OK :
			statusToDisplay = "No Alerts";
			break;
		case SKIPPED :
			statusToDisplay = "Skipped";
			break;
		case MISSING_ASSERTIONS :
			statusToDisplay = "Missing Assertions";
			break;
		case MISSING_PARAMETERS :
			statusToDisplay = "Missing Parameters";
			break;
		}
		return statusToDisplay;
	}

	public synchronized void addSecurityScanResult( SecurityScan securityCheck )
	{
		int size = items.size();
		checkCount++ ;
		requestCount = 0;

		SecurityScanResult securityCheckResult = securityCheck.getSecurityScanResult();
		SoftReference<SecurityResult> checkResultRef = new SoftReference<SecurityResult>( securityCheckResult );

		items.add( "SecurityScan " + checkCount + " [" + securityCheck.getName() + "] " );
		results.add( checkResultRef );
		currentCheckEntriesCount = 1;
		currentStepEntriesCount++ ;

		fireIntervalAdded( this, size, items.size() - 1 );
		enforceMaxSize();
	}

	// updates log entry for security scan with the status, time taken, and
	// similar info known only after finished
	public synchronized void updateSecurityScanResult( SecurityScanResult securityCheckResult, boolean errorsOnly )
	{
		int startCheckIndex = 0;
		if( items.size() > currentCheckEntriesCount )
		{
			if( currentCheckEntriesCount > 0 )
				startCheckIndex = items.size() - currentCheckEntriesCount;
			else
				startCheckIndex = items.size();
		}
		if( errorsOnly && !securityCheckResult.isHasRequestsWithWarnings() )
		{
			// remove all entries for the securityCheck that had no warnings
			checkCount-- ;
			int size = items.size() - 1;
			while( size >= startCheckIndex )
			{
				items.remove( size );
				results.remove( size );
				size-- ;
			}
			if( startCheckIndex > 0 )
			{
				fireIntervalRemoved( this, startCheckIndex, size );
			}
			else
			{
				fireIntervalRemoved( this, startCheckIndex, size );
			}

		}
		else
		{
			SecurityScan securityCheck = securityCheckResult.getSecurityScan();
			securityCheckResult.detectMissingItems();
			StringBuilder outStr = new StringBuilder( "SecurityScan " );
			String statusToDisplay = getStatusToDisplay( securityCheckResult.getExecutionProgressStatus() );
			outStr.append( checkCount ).append( " [" ).append( securityCheck.getName() ).append( "] " ).append(
					statusToDisplay ).append( ", took = " ).append( securityCheckResult.getTimeTaken() );
			try
			{
				if( startCheckIndex > 0 && startCheckIndex < maxSize )
				{
					items.set( startCheckIndex, outStr.toString() );
					SoftReference<SecurityResult> checkResultRef = new SoftReference<SecurityResult>( securityCheckResult );
					results.set( startCheckIndex, checkResultRef );
					currentCheckEntriesCount = 0;
					fireContentsChanged( this, startCheckIndex, startCheckIndex );
				}
			}
			catch( IndexOutOfBoundsException e )
			{
				// when log max size is exceeded skip updating the raw since it
				// won't be visible anyway
			}

		}
	}

	public synchronized void addSecurityScanRequestResult( SecurityScanRequestResult securityCheckRequestResult )
	{
		int size = items.size();
		requestCount++ ;

		SoftReference<SecurityResult> checkReqResultRef = new SoftReference<SecurityResult>( securityCheckRequestResult );

		items.add( securityCheckRequestResult.getChangedParamsInfo( requestCount ) );
		results.add( checkReqResultRef );
		currentCheckEntriesCount++ ;
		currentStepEntriesCount++ ;

		for( String msg : securityCheckRequestResult.getMessages() )
		{
			items.add( " -> " + msg );
			results.add( checkReqResultRef );
			currentCheckEntriesCount++ ;
			currentStepEntriesCount++ ;
		}

		fireIntervalAdded( this, size, items.size() - 1 );
		enforceMaxSize();
	}

	public synchronized void clear()
	{
		int sz = items.size();
		items.clear();
		results.clear();
		stepCount = 0;
		fireIntervalRemoved( this, 0, sz );
	}

	public int getMaxSize()
	{
		return maxSize;
	}

	public void setMaxSize( int maxSize )
	{
		this.maxSize = maxSize;
		enforceMaxSize();
	}

	private synchronized void enforceMaxSize()
	{
		while( items.size() > maxSize )
		{
			items.remove( 0 );
			results.remove( 0 );
			fireIntervalRemoved( this, 0, 0 );
		}
	}

	public synchronized int getIndexOfSecurityScan( SecurityScan check )
	{
		for( int i = 0; i < results.size(); i++ )
		{
			SoftReference<SecurityResult> result = results.get( i );
			if( result != null )
			{
				SecurityResult referent = result.get();
				if( referent instanceof SecurityScanResult )
				{
					if( ( ( SecurityScanResult )referent ).getSecurityScan() == check )
					{
						return i;
					}
				}
			}
		}
		return -1;
	}
}
