/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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 com.android.dexgen.dex.file;

import com.android.dexgen.dex.code.DalvCode;
import com.android.dexgen.rop.code.AccessFlags;
import com.android.dexgen.rop.cst.CstMethodRef;
import com.android.dexgen.rop.cst.CstUtf8;
import com.android.dexgen.rop.type.TypeList;
import com.android.dexgen.util.AnnotatedOutput;
import com.android.dexgen.util.Hex;
import com.android.dexgen.util.Leb128Utils;

import java.io.PrintWriter;

/**
 * Class that representats a method of a class.
 */
public final class EncodedMethod extends EncodedMember
        implements Comparable<EncodedMethod> {
    /** {@code non-null;} constant for the method */
    private final CstMethodRef method;

    /**
     * {@code null-ok;} code for the method, if the method is neither
     * {@code abstract} nor {@code native}
     */
    private final CodeItem code;

    /**
     * Constructs an instance.
     *
     * @param method {@code non-null;} constant for the method
     * @param accessFlags access flags
     * @param code {@code null-ok;} code for the method, if it is neither
     * {@code abstract} nor {@code native}
     * @param throwsList {@code non-null;} list of possibly-thrown exceptions,
     * just used in generating debugging output (listings)
     */
    public EncodedMethod(CstMethodRef method, int accessFlags,
            DalvCode code, TypeList throwsList) {
        super(accessFlags);

        if (method == null) {
            throw new NullPointerException("method == null");
        }

        this.method = method;

        if (code == null) {
            this.code = null;
        } else {
            boolean isStatic = (accessFlags & AccessFlags.ACC_STATIC) != 0;
            this.code = new CodeItem(method, code, isStatic, throwsList);
        }
    }

    /** {@inheritDoc} */
    public boolean equals(Object other) {
        if (! (other instanceof EncodedMethod)) {
            return false;
        }

        return compareTo((EncodedMethod) other) == 0;
    }

    /**
     * {@inheritDoc}
     *
     * <p><b>Note:</b> This compares the method constants only,
     * ignoring any associated code, because it should never be the
     * case that two different items with the same method constant
     * ever appear in the same list (or same file, even).</p>
     */
    public int compareTo(EncodedMethod other) {
        return method.compareTo(other.method);
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(100);

        sb.append(getClass().getName());
        sb.append('{');
        sb.append(Hex.u2(getAccessFlags()));
        sb.append(' ');
        sb.append(method);

        if (code != null) {
            sb.append(' ');
            sb.append(code);
        }

        sb.append('}');

        return sb.toString();
    }

    /** {@inheritDoc} */
    @Override
    public void addContents(DexFile file) {
        MethodIdsSection methodIds = file.getMethodIds();
        MixedItemSection wordData = file.getWordData();

        methodIds.intern(method);

        if (code != null) {
            wordData.add(code);
        }
    }

    /** {@inheritDoc} */
    public final String toHuman() {
        return method.toHuman();
    }

    /** {@inheritDoc} */
    @Override
    public final CstUtf8 getName() {
        return method.getNat().getName();
    }

    /** {@inheritDoc} */
    @Override
    public void debugPrint(PrintWriter out, boolean verbose) {
        if (code == null) {
            out.println(getRef().toHuman() + ": abstract or native");
        } else {
            code.debugPrint(out, "  ", verbose);
        }
    }

    /**
     * Gets the constant for the method.
     *
     * @return {@code non-null;} the constant
     */
    public final CstMethodRef getRef() {
        return method;
    }

    /** {@inheritDoc} */
    @Override
    public int encode(DexFile file, AnnotatedOutput out,
            int lastIndex, int dumpSeq) {
        int methodIdx = file.getMethodIds().indexOf(method);
        int diff = methodIdx - lastIndex;
        int accessFlags = getAccessFlags();
        int codeOff = OffsettedItem.getAbsoluteOffsetOr0(code);
        boolean hasCode = (codeOff != 0);
        boolean shouldHaveCode = (accessFlags &
                (AccessFlags.ACC_ABSTRACT | AccessFlags.ACC_NATIVE)) == 0;

        /*
         * Verify that code appears if and only if a method is
         * declared to have it.
         */
        if (hasCode != shouldHaveCode) {
            throw new UnsupportedOperationException(
                    "code vs. access_flags mismatch");
        }

        if (out.annotates()) {
            out.annotate(0, String.format("  [%x] %s", dumpSeq,
                            method.toHuman()));
            out.annotate(Leb128Utils.unsignedLeb128Size(diff),
                    "    method_idx:   " + Hex.u4(methodIdx));
            out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags),
                    "    access_flags: " +
                    AccessFlags.methodString(accessFlags));
            out.annotate(Leb128Utils.unsignedLeb128Size(codeOff),
                    "    code_off:     " + Hex.u4(codeOff));
        }

        out.writeUnsignedLeb128(diff);
        out.writeUnsignedLeb128(accessFlags);
        out.writeUnsignedLeb128(codeOff);

        return methodIdx;
    }
}
