/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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 com.android.ide.eclipse.adt.internal.editors.xml;

import static com.android.sdklib.SdkConstants.FD_SOURCES;

import com.android.ide.common.resources.ResourceFile;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.ResourceLink;
import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.XmlResolver;

import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.internal.ErrorEditorPart;
import org.eclipse.ui.internal.browser.WebBrowserEditor;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@SuppressWarnings("restriction")
public class HyperlinksTest extends AdtProjectTest {
    @Override
    protected boolean testCaseNeedsUniqueProject() {
        return true;
    }

    public void testFqnRegexp() throws Exception {
        assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo").matches());
        assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.pk_g.Foo_Bar1").
                matches());
        assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo$Inner").matches());

        // Should we allow non-standard packages and class names?
        // For now, we're allowing it -- see how this works out in practice.
        //assertFalse(XmlHyperlinkResolver.CLASS_PATTERN.matcher("Foo.bar").matches());
        assertTrue(Hyperlinks.CLASS_PATTERN.matcher("Foo.bar").matches());

        assertFalse(Hyperlinks.CLASS_PATTERN.matcher("LinearLayout").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".F").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher("f.").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher("Foo").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher("com.android.1Foo").matches());
        assertFalse(Hyperlinks.CLASS_PATTERN.matcher("1com.Foo").matches());
    }

    public void testNavigate1() throws Exception {
        // Check navigating to a local resource
        checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
                "android:text=\"@string/app^_name\"");
    }

    public void testNavigate2() throws Exception {
        // Check navigating to a framework resource
        checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
                "marginLeft=\"@android:dimen/app_ico^n_size\"");
    }

    public void testNavigate3() throws Exception {
        // Check navigating to a style
        checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
                "style=\"@android:style/Widget.B^utton\"");
    }

    public void testNavigate4() throws Exception {
        // Check navigating to resource with many resolutions
        checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
                "android:text=\"@android:st^ring/ok\"");
    }

    public void testNavigate5() throws Exception {
        // Check navigating to styles
        checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml",
                "parent=\"android:Theme.Li^ght\">");
    }

    public void testNavigate6() throws Exception {
        // Check navigating to a portion of a style (this should pick android:Theme, not
        // android:Theme.Light
        checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml",
                "parent=\"android:The^me.Light\">");
    }

    public void testNavigate7() throws Exception {
        // Check navigating to a resource inside text content
        checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml",
                "popupBackground\">@android:drawable/spinner_dr^opdown_background</item>");
    }

    public void testNavigate8() throws Exception {
        // Check navigating to a resource inside text content where there is space around
        // the URL
        checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml",
                "colorBackground\"> @color/cust^om_theme_color </item>");
    }

    public void testNavigate9a() throws Exception {
        // Check navigating to a an activity
        checkXmlNavigation("manifest.xml", "AndroidManifest.xml",
                "<activity android:name=\".Test^Activity\"");
    }

    /* Not yet implemented
    public void testNavigate9b() throws Exception {
        // Check navigating to a an activity - clicking on the activity element should
        // work too
        checkXmlNavigation("manifest.xml", "AndroidManifest.xml",
                "<acti^vity android:name=\".TestActivity\"");
    }
    */

    public void testNavigate10() throws Exception {
        // Check navigating to a permission
        checkXmlNavigation("manifest.xml", "AndroidManifest.xml",
                "<uses-permission android:name=\"android.permission.AC^CESS_NETWORK_STATE\" />");
    }

    public void testNavigate11a() throws Exception {
        // Check navigating to an intent
        checkXmlNavigation("manifest.xml", "AndroidManifest.xml",
                "<action android:name=\"android.intent.ac^tion.MAIN\" />");
    }

    public void testNavigate11g() throws Exception {
        // Check navigating to an intent
        checkXmlNavigation("manifest.xml", "AndroidManifest.xml",
                "<category android:name=\"android.intent.category.LA^UNCHER\" />");
    }

    public void testNavigate12() throws Exception {
        // Check navigating to a custom view class
        checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
                "<my.Cust^omView></my.CustomView>");
    }

    public void testNavigate13() throws Exception {
        // Check jumping to classes pointed to by fragments

        getTestDataFile(getProject(), "TestFragment.java.txt",
                FD_SOURCES + "/" + TEST_PROJECT_PACKAGE.replace('.', '/') + "/TestFragment.java");
        checkXmlNavigation("fragmentlayout.xml", "res/layout/fragmentlayout.xml",
                "android:name=\"com.android.ecl^ipse.tests.TestFragment\"");
    }

    public void testNavigate14() throws Exception {
        // Check jumping to classes pointed to by fragments

        getTestDataFile(getProject(), "TestFragment.java.txt",
                FD_SOURCES + "/" + TEST_PROJECT_PACKAGE.replace('.', '/') + "/TestFragment.java");
        checkXmlNavigation("fragmentlayout.xml", "res/layout/fragmentlayout.xml",
                "class=\"com.and^roid.eclipse.tests.TestFragment\"");
    }

    // Left to test:
    // onClick handling
    // class attributes
    // Test that the correct file is actually opened!

    private void checkXmlNavigation(String basename, String targetPath,
            String caretLocation) throws Exception {
        IFile file = getTestDataFile(getProject(), basename, targetPath, true);

        // Determine the offset
        int offset = getCaretOffset(file, caretLocation);

        // Open file
        IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
        assertNotNull(page);
        IEditorPart editor = IDE.openEditor(page, file);
        assertTrue(editor.getClass().getName(), editor instanceof AndroidXmlEditor);
        AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor;
        ISourceViewer viewer = layoutEditor.getStructuredSourceViewer();

        XmlResolver resolver = new Hyperlinks.XmlResolver();
        IHyperlink[] links = resolver.detectHyperlinks(viewer, new Region(offset, 0), true);
        assertNotNull(links);

        StringBuilder sb = new StringBuilder(1000);
        sb.append("Go To Declaration in " + basename + " for " + caretLocation + ":\n");
        for (IHyperlink link : links) {
            sb.append(link.getHyperlinkText());
            sb.append(" : ");
            sb.append(" [");
            IRegion region = link.getHyperlinkRegion();
            sb.append(viewer.getDocument().get(region.getOffset(), region.getLength()));
            sb.append("]");
            if (link instanceof Hyperlinks.ResourceLink) {
                Hyperlinks.ResourceLink resourceLink = (ResourceLink) link;
                sb.append("\n    ");
                ResourceFile resourceFile = resourceLink.getFile();
                String desc = resourceFile.toString();
                desc = desc.replace('\\', '/');
                // For files in the SDK folder, strip out file prefix
                int dataRes = desc.indexOf("data/res");
                if (dataRes != -1) {
                    desc = desc.substring(dataRes);
                }
                desc = removeSessionData(desc);
                sb.append(desc);
            }
            sb.append('\n');
        }

        // Open the first link
        IHyperlink link = links[0];
        link.open();
        IEditorPart newEditor = AdtUtils.getActiveEditor();
        // Ensure that this isn't an invalid file (e.g. opening the SDK platform files
        // with incorrect content binding could cause this)
        assertTrue(!(newEditor instanceof ErrorEditorPart));

        IDocument document = null;
        Point selection = null;

        if (newEditor instanceof AndroidXmlEditor) {
            AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor;
            document = xmlEditor.getStructuredSourceViewer().getDocument();
            selection = xmlEditor.getStructuredSourceViewer().getSelectedRange();
        } else if (newEditor instanceof XMLMultiPageEditorPart) {
            XMLMultiPageEditorPart xmlEditor = (XMLMultiPageEditorPart) newEditor;
            Field field = xmlEditor.getClass().getDeclaredField("fTextEditor");
            field.setAccessible(true);
            StructuredTextEditor ste = (StructuredTextEditor) field.get(newEditor);
            if (ste == null) {
                Method method = xmlEditor.getClass().getMethod("getTextEditor", new Class[0]);
                ste = (StructuredTextEditor) method.invoke(newEditor, new Object[0]);
            }
            StructuredTextViewer textViewer = ste.getTextViewer();
            document = textViewer.getDocument();
            selection = textViewer.getSelectedRange();
        } else if (newEditor instanceof WebBrowserEditor) {
            WebBrowserEditor browser = (WebBrowserEditor) newEditor;
            Field field = browser.getClass().getDeclaredField("initialURL");
            field.setAccessible(true);
            String initialUrl = (String) field.get(newEditor);
            int index = initialUrl.indexOf("reference");
            if (index != -1) {
                initialUrl = initialUrl.substring(index);
            }
            initialUrl = initialUrl.replace('\\', '/');
            sb.append("\n\nAfter open, a browser is shown with this URL:\n");
            sb.append("  ");
            sb.append(initialUrl);
            sb.append("\n");
        } else if (newEditor instanceof JavaEditor) {
            JavaEditor javaEditor = (JavaEditor) newEditor;
            document = javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
            IRegion range = javaEditor.getHighlightRange();
            selection = new Point(range.getOffset(), range.getLength());
        } else {
            fail("Unhandled editor type: " + newEditor.getClass().getName());
            return;
        }

        if (document != null && selection != null) {
            int lineStart = document.getLineInformationOfOffset(selection.x).getOffset();
            IRegion lineEndInfo = document.getLineInformationOfOffset(selection.x + selection.y);
            int lineEnd = lineEndInfo.getOffset() + lineEndInfo.getLength();
            String text = document.get(lineStart, lineEnd - lineStart);
            int selectionStart = selection.x - lineStart;
            int selectionEnd = selectionStart + selection.y;
            if (selectionEnd > selectionStart) {
                // Selection range
                text = text.substring(0, selectionStart) + "[^" +
                   text.substring(selectionStart, selectionEnd) + "]" +
                   text.substring(selectionEnd);
            } else {
                text = text.substring(0, selectionStart) + "^" +
                    text.substring(selectionStart);
            }
            text = removeSessionData(text);

            sb.append("\n\nAfter open, the selected text is:\n");
            sb.append(text);
            sb.append("\n");
        }

        assertEqualsGolden(basename, sb.toString(), "txt");
    }
}
