// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package org.jetbrains.kotlin.idea.configuration;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaModule;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.idea.compiler.configuration.IdeKotlinVersion;
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase;
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.idea.test.KotlinTestUtils;
import org.jetbrains.plugins.groovy.GroovyFileType;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

@SuppressWarnings("deprecation")
public abstract class AbstractConfigureProjectByChangingFileTest<C extends KotlinProjectConfigurator>
        extends KotlinLightCodeInsightTestCase {
    private PsiFile moduleInfoFile;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ApplicationManager.getApplication().runWriteAction(
                () -> FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle")
        );
    }

    @Override
    protected void tearDown() throws Exception {
        moduleInfoFile = null;
        super.tearDown();
    }

    protected void doTest(@NotNull String beforeFile, @NotNull String afterFile, @NotNull C configurator) throws Exception {
        configureByFile(beforeFile);

        prepareModuleInfoFile(beforeFile);

        String rawVersion = InTextDirectivesUtils.findStringWithPrefixes(getFile().getText(), "// VERSION:");
        assert rawVersion != null : "Directive with configured Kotlin version ('//VERSION: ') should be specified";

        IdeKotlinVersion version = IdeKotlinVersion.get(rawVersion);

        NotificationMessageCollector collector = NotificationMessageCollector.create(getProject());

        runConfigurator(getModule(), getFile(), configurator, version, JvmTarget.JVM_1_8.getDescription(), collector);

        collector.showNotification();

        KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), afterFile), getFile().getText());

        checkModuleInfoFile(beforeFile);
    }

    private void prepareModuleInfoFile(@NotNull String beforeFile) throws IOException {
        File file = new File(beforeFile);
        String parent = file.getParent();
        File moduleInfo = new File(parent, PsiJavaModule.MODULE_INFO_FILE);
        moduleInfoFile = null;
        if (moduleInfo.exists()) {
            String fileText = FileUtilRt.loadFile(moduleInfo, CharsetToolkit.UTF8, true);
            createAndSaveFile(PsiJavaModule.MODULE_INFO_FILE, fileText);

            PsiFile[] moduleInfoFiles =
                    FilenameIndex.getFilesByName(getProject(), PsiJavaModule.MODULE_INFO_FILE, GlobalSearchScope.allScope(getProject()));
            assertEquals(PsiJavaModule.MODULE_INFO_FILE + " should be present in index", 1, moduleInfoFiles.length);
            moduleInfoFile = moduleInfoFiles[0];
        }
    }

    private void checkModuleInfoFile(@NotNull String beforeFile) {
        File file = new File(beforeFile);
        String parent = file.getParent();

        if (moduleInfoFile != null) {
            String afterFileName = PsiJavaModule.MODULE_INFO_FILE.replace(".java", "_after.java");
            commitAllDocuments();
            KotlinTestUtils.assertEqualsToFile(new File(parent, afterFileName), moduleInfoFile.getText());
        }
    }

    protected abstract void runConfigurator(
            Module module, @NotNull PsiFile file,
            @NotNull C configurator,
            @NotNull IdeKotlinVersion version,
            @NotNull String jvmTarget,
            @NotNull NotificationMessageCollector collector
    );

    @NotNull
    @Override
    protected LightProjectDescriptor getProjectDescriptor() {
        return new SimpleLightProjectDescriptor(getModuleTypeId(), getProjectJDK());
    }

    private static class SimpleLightProjectDescriptor extends LightProjectDescriptor {
        @NotNull private final String myModuleTypeId;
        @Nullable private final Sdk mySdk;

        SimpleLightProjectDescriptor(@NotNull String moduleTypeId, @Nullable Sdk sdk) {
            myModuleTypeId = moduleTypeId;
            mySdk = sdk;
        }

        @NotNull
        @Override
        public String getModuleTypeId() {
            return myModuleTypeId;
        }

        @Nullable
        @Override
        public Sdk getSdk() {
            return mySdk;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SimpleLightProjectDescriptor that = (SimpleLightProjectDescriptor)o;

            if (!myModuleTypeId.equals(that.myModuleTypeId)) return false;
            return areJdksEqual(that.getSdk());
        }

        @Override
        public int hashCode() {
            return myModuleTypeId.hashCode();
        }

        private boolean areJdksEqual(Sdk newSdk) {
            if (mySdk == null || newSdk == null) return mySdk == newSdk;

            if (!Objects.equals(mySdk.getVersionString(), newSdk.getVersionString())) {
                return false;
            }

            String[] myUrls = mySdk.getRootProvider().getUrls(OrderRootType.CLASSES);
            String[] newUrls = newSdk.getRootProvider().getUrls(OrderRootType.CLASSES);
            return ContainerUtil.newHashSet(myUrls).equals(ContainerUtil.newHashSet(newUrls));
        }
    }
}
