/*
 * Copyright (c) 2017. tangzx(love.tangzx@qq.com)
 *
 * 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 com.tang.intellij.lua.psi;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 *
 * Created by tangzx on 2016/12/3.
 */
public class LuaPsiTreeUtil {

    public interface ElementProcessor<T extends PsiElement> {
        boolean accept(T t);
    }

    public static void walkUpLabel(PsiElement current, ElementProcessor<LuaLabelStat> processor) {
        PsiElement prev = current.getPrevSibling();
        while (true) {
            if (prev == null)
                prev = current.getParent();
            if (prev == null || prev instanceof PsiFile)
                break;
            if (prev instanceof LuaLabelStat && !processor.accept((LuaLabelStat) prev))
                break;
            current = prev;
            prev = prev.getPrevSibling();
        }
    }

    public static <T extends PsiElement> void walkTopLevelInFile(PsiElement element, Class<T> cls, ElementProcessor<T> processor) {
        if (element == null || processor == null)
            return;
        PsiElement parent = element;
        while (!(parent.getParent() instanceof PsiFile))
            parent = parent.getParent();

        for(PsiElement child = parent; child != null; child = child.getPrevSibling()) {
            if (cls.isInstance(child)) {
                if (!processor.accept(cls.cast(child))) {
                    break;
                }
            }
        }
    }

    /**
     * 向上寻找 local function 定义
     * @param current 当前搜导起点
     * @param processor 处理器
     */
    public static void walkUpLocalFuncDef(PsiElement current, ElementProcessor<LuaLocalFuncDef> processor) {
        if (current == null || processor == null)
            return;
        boolean continueSearch = true;
        int treeDeep = 0;
        int funcDeep = 0;
        PsiElement curr = current;
        do {
            if (curr instanceof LuaLocalFuncDef) {
                LuaLocalFuncDef localFuncDef = (LuaLocalFuncDef) curr;
                //todo 第一级local function不能使用
                continueSearch = processor.accept(localFuncDef);
                funcDeep++;
            }

            PsiElement prevSibling = curr.getPrevSibling();
            if (prevSibling == null) {
                treeDeep++;
                prevSibling = curr.getParent();
            }
            curr = prevSibling;
        } while (continueSearch && !(curr instanceof PsiFile));
    }

    /**
     * 向上寻找 local 定义
     * @param element 当前搜索起点
     * @param processor 处理器
     */
    public static void walkUpLocalNameDef(PsiElement element, ElementProcessor<LuaNameDef> processor) {
        if (element == null || processor == null)
            return;
        boolean continueSearch = true;
        boolean searchParList = false;

        PsiElement curr = element;
        do {
            PsiElement next = curr.getPrevSibling();
            if (next == null) {
                next = curr.getParent();
            }
            curr = next;

            if (curr instanceof LuaLocalDef) {
                LuaLocalDef localDef = (LuaLocalDef) curr;
                // 跳过类似
                // local name = name //skip
                if (!localDef.getNode().getTextRange().contains(element.getNode().getTextRange())) {
                    LuaNameList nameList = localDef.getNameList();
                    continueSearch = resolveInNameList(nameList, processor);
                }
            } else {
                //check if we can search in params
                //
                // local name
                // function(name) end //skip this `name`
                // name = nil
                //
                // for i, name in paris(name) do end // skip first `name`
                if (!searchParList && curr instanceof LuaBlock) {
                    LuaBlock block = (LuaBlock) curr;
                    searchParList = block.getNode().getStartOffset() <= curr.getNode().getStartOffset();
                }

                if (curr instanceof LuaFuncBody) {
                    //参数部分
                    if (searchParList) continueSearch = resolveInFuncBody((LuaFuncBody) curr, processor);
                }
                // for name = x, y do end
                else if (curr instanceof LuaForAStat) {
                    LuaForAStat forAStat = (LuaForAStat) curr;
                    if (searchParList) continueSearch = processor.accept(forAStat.getParamNameDef());
                }
                // for name in xxx do end
                else if (curr instanceof LuaForBStat) {
                    LuaForBStat forBStat = (LuaForBStat) curr;
                    if (searchParList) continueSearch = resolveInNameList(forBStat.getParamNameDefList(), processor);
                }
            }
        } while (continueSearch && !(curr instanceof PsiFile));
    }

    private static boolean resolveInFuncBody(LuaFuncBody funcBody, ElementProcessor<LuaNameDef> processor) {
        if (funcBody != null) {
            for (LuaParamNameDef parDef : funcBody.getParamNameDefList()) {
                if (!processor.accept(parDef)) return false;
            }
        }
        return true;
    }

    private static boolean resolveInNameList(LuaNameList nameList, ElementProcessor<LuaNameDef> processor) {
        if (nameList != null) {
            for (LuaNameDef nameDef : nameList.getNameDefList()) {
                if (!processor.accept(nameDef)) return false;
            }
        }
        return true;
    }

    private static boolean resolveInNameList(List<LuaParamNameDef> nameList, ElementProcessor<LuaNameDef> processor) {
        if (nameList != null) {
            for (LuaNameDef nameDef : nameList) {
                if (!processor.accept(nameDef)) return false;
            }
        }
        return true;
    }

    @Nullable
    public static <T extends PsiElement> T findElementOfClassAtOffset(@NotNull PsiFile file, int offset, @NotNull Class<T> clazz, boolean strictStart) {
        T t = PsiTreeUtil.findElementOfClassAtOffset(file, offset, clazz, strictStart);
        if (t == null)
            t = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, clazz, strictStart);
        return t;
    }

    public static <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass, @NotNull Class... skips) {
        if (element == null) {
            return null;
        } else {
            element = element.getParent();

            while(element != null && !aClass.isInstance(element) && PsiTreeUtil.instanceOf(element, skips)) {
                if (element instanceof PsiFile) {
                    return null;
                }

                element = element.getParent();
            }

            @SuppressWarnings("unchecked")
            T e = (T) element;
            return e;
        }
    }
}

@@ -3462,47 +3462,8 @@
 rue;
-%0A        boolean searchParList = false;
 %0A%0A  
@@ -3553,32 +3553,70 @@
 tPrevSibling();%0A
+            boolean isParent = false;%0A
             if (
@@ -3664,32 +3664,65 @@
 rr.getParent();%0A
+                isParent = true;%0A
             %7D%0A  
@@ -4205,533 +4205,23 @@
 lse 
-%7B%0A                //check if we can search in params%0A                //%0A                // local name%0A                // function(name) end //skip this %60name%60%0A                // name = nil%0A                //%0A                // for i, name in paris(name) do end // skip first %60name%60%0A                if (!searchParList && curr instanceof LuaBlock) %7B%0A                    LuaBlock block = (LuaBlock) curr;%0A                    searchParList = block.getNode().getStartOffset() %3C= curr.getNode().getStartOffset();%0A                %7D%0A
+if (isParent) %7B
 %0A   
@@ -4291,54 +4291,8 @@
     
- //%E5%8F%82%E6%95%B0%E9%83%A8%E5%88%86%0A                    if (searchParList)
  con
@@ -4553,35 +4553,16 @@
         
- if (searchParList)
  continu
@@ -4819,27 +4819,8 @@
     
- if (searchParList)
  con

optimize walkUpLocalNameDef()