/**
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.eclipse.refactoring;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.internal.core.SourceField;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.rename.JavaRenameProcessor;
import org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;

/**
 * Participant to generate refactoring when a field is renamed.
 * This isn't executed by the RefactoringProcessor when a field is refactored with
 * the rename hotkey into the editor. Only is called when the Rename field dialog is used.
 * 
 * @author Lucas Amador
 *
 */
@SuppressWarnings("restriction")
public class RuleSourceFieldRenameParticipant extends RenameParticipant {

	public static final String NAME = "Rule Source Field Rename Refactoring";

	private static final String FIELD_NAME = "(?<=:\\s)FIELD_NAME|FIELD_NAME(?=\\s=)";
	private static final String VARIABLE_ASSIGNED = "[\\w]*(?=\\s*:\\s*TYPE\\s*\\()";
	private static final String GETTER_NAME = "(?<=VARIABLE_NAME\\.)CURRENT_GETTER_NAME(?=\\s*\\()";
	private static final String SETTER_NAME = "(?<=VARIABLE_NAME\\.)CURRENT_SETTER_NAME(?=\\s*\\()";

	private DRLProjectDetector drlProjectDetector = new DRLProjectDetector();
	private Matcher matcher;

	private RefactoringProcessor processor;
	private List<IFile> drlFiles;
	private SourceField sourceField;
	private String newName;
	private String currentName;

	@Override
	public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException {
		RefactoringStatus status = new RefactoringStatus();
		try {
			IFile file = getSourceFieldIFile();
			if (file==null || file.isReadOnly())
				return status;
			drlFiles = drlProjectDetector.detect(file.getProject());
			String content = null;
			// if at least one file have a reference to the renamed field => apply refactoring
			for (IFile drlFile : drlFiles) {

				if ((content = readFile(drlFile))==null)
					return null;

				Pattern pattern = Pattern.compile("(?<=:\\s)" + currentName + "|" + currentName + "(?=\\s=)");
				matcher = pattern.matcher(content);

				if (matcher.find()) {
					RenameFieldProcessor renameFieldProcessor = (RenameFieldProcessor)processor;
					if (!renameFieldProcessor.getRenameGetter())
						status.addInfo("The getter must be also updated to refactor the DRL files.");
					return status;
				}

			}
		} catch (CoreException e) {
			throw new OperationCanceledException(e.getMessage());
		}
		return status;
	}

	@Override
	public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
		CompositeChange changes = null;
		String content;
		changes = new CompositeChange("Fix " + currentName + " field on DRL files");
		IFile file = getSourceFieldIFile();
		String typeName = sourceField.getParent().getElementName();
		if (file!=null) {
			RenameFieldProcessor renameFieldProcessor = (RenameFieldProcessor)processor;
			for (IFile drlFile : drlFiles) {

				if ((content = readFile(drlFile))==null)
					return null;

				TextFileChange change = new TextFileChange(drlFile.getName(), drlFile);
				MultiTextEdit mte = new MultiTextEdit();
				change.setEdit(mte);

				// rename the field name
				Pattern pattern = Pattern.compile(FIELD_NAME.replaceAll("FIELD_NAME", currentName));
				matcher = pattern.matcher(content);
				while (matcher.find()) {
					ReplaceEdit replace = new ReplaceEdit(matcher.start(), currentName.length(), newName);
					mte.addChild(replace);
				}

				// search all the variables of the type to replace the getters/setters
				pattern = Pattern.compile(VARIABLE_ASSIGNED.replace("TYPE", typeName));
				matcher = pattern.matcher(content);
				while (matcher.find()) {
					if (matcher.group().length() > 0) {
						String variableNameAssigned = matcher.group();
						if (renameFieldProcessor.getRenameGetter()) {
							String newGetterName = renameFieldProcessor.getNewGetterName();
							String currentGetterName = renameFieldProcessor.getGetter().getElementName();
							String regexp = GETTER_NAME.replace("VARIABLE_NAME", variableNameAssigned).replace("CURRENT_GETTER_NAME", currentGetterName);
							createFieldRenameChanges(mte, content, regexp, currentGetterName, newGetterName);
						}
						if (renameFieldProcessor.getRenameSetter()) {
							String newSetterName = renameFieldProcessor.getNewSetterName();
							String currentSetterName = renameFieldProcessor.getSetter().getElementName();
							String regexp = SETTER_NAME.replace("VARIABLE_NAME", variableNameAssigned).replace("CURRENT_SETTER_NAME", currentSetterName);
							createFieldRenameChanges(mte, content, regexp, currentSetterName, newSetterName);
						}
					}
				}

				if (change.getEdit().getChildrenSize() > 0)
					changes.add(change);

			}
		}
		return (changes.getChildren().length > 0)?changes:null;
	}

	private void createFieldRenameChanges(MultiTextEdit mte, String content, String regexp, String currentName, String newName) {
		Pattern pattern = Pattern.compile(regexp);
		Matcher setterMatcher = pattern.matcher(content);
		ReplaceEdit replace = null;
		while (setterMatcher.find()) {
			replace = new ReplaceEdit(setterMatcher.start(), currentName.length(), newName);
			mte.addChild(replace);
		}
	}

	@Override
	public String getName() {
		return NAME;
	}

	// TODO: Search the Native way to find the SourceField IFile
	private IFile getSourceFieldIFile() {
		Field fReferences;
		try {
			fReferences = processor.getClass().getDeclaredField("fReferences");
			fReferences.setAccessible(true);
			SearchResultGroup object[] = (SearchResultGroup[]) fReferences.get(processor);
			for (SearchResultGroup searchResultGroup : object) {
				if (searchResultGroup.getResource() instanceof IFile)
					return (IFile) searchResultGroup.getResource();
			}
		} catch (SecurityException e) {
			return null;
		} catch (NoSuchFieldException e) {
			return null;
		} catch (IllegalArgumentException e) {
			return null;
		} catch (IllegalAccessException e) {
			return null;
		}
		return null;
	}

	@Override
	protected boolean initialize(Object element) {
		if (element instanceof SourceField) {
			this.sourceField = (SourceField) element;
			this.processor = getProcessor();
			if (this.processor instanceof JavaRenameProcessor) {
				newName = ((JavaRenameProcessor)processor).getNewElementName();
				currentName = ((JavaRenameProcessor)processor).getCurrentElementName();
				return true;
			}
		}
		return false;
	}

	private String readFile(IFile file) throws CoreException {
		InputStream inputStream = file.getContents();
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		StringBuilder sb = new StringBuilder();
		String buffer = null;
		try {
			while ((buffer = reader.readLine()) != null)
				sb.append(buffer + "\n");
		}
		catch (IOException e) {
			return null;
		}
		finally {
			try {
				inputStream.close();
			}
			catch (IOException e) {
				// Nothing
			}
		}
		return sb.toString();
	}

}
