package com.inaka.lewis.issues;

import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.inaka.lewis.utils.Constants;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_APPLICATION;
import static com.android.SdkConstants.TAG_INTENT_FILTER;
import static com.android.xml.AndroidManifest.NODE_ACTION;
import static com.android.xml.AndroidManifest.NODE_CATEGORY;


public class LauncherActivityDetector extends ResourceXmlDetector implements Detector.XmlScanner {

    public static final Issue ISSUE_MISSING_LAUNCHER = Issue.create(
            "MissingLauncher",
            "Missing Launcher Activity",
            "This app should have an activity with a launcher intent.",
            Category.CORRECTNESS,
            5,
            Severity.WARNING,
            new Implementation(LauncherActivityDetector.class, Scope.MANIFEST_SCOPE));

    public static final Issue ISSUE_MORE_THAN_ONE_LAUNCHER = Issue.create(
            "MoreThanOneLauncher",
            "More than one Launcher Activity",
            "This app should have only one activity with a launcher intent.",
            Category.CORRECTNESS,
            5,
            Severity.WARNING,
            new Implementation(LauncherActivityDetector.class, Scope.MANIFEST_SCOPE));

    public static final Issue ISSUE_LAUNCHER_ACTIVITY_IN_LIBRARY = Issue.create(
            "LauncherActivityInLibrary",
            "Launcher Activity in library",
            "This library should not have activities with a launcher intent.",
            Category.CORRECTNESS,
            8,
            Severity.ERROR,
            new Implementation(LauncherActivityDetector.class, Scope.MANIFEST_SCOPE));

    /**
     * This will be true if the current file we're checking has at least one activity.
     */
    private boolean mHasActivity;

    /**
     * This will be true if there is a launcher activity
     */
    private boolean mHasLauncherActivity;

    /**
     * The location of the <application> tag
     */
    private Location mApplicationTagLocation;

    @Override
    public Collection<String> getApplicableElements() {
        List<String> elements = new ArrayList<String>();
        elements.add(TAG_ACTIVITY);
        elements.add(TAG_APPLICATION);
        return elements;
    }

    @Override
    public void beforeCheckProject(@NonNull Context context) {
        mHasActivity = false;
        mHasLauncherActivity = false;
        mApplicationTagLocation = null;
    }

    @Override
    public void afterCheckProject(@NonNull Context context) {

        // if it's not a library, it's an application
        if (context.getProject() == context.getMainProject() && !context.getMainProject().isLibrary() && mApplicationTagLocation != null) {

            if (!mHasActivity) {
                context.report(ISSUE_MISSING_LAUNCHER, mApplicationTagLocation,
                        "Expecting " + ANDROID_MANIFEST_XML + " to have an <" + TAG_ACTIVITY + "> tag.");
            } else if (!mHasLauncherActivity) {
                context.report(ISSUE_MISSING_LAUNCHER, mApplicationTagLocation,
                        "Expecting " + ANDROID_MANIFEST_XML + " to have an activity with a launcher intent.");
            }

        }
    }

    @Override
    public void visitElement(XmlContext context, Element element) {
        if (isMainActivity(context, element)) {
            mHasLauncherActivity = true;
        }
    }

    /**
     * Returns true if the XML node is an activity with a launcher intent.
     *
     * @param node is the node to check.
     * @return true if the node is an activity with a launcher intent, false if not.
     */
    private boolean isMainActivity(XmlContext context, Node node) {

        if (TAG_APPLICATION.equals(node.getNodeName())) {
            mApplicationTagLocation = context.getLocation(node);
        }

        if (TAG_ACTIVITY.equals(node.getNodeName())) {

            mHasActivity = true;

            for (Element activityChild : LintUtils.getChildren(node)) {
                if (TAG_INTENT_FILTER.equals(activityChild.getNodeName())) {

                    boolean hasLauncherCategory = false;
                    boolean hasMainAction = false;

                    for (Element intentFilterChild : LintUtils.getChildren(activityChild)) {
                        // Check for category tag)
                        if (NODE_CATEGORY.equals(intentFilterChild.getNodeName())
                                && Constants.CATEGORY_NAME_LAUNCHER.equals(
                                intentFilterChild.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
                            hasLauncherCategory = true;
                        }
                        // Check for action tag
                        if (NODE_ACTION.equals(intentFilterChild.getNodeName())
                                && Constants.ACTION_NAME_MAIN.equals(
                                intentFilterChild.getAttributeNS(ANDROID_URI, ATTR_NAME))) {
                            hasMainAction = true;
                        }
                    }

                    if (hasLauncherCategory && hasMainAction) {
                        if (mHasLauncherActivity) {
                            context.report(ISSUE_MORE_THAN_ONE_LAUNCHER, context.getLocation(node),
                                    "Expecting " + ANDROID_MANIFEST_XML + " to have only one activity with a launcher intent.");
                        }

                        // if it is a library
                        if (context.getProject() == context.getMainProject() && context.getMainProject().isLibrary()) {
                            context.report(ISSUE_LAUNCHER_ACTIVITY_IN_LIBRARY, context.getLocation(node),
                                    "Expecting " + ANDROID_MANIFEST_XML + " not to have an activity with a launcher intent.");
                        }

                        return true;
                    }
                }
            }
        }
        return false;
    }
}