/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 hsz Jakub Chrzanowski <[email protected]>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package mobi.hsz.idea.gitignore.psi;

import com.intellij.lang.Language;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.ParserDefinition;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.tree.IFileElementType;
import mobi.hsz.idea.gitignore.file.type.IgnoreFileType;
import mobi.hsz.idea.gitignore.lang.IgnoreLanguage;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Set;

/**
 * Base plugin file.
 *
 * @author Jakub Chrzanowski <[email protected]>
 * @since 0.8
 */
public class IgnoreFile extends PsiFileImpl {
    /** Current language. */
    @NotNull
    private final Language language;

    /** Current parser definition. */
    @NotNull
    private final ParserDefinition parserDefinition;

    /** Current file type. */
    @NotNull
    private final IgnoreFileType fileType;

    /** Builds a new instance of {@link IgnoreFile}. */
    public IgnoreFile(@NotNull FileViewProvider viewProvider, @NotNull IgnoreFileType fileType) {
        super(viewProvider);

        this.fileType = fileType;
        this.language = findLanguage(fileType.getLanguage(), viewProvider);

        final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(this.language);
        if (parserDefinition == null) {
            throw new RuntimeException(
                    "PsiFileBase: language.getParserDefinition() returned null for: " + this.language
            );
        }
        this.parserDefinition = parserDefinition;

        final IFileElementType nodeType = parserDefinition.getFileNodeType();
        init(nodeType, nodeType);
    }

    /**
     * Searches for the matching language in {@link FileViewProvider}.
     *
     * @param baseLanguage language to look for
     * @param viewProvider current {@link FileViewProvider}
     * @return matched {@link Language}
     */
    private static Language findLanguage(Language baseLanguage, FileViewProvider viewProvider) {
        final Set<Language> languages = viewProvider.getLanguages();

        for (final Language actualLanguage : languages) {
            if (actualLanguage.isKindOf(baseLanguage)) {
                return actualLanguage;
            }
        }

        for (final Language actualLanguage : languages) {
            if (actualLanguage instanceof IgnoreLanguage) {
                return actualLanguage;
            }
        }

        throw new AssertionError("Language " + baseLanguage + " doesn't participate in view provider " +
                viewProvider + ": " + new ArrayList<>(languages));
    }

    /**
     * Passes the element to the specified visitor.
     *
     * @param visitor the visitor to pass the element to.
     */
    @Override
    public void accept(@NotNull PsiElementVisitor visitor) {
        visitor.visitFile(this);
    }

    /**
     * Returns current language.
     *
     * @return current {@link Language}
     */
    @Override
    @NotNull
    public final Language getLanguage() {
        return language;
    }

    /**
     * Returns current parser definition.
     *
     * @return current {@link ParserDefinition}
     */
    @NotNull
    public ParserDefinition getParserDefinition() {
        return parserDefinition;
    }

    /**
     * Returns the file type for the file.
     *
     * @return the file type instance.
     */
    @NotNull
    @Override
    public FileType getFileType() {
        return fileType;
    }

    /**
     * Checks if current file is the language outer file.
     *
     * @return is outer file
     */
    public boolean isOuter() {
        final Set<VirtualFile> outerFiles = fileType.getIgnoreLanguage().getOuterFiles(getProject());
        return outerFiles.contains(getOriginalFile().getVirtualFile());
    }

    /**
     * Returns @{link IgnoreFileType} string interpretation.
     *
     * @return string interpretation
     */
    @Override
    public String toString() {
        return fileType.getName();
    }
}