/*
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jf.smalidea;

import com.google.common.collect.Lists;
import com.intellij.debugger.NoDataException;
import com.intellij.debugger.PositionManager;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.engine.DebugProcessListener;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.jdi.VirtualMachineProxy;
import com.intellij.debugger.engine.managerThread.DebuggerManagerThread;
import com.intellij.debugger.requests.RequestManager;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
import org.jf.dexlib2.Opcode;
import org.jf.smalidea.debugging.SmaliPositionManager;
import org.jf.smalidea.psi.impl.SmaliInstruction;
import org.junit.Assert;

import java.util.List;
import java.util.Map;

public class SmaliPositionManagerTest extends LightCodeInsightFixtureTestCase {
    private static final String testClass =
            ".class public Lmy/pkg/blah; .super Ljava/lang/Object;\n" +
                    ".method public getRandomParentType(I)I\n" +
                    "    .registers 4\n" +
                    "    .param p1, \"edge\"    # I\n" +
                    "\n" +
                    "    .prologue\n" +
                    "    const/4 v1, 0x2\n" +
                    "\n" +
                    "    .line 179\n" +
                    "    if-nez p1, :cond_5\n" +
                    "\n" +
                    "    move v0, v1\n" +
                    "\n" +
                    "    .line 185\n" +
                    "    :goto_4\n" +
                    "    return v0\n" +
                    "\n" +
                    "    .line 182\n" +
                    "    :cond_5\n" +
                    "    if-ne p1, v1, :cond_f\n" +
                    "\n" +
                    "    .line 183\n" +
                    "    sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" +
                    "\n" +
                    "    const/4 v1, 0x3\n" +
                    "\n" +
                    "    invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" +
                    "\n" +
                    "    move-result v0\n" +
                    "\n" +
                    "    goto :goto_4\n" +
                    "\n" +
                    "    .line 185\n" +
                    "    :cond_f\n" +
                    "    sget-object v0, Lorg/jf/Penroser/PenroserApp;->random:Ljava/util/Random;\n" +
                    "\n" +
                    "    invoke-virtual {v0, v1}, Ljava/util/Random;->nextInt(I)I\n" +
                    "\n" +
                    "    move-result v0\n" +
                    "\n" +
                    "    goto :goto_4\n" +
                    ".end method";

    public void testGetSourcePosition() throws NoDataException {
        myFixture.addFileToProject("my/pkg/blah.smali", testClass);

        SmaliPositionManager positionManager = new SmaliPositionManager(new MockDebugProcess());

        SourcePosition sourcePosition = positionManager.getSourcePosition(
                "my.pkg.blah", "getRandomParentType", "(I)I", 0);
        Assert.assertEquals(Opcode.CONST_4, ((SmaliInstruction)sourcePosition.getElementAt()).getOpcode());
        Assert.assertEquals(0, ((SmaliInstruction)sourcePosition.getElementAt()).getOffset());

        sourcePosition = positionManager.getSourcePosition("my.pkg.blah", "getRandomParentType", "(I)I", 10);
        Assert.assertEquals(Opcode.INVOKE_VIRTUAL, ((SmaliInstruction)sourcePosition.getElementAt()).getOpcode());
        Assert.assertEquals(20, ((SmaliInstruction)sourcePosition.getElementAt()).getOffset());
    }

    public void testGetAllClasses() throws NoDataException {
        myFixture.addFileToProject("my/pkg/blah.smali", testClass);

        SmaliPositionManager positionManager = new SmaliPositionManager(new MockDebugProcess());

        List<ReferenceType> classes = positionManager.getAllClasses(positionManager.getSourcePosition(
                "my.pkg.blah", "getRandomParentType", "(I)I", 0));
        Assert.assertEquals(1, classes.size());
        Assert.assertEquals("my.pkg.blah", classes.get(0).name());
    }

    private class MockDebugProcess implements DebugProcess {
        @Override public Project getProject() {
            return SmaliPositionManagerTest.this.getProject();
        }

        @Override public VirtualMachineProxy getVirtualMachineProxy() {
            return new VirtualMachineProxy() {
                @Override public List<ReferenceType> classesByName(final String s) {
                    return Lists.<ReferenceType>newArrayList(new MockReferenceType(s));
                }

                @Override public List<ReferenceType> allClasses() { return null; }
                @Override public boolean canGetBytecodes() { return false; }
                @Override public boolean versionHigher(String version) { return false; }
                @Override public boolean canWatchFieldModification() { return false; }
                @Override public boolean canWatchFieldAccess() { return false; }
                @Override public boolean canInvokeMethods() { return false; }
                @Override public DebugProcess getDebugProcess() { return null; }
                @Override public List<ReferenceType> nestedTypes(ReferenceType refType) { return null; }
            };
        }

        @Override public void addDebugProcessListener(DebugProcessListener listener) {}
        @Override public <T> T getUserData(Key<T> key) { return null; }
        @Override public <T> void putUserData(Key<T> key, T value) {}
        @Override public RequestManager getRequestsManager() { return null; }
        @Override public PositionManager getPositionManager() { return null; }
        @Override public void removeDebugProcessListener(DebugProcessListener listener) {}
        @Override public void appendPositionManager(PositionManager positionManager) {}
        @Override public void waitFor() {}
        @Override public void waitFor(long timeout) {}
        @Override public void stop(boolean forceTerminate) {}
        @Override public ExecutionResult getExecutionResult() { return null; }
        @Override public DebuggerManagerThread getManagerThread() { return null; }
        @Override public Value invokeMethod(EvaluationContext evaluationContext, ObjectReference objRef, Method method, List args) throws EvaluateException { return null; }
        @Override public Value invokeMethod(EvaluationContext evaluationContext, ClassType classType, Method method, List args) throws EvaluateException { return null; }
        @Override public Value invokeInstanceMethod(EvaluationContext evaluationContext, ObjectReference objRef, Method method, List args, int invocationOptions) throws EvaluateException { return null; }
        @Override public ReferenceType findClass(EvaluationContext evaluationContext, String name, ClassLoaderReference classLoader) throws EvaluateException { return null; }
        @Override public ArrayReference newInstance(ArrayType arrayType, int dimension) throws EvaluateException { return null; }
        @Override public ObjectReference newInstance(EvaluationContext evaluationContext, ClassType classType, Method constructor, List paramList) throws EvaluateException { return null; }
        @Override public boolean isAttached() { return false; }
        @Override public boolean isDetached() { return false; }
        @Override public boolean isDetaching() { return false; }
        @NotNull @Override public GlobalSearchScope getSearchScope() { return null; }
        @Override public void printToConsole(String text) {}
        @Override public ProcessHandler getProcessHandler() { return null; }
    }

    private static class MockReferenceType implements ReferenceType {
        private final String name;

        public MockReferenceType(String name) {
            this.name = name;
        }

        @Override public String name() {
            return name;
        }

        @Override public List<Field> allFields() { return null; }
        @Override public String genericSignature() { return null; }
        @Override public ClassLoaderReference classLoader() { return null; }
        @Override public String sourceName() throws AbsentInformationException { return null; }
        @Override public List<String> sourceNames(String s) throws AbsentInformationException { return null; }
        @Override public List<String> sourcePaths(String s) throws AbsentInformationException { return null; }
        @Override public String sourceDebugExtension() throws AbsentInformationException { return null; }
        @Override public boolean isStatic() { return false; }
        @Override public boolean isAbstract() { return false; }
        @Override public boolean isFinal() { return false; }
        @Override public boolean isPrepared() { return false; }
        @Override public boolean isVerified() { return false; }
        @Override public boolean isInitialized() { return false; }
        @Override public boolean failedToInitialize() { return false; }
        @Override public List<Field> fields() { return null; }
        @Override public List<Field> visibleFields() { return null; }
        @Override public Field fieldByName(String s) { return null; }
        @Override public List<Method> methods() { return null; }
        @Override public List<Method> visibleMethods() { return null; }
        @Override public List<Method> allMethods() { return null; }
        @Override public List<Method> methodsByName(String s) { return null; }
        @Override public List<Method> methodsByName(String s, String s1) { return null; }
        @Override public List<ReferenceType> nestedTypes() { return null; }
        @Override public Value getValue(Field field) { return null; }
        @Override public Map<Field, Value> getValues(List<? extends Field> list) { return null; }
        @Override public ClassObjectReference classObject() { return null; }
        @Override public List<Location> allLineLocations() throws AbsentInformationException { return null; }
        @Override public List<Location> allLineLocations(String s, String s1) throws AbsentInformationException { return null; }
        @Override public List<Location> locationsOfLine(int i) throws AbsentInformationException { return null; }
        @Override public List<Location> locationsOfLine(String s, String s1, int i) throws AbsentInformationException { return null; }
        @Override public List<String> availableStrata() { return null; }
        @Override public String defaultStratum() { return null; }
        @Override public List<ObjectReference> instances(long l) { return null; }
        @Override public int majorVersion() { return 0; }
        @Override public int minorVersion() { return 0; }
        @Override public int constantPoolCount() { return 0; }
        @Override public byte[] constantPool() { return new byte[0]; }
        @Override public int modifiers() { return 0; }
        @Override public boolean isPrivate() { return false; }
        @Override public boolean isPackagePrivate() { return false; }
        @Override public boolean isProtected() { return false; }
        @Override public boolean isPublic() { return false; }
        @Override public int compareTo(ReferenceType o) { return 0; }
        @Override public String signature() { return null; }
        @Override public VirtualMachine virtualMachine() { return null; }
    }
}
