/*
 * 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.ArrayList;
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.core.IPackageDeclaration;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.eclipse.jdt.internal.core.PackageFragment;
import org.eclipse.jdt.internal.corext.refactoring.rename.JavaRenameProcessor;
import org.eclipse.jdt.internal.corext.refactoring.reorg.JavaMoveProcessor;
import org.eclipse.jdt.internal.corext.refactoring.reorg.MoveModifications;
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.MoveArguments;
import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;

/**
 * Participant to generate refactoring when types are moved or a package is renamed
 */
@SuppressWarnings("restriction")
public class RuleIFileMoveParticipant extends MoveParticipant {

    public static final String NAME = "Rule Move Refactoring";

    private static RefactoringContent refactoringContent = new RefactoringContent();
    private DRLProjectDetector drlProjectDetector = new DRLProjectDetector();
    private Matcher matcher;

    private IFile file;
    private RefactoringProcessor processor;
    private String newName;
    private String currentName;

    private String className;

    @Override
    public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException {
        RefactoringStatus status = new RefactoringStatus();
        if (file==null || file.isReadOnly())
            status.addFatalError("File don't exists or is read only");
        return status;
    }

    @Override
    public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
        CompositeChange changes = new CompositeChange("Reorganize DRL " + currentName + "." + className + " imports ");
        String content;
        for (IFile drlFile : drlProjectDetector.detect(file.getProject())) {

            content = refactoringContent.getIFileContent(drlFile);
            if (content==null && (content = readFile(drlFile))==null)
                continue;

            String toReplace = currentName + "." + className;
            String replaceWith = newName + "." + className;

            Pattern pattern = Pattern.compile(toReplace);
            matcher = pattern.matcher(content);

            if (matcher.find()) {
                TextFileChange change = new TextFileChange(drlFile.getName(), drlFile);
                MultiTextEdit mte = new MultiTextEdit();
                change.setEdit(mte);
                ReplaceEdit replace = new ReplaceEdit(matcher.start(), toReplace.length(), replaceWith);
                mte.addChild(replace);
                changes.add(change);
                refactoringContent.updateContent(drlFile, content.replace(toReplace, replaceWith));
            }
        }
        return (changes.getChildren().length > 0)?changes:null;
    }

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

    @Override
    protected boolean initialize(Object element) {
        if (element instanceof IFile) {
            IFile file = (IFile)element;
            if (file.getType()==IFile.FILE) {
                if (file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("java")) {
                    this.processor = getProcessor();
                    this.file = file;
                    this.className = file.getName().replace("."+file.getFileExtension(), "");
                    if (processor.hashCode()!=refactoringContent.getProcessorHashcode()) {
                        refactoringContent.setProcessorHashcode(processor.hashCode());
                        refactoringContent.clear();
                    }
                    if (processor instanceof JavaRenameProcessor) {
                        newName = ((JavaRenameProcessor)processor).getNewElementName();
                        currentName = ((JavaRenameProcessor)processor).getCurrentElementName();
                        return true;
                    }
                    else if (processor instanceof JavaMoveProcessor) {
                        try {
                            MoveModifications moveModifications = getNewName();
                            getCurrentName(moveModifications);
                        } catch (SecurityException e) {
                            return false;
                        } catch (NoSuchFieldException e) {
                            return false;
                        } catch (IllegalArgumentException e) {
                            return false;
                        } catch (IllegalAccessException e) {
                            return false;
                        } catch (JavaModelException e) {
                            return false;
                        }
                        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();
    }

    @SuppressWarnings("unchecked")
    private MoveModifications getNewName() throws NoSuchFieldException, IllegalAccessException {
        Field fMovePolicyPrivateField = processor.getClass().getDeclaredField("fMovePolicy");
        fMovePolicyPrivateField.setAccessible(true);
        Object movePolicy = fMovePolicyPrivateField.get(processor);

        Field fModificationsPrivateField = movePolicy.getClass().getDeclaredField("fModifications");
        fModificationsPrivateField.setAccessible(true);
        MoveModifications moveModifications = (MoveModifications) fModificationsPrivateField.get(movePolicy);
        Field fMoveArgumentsPrivateField = moveModifications.getClass().getDeclaredField("fMoveArguments");
        fMoveArgumentsPrivateField.setAccessible(true);
        ArrayList<MoveArguments> moveArguments = (ArrayList<MoveArguments>) fMoveArgumentsPrivateField.get(moveModifications);
        PackageFragment packageFragment = (PackageFragment) moveArguments.get(0).getDestination();

        String[] names = packageFragment.names;
        String newPackageName = "";
        for (int i = 0; i < names.length; i++)
            newPackageName = newPackageName.concat(names[i]+".");

        if (newPackageName.length() > 0) {
            newName = newPackageName.substring(0, newPackageName.length()-1);
        }
        return moveModifications;
    }

    @SuppressWarnings("unchecked")
    private void getCurrentName(MoveModifications moveModifications) throws NoSuchFieldException, IllegalAccessException, JavaModelException {
        Field fMovesPrivateField = moveModifications.getClass().getDeclaredField("fMoves");
        fMovesPrivateField.setAccessible(true);
        ArrayList<Object> fmoves = (ArrayList<Object>) fMovesPrivateField.get(moveModifications);
        for (Object fmove : fmoves) {
            if (fmove instanceof CompilationUnit) {
                CompilationUnit cu = (CompilationUnit)fmove;
                IPackageDeclaration[] packageDeclarations = cu.getPackageDeclarations();
                for (int i = 0; i < packageDeclarations.length; i++)
                    currentName = packageDeclarations[i].getElementName();
            }
        }
    }

}
