package com.daveme.intellij.organizephpimports;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
import com.jetbrains.php.lang.psi.PhpFile;
import com.jetbrains.php.lang.psi.elements.PhpPsiElement;
import com.jetbrains.php.lang.psi.elements.PhpUse;
import com.jetbrains.php.lang.psi.elements.PhpUseList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;

public class OrganizeImports extends AnAction {

    @Override
    public void update(AnActionEvent e) {
        super.update(e);
        Object psiFile = e.getData(CommonDataKeys.PSI_FILE);
        boolean enabled = psiFile instanceof PhpFile;
        e.getPresentation().setVisible(enabled);
        e.getPresentation().setEnabled(enabled);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
        Object psiFile = e.getData(CommonDataKeys.PSI_FILE);
        final PhpFile file = (PhpFile)psiFile;
        final Editor editor = e.getData(CommonDataKeys.EDITOR);
        if (editor == null || psiFile == null) {
            return;
        }
        new WriteCommandAction.Simple(file.getProject(), file) {
            @Override
            protected void run() throws Throwable {
                int offset = editor.getCaretModel().getOffset();
                PsiElement element = file.findElementAt(offset);
                if (element == null) {
                    return;
                }
                PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator(element);
                if (scopeForUseOperator == null) {
                    return;
                }
                List imports = PhpCodeInsightUtil.collectImports(scopeForUseOperator);
                Integer startingOffset = removeUseStatements(imports, editor);

                if (startingOffset != null) {
                    StringBuilder useStatements = generateUseStatements(imports);
                    editor.getDocument().insertString(startingOffset, useStatements);
                }

            }
        }.execute();
    }

    @Nullable
    private Integer removeUseStatements(List imports, Editor editor) {
        int modifyOffset = 0;
        Integer startingOffset = null;
        for (Object useListObject : imports) {
            PhpUseList useList = (PhpUseList)useListObject;
            TextRange textRange = useList.getTextRange();
            if (startingOffset == null) {
                startingOffset = textRange.getStartOffset();
            }
            // get the newline character after this use statement if there is one:
            PsiElement subsequentElement = useList.getNextSibling();
            modifyOffset = removeElement(modifyOffset, textRange, editor);
            if (subsequentElement instanceof PsiWhiteSpace) {
                PsiElement nextElement = subsequentElement.getNextSibling();
                if (nextElement instanceof PhpUseList) {
                    modifyOffset = removeElement(modifyOffset, subsequentElement.getTextRange(), editor);
                } else {
                    modifyOffset = removeUpToNextNewLine(modifyOffset, subsequentElement.getTextRange(), editor);
                }
            }
        }
        return startingOffset;
    }

    @NotNull
    private StringBuilder generateUseStatements(List imports) {
        // replace the use statements:
        StringBuilder useStatements = new StringBuilder();
        useStatements.append("use ");
        int totalUses = 0;
        for (Object useListObject : imports) {
            PhpUseList useList = (PhpUseList)useListObject;

            for (PhpUse use : useList.getDeclarations()) {
                if (totalUses > 0) {
                    useStatements.append(",\n\t");
                }
                useStatements.append(use.getFQN());
                totalUses++;
            }
        }
        useStatements.append(";\n");
        return useStatements;
    }

    private int removeElement(int modifyOffset, TextRange textRange, Editor editor) {
        editor.getDocument().deleteString(textRange.getStartOffset() - modifyOffset,
                    textRange.getEndOffset() - modifyOffset);
        return modifyOffset + textRange.getEndOffset() - textRange.getStartOffset();
    }

    private int removeUpToNextNewLine(int modifyOffset, TextRange textRange, Editor editor) {
        TextRange modifiedRange = new TextRange(textRange.getStartOffset() - modifyOffset,
                textRange.getEndOffset() - modifyOffset);
        String text = editor.getDocument().getText(modifiedRange);
        char[] chars = text.toCharArray();
        int textLength = 0;
        for (char aChar : chars) {
            textLength += 1;
            if (aChar == '\n') {
                break;
            }
        }
        TextRange newRange = new TextRange(textRange.getStartOffset(), textRange.getStartOffset() + textLength);
        return removeElement(modifyOffset, newRange, editor);
    }

}

@@ -266,53 +266,8 @@
 on;%0A
-import com.intellij.openapi.editor.Document;%0A
 impo
@@ -424,16 +424,58 @@
 eSpace;%0A
+import com.intellij.psi.util.PsiTreeUtil;%0A
 import c
@@ -562,24 +562,81 @@
 si.PhpFile;%0A
+import com.jetbrains.php.lang.psi.elements.PhpNamespace;%0A
 import com.j
@@ -787,16 +787,82 @@
 seList;%0A
+import com.jetbrains.php.lang.psi.elements.impl.PhpNamespaceImpl;%0A
 import o
@@ -2350,16 +2350,377 @@
 ditor);%0A
+                PsiElement namespaceParent = PsiTreeUtil.findFirstParent(element, PhpNamespace.INSTANCEOF);%0A                boolean indentExtraLevel = false;%0A                if (namespaceParent != null) %7B%0A                    PhpNamespaceImpl parent = (PhpNamespaceImpl)namespaceParent;%0A                    indentExtraLevel = parent.isBraced();%0A                %7D
 %0A       
@@ -2837,16 +2837,34 @@
 (imports
+, indentExtraLevel
 );%0A     
@@ -3618,39 +3618,37 @@
 yOffset = remove
-Element
+Range
 (modifyOffset, t
@@ -3899,31 +3899,29 @@
 set = remove
-Element
+Range
 (modifyOffse
@@ -4250,32 +4250,58 @@
 nts(List imports
+, boolean indentExtraLevel
 ) %7B%0A        // r
@@ -4702,24 +4702,142 @@
 d(%22,%5Cn%5Ct%22);%0A
+                    if (indentExtraLevel) %7B%0A                        useStatements.append(%22%5Ct%22);%0A                    %7D%0A
             
@@ -4982,13 +4982,93 @@
 d(%22;
-%5Cn
 %22);
+%0A        if (indentExtraLevel) %7B%0A            useStatements.append(%22%5Cn%22);%0A        %7D
 %0A   
@@ -5123,23 +5123,21 @@
 t remove
-Element
+Range
 (int mod
@@ -6083,23 +6083,21 @@
 n remove
-Element
+Range
 (modifyO

Indent by extra level if use statement is within namespace