/*******************************************************************************
 * Copyright (c) 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aspectj.ajdt.internal.compiler;

import java.util.Iterator;
import java.util.Map;

import org.aspectj.asm.internal.CharOperation;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IProgressListener;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.aspectj.weaver.IClassFileProvider;
import org.aspectj.weaver.IUnwovenClassFile;
import org.aspectj.weaver.IWeaveRequestor;
import org.eclipse.core.runtime.OperationCanceledException;

/**
 * @author colyer This class provides the weaver with a source of class files to weave (via the iterator and IClassFileProvider
 *         interfaces). It receives results back from the weaver via the IWeaveRequestor interface.
 */
public class WeaverAdapter implements IClassFileProvider, IWeaveRequestor, Iterator {

	private final AbstractCompilerAdapter compilerAdapter;
	private Iterator resultIterator;
	private int classFileIndex = 0;
	private InterimCompilationResult nowProcessing;
	private InterimCompilationResult lastReturnedResult;
	private final WeaverMessageHandler weaverMessageHandler;
	private final IProgressListener progressListener;
	private boolean finalPhase = false;
	private int localIteratorCounter;

	// Fields related to progress monitoring
	private int progressMaxTypes;
	private String progressPhasePrefix;
	private double fromPercent;
	private final double toPercent = 100.0;
	private int progressCompletionCount;

	public WeaverAdapter(AbstractCompilerAdapter forCompiler, WeaverMessageHandler weaverMessageHandler,
			IProgressListener progressListener) {
		this.compilerAdapter = forCompiler;
		this.weaverMessageHandler = weaverMessageHandler;
		this.progressListener = progressListener;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.aspectj.weaver.IClassFileProvider#getClassFileIterator()
	 */
	public Iterator getClassFileIterator() {
		classFileIndex = 0;
		localIteratorCounter = 0;
		nowProcessing = null;
		lastReturnedResult = null;
		resultIterator = compilerAdapter.getResultsPendingWeave().iterator();
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.aspectj.weaver.IClassFileProvider#getRequestor()
	 */
	public IWeaveRequestor getRequestor() {
		return this;
	}

	public boolean isApplyAtAspectJMungersOnly() {
		return false;
	}

	// Iteration
	// ================================================================

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Iterator#hasNext()
	 */
	public boolean hasNext() {
		if (nowProcessing == null) {
			if (!resultIterator.hasNext())
				return false;
			nowProcessing = (InterimCompilationResult) resultIterator.next();
			classFileIndex = 0;
		}
		while (nowProcessing.unwovenClassFiles().length == 0) {
			if (!resultIterator.hasNext())
				return false;
			nowProcessing = (InterimCompilationResult) resultIterator.next();
		}
		if (classFileIndex < nowProcessing.unwovenClassFiles().length) {
			return true;
		} else {
			classFileIndex = 0;
			if (!resultIterator.hasNext())
				return false;
			nowProcessing = (InterimCompilationResult) resultIterator.next();
			while (nowProcessing.unwovenClassFiles().length == 0) {
				if (!resultIterator.hasNext())
					return false;
				nowProcessing = (InterimCompilationResult) resultIterator.next();
			}
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Iterator#next()
	 */
	public Object next() {
		if (!hasNext())
			return null; // sets up indices correctly
		if (finalPhase) {
			if ((lastReturnedResult != null) && (lastReturnedResult != nowProcessing)) {
				// we're done with the lastReturnedResult
				finishedWith(lastReturnedResult);
			}
		}
		localIteratorCounter++;
		lastReturnedResult = nowProcessing;
		weaverMessageHandler.setCurrentResult(nowProcessing.result());
		// weaverMessageHandler.handleMessage(new Message("weaving " + nowProcessing.fileName(),IMessage.INFO, null, null));
		return nowProcessing.unwovenClassFiles()[classFileIndex++];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Iterator#remove()
	 */
	public void remove() {
		throw new UnsupportedOperationException();
	}

	// IWeaveRequestor
	// =====================================================================================

	// weave phases as indicated by bcelWeaver...
	public void processingReweavableState() {

		// progress reporting logic
		fromPercent = 50.0; // Assume weaving takes 50% of the progress bar...
		// recordProgress("processing reweavable state");
	}

	public void addingTypeMungers() {

		// progress reporting logic
		// At this point we have completed one iteration through all the classes/aspects
		// we'll be dealing with, so let us remember this max value for localIteratorCounter
		// (for accurate progress reporting)
		// recordProgress("adding type mungers");
		progressMaxTypes = localIteratorCounter;
	}

	public void weavingAspects() {

		// progress reporting logic
		progressPhasePrefix = "woven aspect ";
		progressCompletionCount = 0; // Start counting from *now*
	}

	public void weavingClasses() {
		finalPhase = true;

		// progress reporting logic
		progressPhasePrefix = "woven class ";
	}

	public void weaveCompleted() {
		if ((lastReturnedResult != null) && (!lastReturnedResult.result().hasBeenAccepted)) {
			finishedWith(lastReturnedResult);
		}
		lastReturnedResult = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.aspectj.weaver.IWeaveRequestor#acceptResult(org.aspectj.weaver.bcel.UnwovenClassFile)
	 */
	public void acceptResult(IUnwovenClassFile result) {
		char[] key = result.getClassNameAsChars();
		removeFromMap(lastReturnedResult.result().compiledTypes, key);
		AjClassFile ajcf = new AjClassFile(key, result.getBytes());
		lastReturnedResult.result().record(ajcf.fileName(), ajcf);
		if (!weaverMessageHandler.isIgnoring(IMessage.INFO) || progressListener != null) {
			StringBuffer msg = new StringBuffer();
			msg.append(progressPhasePrefix).append(result.getClassName()).append(" (from ").append(nowProcessing.fileName())
					.append(")");
			weaverMessageHandler.handleMessage(MessageUtil.info(msg.toString()));
			if (progressListener != null) {
				progressCompletionCount++;

				// Smoothly take progress from 'fromPercent' to 'toPercent'
				recordProgress(fromPercent + ((progressCompletionCount / (double) progressMaxTypes) * (toPercent - fromPercent)),
						msg.toString());
				// progressPhasePrefix+result.getClassName()+" (from "+nowProcessing.fileName()+")");

				if (progressListener.isCancelledRequested()) {
					throw new AbortCompilation(true, new OperationCanceledException("Weaving cancelled as requested"));
				}
			}
		}
	}

	// helpers...
	// =================================================================

	private void finishedWith(InterimCompilationResult result) {
		compilerAdapter.acceptResult(result.result());
	}

	private boolean removeFromMap(Map aMap, char[] key) {
		// jdt uses char[] as a key in the hashtable, which is not very useful as equality is based on being
		// the same array, not having the same content.
		// String skey = new String(key);
		// OPTIMIZE what is this code for?
		if (aMap.remove(key) != null) {
			return true;
		}
		char[] victim = null;
		for (Iterator iter = aMap.keySet().iterator(); iter.hasNext();) {
			char[] thisKey = (char[]) iter.next();
			if (CharOperation.equals(thisKey, key)) {
				// if (skey.equals(new String(thisKey))) {
				victim = thisKey;
				break;
			}
		}
		if (victim != null) {
			aMap.remove(victim);
			return true;
		}
		return false;
	}

	private void recordProgress(double percentage, String message) {
		if (progressListener != null) {
			progressListener.setProgress(percentage / 100);
			progressListener.setText(message);
		}
	}
}