/*
 * 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.ktSignature;

import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.load.java.components.PsiBasedExternalAnnotationResolver;

import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.KOTLIN_SIGNATURE;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.OLD_KOTLIN_SIGNATURE;

class KotlinSignatureUtil {
    private KotlinSignatureUtil() {
    }

    /* For element from library sources or decompiled library sources return corresponding cls element */
    @Nullable
    static PsiMember getAnalyzableAnnotationOwner(@NotNull PsiElement element) {
        if (!(element instanceof PsiField || element instanceof PsiMethod) || element instanceof PsiEnumConstant) {
            return null;
        }

        PsiMember member = (PsiMember) element;
        if (member.hasModifierProperty(PsiModifier.PRIVATE)) {
            return null;
        }

        PsiClass containingClass = member.getContainingClass();
        if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass)) {
            return null;
        }

        PsiMember annotationOwner = element.getOriginalElement() instanceof PsiMember
                                    ? (PsiMember) element.getOriginalElement()
                                    : (PsiMember) element;

        if (!annotationOwner.isPhysical()) {
            // this is fake PsiFile which is mirror for ClsFile without sources
            PsiCompiledElement compiledElement = element.getUserData(ClsElementImpl.COMPILED_ELEMENT);
            if (compiledElement instanceof PsiMember) {
                return (PsiMember) compiledElement;
            }
        }
        return annotationOwner;
    }

    @NotNull
    static String getKotlinSignature(@NotNull PsiAnnotation kotlinSignatureAnnotation) {
        PsiNameValuePair pair = kotlinSignatureAnnotation.getParameterList().getAttributes()[0];
        PsiAnnotationMemberValue value = pair.getValue();
        if (value == null) {
            return "null";
        }
        else if (value instanceof PsiLiteralExpression) {
            Object valueObject = ((PsiLiteralExpression) value).getValue();
            return valueObject == null ? "null" : StringUtil.unescapeStringCharacters(valueObject.toString());
        }
        else {
            return value.getText();
        }
    }

    @NotNull
    static PsiNameValuePair[] signatureToNameValuePairs(@NotNull Project project, @NotNull String signature) {
        return JavaPsiFacade.getElementFactory(project).createAnnotationFromText(
                "@" + KOTLIN_SIGNATURE + "(value=\"" + StringUtil.escapeStringCharacters(signature) + "\")", null)
                .getParameterList().getAttributes();
    }

    @Nullable
    static PsiAnnotation findKotlinSignatureAnnotation(@NotNull PsiElement element) {
        if (!(element instanceof PsiModifierListOwner)) return null;
        PsiModifierListOwner annotationOwner = getAnalyzableAnnotationOwner(element);
        if (annotationOwner == null) {
            return null;
        }

        PsiModifierList list = annotationOwner.getModifierList();
        PsiAnnotation annotation = list == null ? null : list.findAnnotation(KOTLIN_SIGNATURE.asString());
        if (annotation == null) {
            annotation = PsiBasedExternalAnnotationResolver.findExternalAnnotation(annotationOwner, KOTLIN_SIGNATURE);
            if (annotation == null) {
                annotation = list == null ? null : list.findAnnotation(OLD_KOTLIN_SIGNATURE.asString());
                if (annotation == null) {
                    annotation = PsiBasedExternalAnnotationResolver.findExternalAnnotation(annotationOwner, OLD_KOTLIN_SIGNATURE);
                }
            }
        }

        if (annotation == null) return null;
        if (annotation.getParameterList().getAttributes().length == 0) return null;
        return annotation;
    }

    static void refreshMarkers(@NotNull Project project) {
        DaemonCodeAnalyzer.getInstance(project).restart();
    }

    static boolean isAnnotationEditable(@NotNull PsiElement element) {
        PsiModifierListOwner annotationOwner = getAnalyzableAnnotationOwner(element);
        if (annotationOwner == null) {
            return false;
        }
        PsiAnnotation annotation = findKotlinSignatureAnnotation(element);
        assert annotation != null : "Annotation not found for " + element.getText();
        if (annotation.getContainingFile() == annotationOwner.getContainingFile()) {
            return annotation.isWritable();
        } else {
            ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(element.getProject());
            //noinspection ConstantConditions
            return annotationsManager.isExternalAnnotationWritable(annotationOwner, annotation.getQualifiedName());
        }
    }
}
