/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * 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.jetbrains.kotlin.idea.debugger.breakpoints;

import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
import org.jetbrains.kotlin.psi.KtClassInitializer;
import org.jetbrains.kotlin.psi.KtFunction;

import java.util.List;

public class KotlinLineBreakpointType extends JavaLineBreakpointType {
    public KotlinLineBreakpointType() {
        super("kotlin-line", "Kotlin Line Breakpoints");
    }

    @Override
    public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
        JavaBreakpointProperties properties = getProperties(breakpoint);
        if (properties == null || properties instanceof JavaLineBreakpointProperties) {
            if (position instanceof KotlinPositionManager.KotlinReentrantSourcePosition) {
                return false;
            }

            if (properties != null && ((JavaLineBreakpointProperties) properties).getLambdaOrdinal() == null) return true;

            PsiElement containingMethod = getContainingMethod(breakpoint);
            if (containingMethod == null) return false;
            return inTheMethod(position, containingMethod);
        }

        return true;
    }

    @Override
    @Nullable
    public PsiElement getContainingMethod(@NotNull LineBreakpoint<?> breakpoint) {
        SourcePosition position = breakpoint.getSourcePosition();
        if (position == null) return null;

        JavaBreakpointProperties properties = getProperties(breakpoint);
        if (properties instanceof JavaLineBreakpointProperties) {
            Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal();
            PsiElement lambda = getLambdaByOrdinal(position, ordinal);
            if (lambda != null) return lambda;
        }

        return getContainingMethod(position.getElementAt());
    }


    @Nullable
    private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint<?> breakpoint) {
        XBreakpoint<?> xBreakpoint = breakpoint.getXBreakpoint();
        return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null;
    }

    @Nullable
    private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) {
        if (ordinal != null && ordinal >= 0) {
            List<KtFunction> lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position);
            if (lambdas.size() > ordinal) {
                return lambdas.get(ordinal);
            }
        }
        return null;
    }

    @Nullable
    public static PsiElement getContainingMethod(@Nullable PsiElement elem) {
        //noinspection unchecked
        return PsiTreeUtil.getParentOfType(elem, KtFunction.class, KtClassInitializer.class);
    }

    public static boolean inTheMethod(@NotNull SourcePosition pos, @NotNull PsiElement method) {
        PsiElement elem = pos.getElementAt();
        if (elem == null) return false;
        return Comparing.equal(getContainingMethod(elem), method);
    }

    @Override
    public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
        return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
    }

    @NotNull
    @Override
    public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
        return BreakpointTypeUtilsKt.computeVariants(project, position, this);
    }

    @Nullable
    @Override
    public TextRange getHighlightRange(XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
        JavaLineBreakpointProperties properties = breakpoint.getProperties();
        if (properties != null) {
            Integer ordinal = properties.getLambdaOrdinal();
            if (ordinal != null) {
                Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
                if (javaBreakpoint instanceof LineBreakpoint) {
                    SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition();
                    if (position != null) {
                        KtFunction lambda = getLambdaByOrdinal(position, ordinal);
                        if (lambda != null) {
                            return lambda.getTextRange();
                        }
                    }
                }
            }
        }
        return null;
    }
}
