/**
 * Copyright (c) 2004-2005, Regents of the University of California
 * 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 the University of California, Los Angeles 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 avrora.actions;

import avrora.Main;
import avrora.arch.*;
import avrora.arch.AbstractInstr;
import cck.text.StringUtil;
import cck.text.Terminal;
import cck.util.Option;
import cck.util.Util;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * The <code>DisassembleAction</code> class represents an action that allows the user to disassemble
 * a binary file and display the instructions. This is useful for debugging the disassembler and also
 * for inspecting binaries.
 *
 * @author Ben L. Titzer
 */
public class DisassembleAction extends Action {

    Option.Str ARCH = newOption("arch", "avr",
            "This option selects the architecture for the disassembler.");
    Option.Long MAX_LENGTH = newOption("max-length", 16,
            "This option specifies the maximum length of an instruction in bytes.");
    Option.Bool EXHAUSTIVE = newOption("exhaustive", false,
            "When this option is specified, this action will test the disassembler exhaustively by " +
            "trying bit patterns systematically.");
    Option.Str FILE = newOption("file", "",
            "When this option is specified, this action will test the disassembler by loading the " +
            "specified file and disassembling the data contained inside.");

    public DisassembleAction() {
        super("The \"disassemble\" action disassembles a binary file into source level instructions.");
    }

    /**
     * The <code>run()</code> method executes the action. The arguments on the command line are passed.
     * The <code>Disassemble</code> action expects the first argument to be the name of the file to
     * disassemble.
     * @param args the command line arguments
     * @throws Exception if there is a problem reading the file or disassembling the instructions in the
     * file
     */
    public void run(String[] args) throws Exception {
        byte[] buf = new byte[128];

        AbstractArchitecture arch = ArchitectureRegistry.getArchitecture(ARCH.get());
        AbstractDisassembler da = arch.getDisassembler();
        if ( EXHAUSTIVE.get() ) {
            // run the exhaustive disassembler
            exhaustive(da);
        } else if ( !FILE.isBlank() ) {
            // load and disassemble a complete file
            disassembleFile(da);
        } else {
            // disassemble the bytes specified on the command line
            disassembleArguments(args, buf, da);
        }
    }

    private void disassembleArguments(String[] args, byte[] buf, AbstractDisassembler da) {
        if ( args.length < 1 )
            Util.userError("no input data");
        for ( int cntr = 0; cntr < args.length; cntr++ ) {
            buf[cntr] = (byte)StringUtil.evaluateIntegerLiteral(args[cntr]);
        }

        disassembleAndPrint(buf, 0, da);
    }

    private void disassembleFile(AbstractDisassembler da) throws IOException {
        String fname = FILE.get();
        Main.checkFileExists(fname);
        FileInputStream fis = new FileInputStream(fname);
        byte[] buf;
        try {
            buf = new byte[fis.available()];
            fis.read(buf);
        } finally {
            fis.close();
        }
        for ( int cntr = 0; cntr < buf.length; ) {
            cntr += disassembleAndPrint(buf, cntr, da);
        }
    }

    void exhaustive(AbstractDisassembler da) {
        byte[] buf = new byte[(int)MAX_LENGTH.get()];
        for ( int cntr = 0; cntr < 0x10000; cntr++ ) {
            buf[0] = (byte)cntr;
            buf[1] = (byte)(cntr >> 8);
            try {
                disassembleAndPrint(buf, 0, da);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private int disassembleAndPrint(byte[] buf, int off, AbstractDisassembler da) {
        String result;
        int len = 2;
        AbstractInstr instr = da.disassemble(0, off, buf);
        if ( instr == null ) result = "null";
        else {
            result = instr.toString();
            len = instr.getSize();
        }
        print(buf, off, len, result);
        return len;
    }

    private void print(byte[] buf, int off, int len, String str) {
        StringBuffer sbuf = new StringBuffer();
        sbuf.append(StringUtil.addrToString(off));
        sbuf.append(": ");
        for ( int cntr = 0; cntr < len; cntr++ ) {
            StringUtil.toHex(sbuf, buf[off+cntr], 2);
            sbuf.append(' ');
        }
        for ( int cntr = sbuf.length(); cntr < 30; cntr++ ) sbuf.append(' ');
        sbuf.append(str);
        Terminal.println(sbuf.toString());
    }
}
