/*
 * Copyright (C) 2019 Stefan Zeller
 *
 * 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 ch.docksnet.rgraph;

import ch.docksnet.rgraph.method.ReferenceNode;
import ch.docksnet.utils.lcom.LCOMNode;
import com.intellij.diagram.DiagramEdge;
import com.intellij.diagram.DiagramNode;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassInitializer;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.impl.file.PsiJavaDirectoryImpl;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Stefan Zeller
 */
class LCOMConverter {

    private final ReferenceDiagramVfsResolver vfsResolver = new ReferenceDiagramVfsResolver();
    private final Map<String, LCOMNode> lcomNodeRegistry = new HashMap<>();

    /**
     * Returns nodes representating a directed graph regarding to given {@code nodes} and {@code edges}.
     */
    Collection<LCOMNode> convert(Collection<? extends DiagramNode<PsiElement>> nodes, Collection<? extends
            DiagramEdge<PsiElement>> edges) {
        for (DiagramNode<PsiElement> node : nodes) {
            String fqn = this.vfsResolver.getQualifiedName(node.getIdentifyingElement());
            LCOMNode.Type type = resolveType(node);
            this.lcomNodeRegistry.put(fqn, new LCOMNode(fqn, type, (ReferenceNode) node));
        }

        for (DiagramEdge<PsiElement> edge : edges) {
            ReferenceNode source = (ReferenceNode) edge.getSource();
            ReferenceNode target = (ReferenceNode) edge.getTarget();
            String sourceFqn = this.vfsResolver.getQualifiedName(source.getIdentifyingElement());
            String targetFqn = this.vfsResolver.getQualifiedName(target.getIdentifyingElement());

            if (isSourceOrTargetNotRegistered(sourceFqn, targetFqn)) {
                continue;
            }

            this.lcomNodeRegistry.get(sourceFqn).addCallee(this.lcomNodeRegistry.get(targetFqn));
        }

        return this.lcomNodeRegistry.values();
    }

    private boolean isSourceOrTargetNotRegistered(String sourceFqn, String targetFqn) {
        return this.lcomNodeRegistry.get(sourceFqn) == null || this.lcomNodeRegistry.get(targetFqn) == null;
    }

    private LCOMNode.Type resolveType(DiagramNode<PsiElement> referenceNode) {
        PsiElementDispatcher<LCOMNode.Type> elementDispatcher = new PsiElementDispatcher<LCOMNode.Type>() {

            @Override
            public LCOMNode.Type processClass(PsiClass psiClass) {
                return LCOMNode.Type.Class;
            }

            @Override
            public LCOMNode.Type processMethod(PsiMethod psiMethod) {
                if (psiMethod.isConstructor()) {
                    return LCOMNode.Type.Constructur;
                } else {
                    return LCOMNode.Type.Method;
                }
            }

            @Override
            public LCOMNode.Type processField(PsiField psiField) {
                if (psiField.hasModifierProperty("static")) {
                    return LCOMNode.Type.Constant;
                } else {
                    return LCOMNode.Type.Field;
                }
            }

            @Override
            public LCOMNode.Type processClassInitializer(PsiClassInitializer psiClassInitializer) {
                return LCOMNode.Type.ClassInitializer;
            }

            @Override
            public LCOMNode.Type processInnerClass(PsiClass innerClass) {
                return LCOMNode.Type.InnerClass;
            }

            @Override
            public LCOMNode.Type processStaticInnerClass(PsiClass staticInnerClass) {
                return LCOMNode.Type.StaticInnerClass;
            }

            @Override
            public LCOMNode.Type processEnum(PsiClass anEnum) {
                return LCOMNode.Type.Enum;
            }

            @Override
            public LCOMNode.Type processPackage(PsiJavaDirectoryImpl aPackage) {
                return LCOMNode.Type.Package;
            }

            @Override
            public LCOMNode.Type processFile(PsiJavaFile psiElement) {
                return LCOMNode.Type.File;
            }
        };
        return elementDispatcher.dispatch(referenceNode.getIdentifyingElement());
    }

}
