/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.compilers.common.assembler.ia32;

import org.jikesrvm.VM;
import org.jikesrvm.ia32.RegisterConstants;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Address;
import org.vmmagic.pragma.Pure;

/**
 */
public final class Lister implements RegisterConstants {

  private static final int PREFIX_AREA_SIZE = 8;
  private static final int OP_AREA_SIZE = 9;
  private static final int SOURCE_AREA_SIZE = 16;
  private static final int DEST_AREA_SIZE = 16;

  private final Assembler asm;

  private enum Prefix {LOCK, LIKELY, UNLIKELY};

  private Prefix prefix;

  public Lister(Assembler asm) {
    this.asm = asm;
  }

  public void lockPrefix() {
    prefix = Prefix.LOCK;
  }

  public void branchLikelyPrefix() {
    prefix = Prefix.LIKELY;
  }

  public void branchUnlikelyPrefix() {
    prefix = Prefix.UNLIKELY;
  }

  public void OP(int i, String op) {
    i = begin(i, op);
    VM.sysWrite(right("", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void I(int i, String op, int n) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(n) + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void R(int i, String op, MachineRegister R0) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RD(int i, String op, MachineRegister R0, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(d) + "[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RI(int i, String op, MachineRegister R0, int n) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n) + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RDI(int i, String op, MachineRegister R0, Offset d, int n) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(d) + "[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n) + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RNI(int i, String op, MachineRegister R0, int n) {
    i = begin(i, op);
    VM.sysWrite(right("[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n) + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RR(int i, String op, MachineRegister R0, MachineRegister R1) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RDR(int i, String op, MachineRegister R0, Offset d, MachineRegister R1) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(d) + "[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RDRI(int i, String op, MachineRegister R0, Offset d, MachineRegister R1, int imm) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(d) + "[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RDRR(int i, String op, MachineRegister R0, Offset d, MachineRegister R1, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right(decimal(d) + "[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRD(int i, String op, MachineRegister R0, MachineRegister R1, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(d) + "[" + R1 + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RNR(int i, String op, MachineRegister R0, MachineRegister R1) {
    i = begin(i, op);
    VM.sysWrite(right("[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RN(int i, String op, MachineRegister R0) {
    i = begin(i, op);
    VM.sysWrite(right("[" + R0 + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(" ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRN(int i, String op, MachineRegister R0, MachineRegister R1) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + R1 + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RXD(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + R0 + "+" + X + "<<" + decimal(s) + "]",
                      DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RXDI(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d, int n) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + R0 + "+" + X + "<<" + decimal(s) + "]",
                      DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RFD(int i, String op, MachineRegister X, short s, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RA(int i, String op, Address d) {
    i = begin(i, op);
    VM.sysWrite(right("[" + hex(d) + "]", DEST_AREA_SIZE));
    VM.sysWrite(right("", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RFDI(int i, String op, MachineRegister X, short s, Offset d, int n) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RAI(int i, String op, Address d, int n) {
    i = begin(i, op);
    VM.sysWrite(right("[" + hex(d) + "]", DEST_AREA_SIZE));
    VM.sysWrite(right(decimal(n), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRR(int i, String op, MachineRegister R0, MachineRegister R1, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RNRI(int i, String op, MachineRegister R0, MachineRegister R1, int imm) {
    i = begin(i, op);
    VM.sysWrite(right("[" + R0 + "] ", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RNRR(int i, String op, MachineRegister R0, MachineRegister R1, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right("[" + R0 + "] ", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRI(int i, String op, MachineRegister R0, MachineRegister R1, int imm) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRXD(int i, String op, MachineRegister R0, MachineRegister R1, MachineRegister X, short s, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + decimal(d) + "+" + R1 + "+" + X + "<<" + decimal(s) + "]",
                      SOURCE_AREA_SIZE));
    end(i);
  }

  public void RXDR(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d, MachineRegister R1) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + R0 + "+" + X + "<<" + decimal(s) + "]",
                      DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RXDRI(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d, MachineRegister R1, int imm) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + R0 + "+" + X + "<<" + decimal(s) + "]",
                      DEST_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RXDRR(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d, MachineRegister R1, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right("[" + decimal(d) + "+" + R0 + "+" + X + "<<" + decimal(s) + "]",
                      SOURCE_AREA_SIZE));
    VM.sysWrite(right(R1 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRFD(int i, String op, MachineRegister R0, MachineRegister X, short s, Offset d) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RFDR(int i, String op, MachineRegister X, short s, Offset d, MachineRegister R0) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RFDRI(int i, String op, MachineRegister X, short s, Offset d, MachineRegister R0, int imm) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RFDRR(int i, String op, MachineRegister X, short s, Offset d, MachineRegister R0, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + decimal(d) + "+" + X + "<<" + decimal(s) + "]", SOURCE_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RRA(int i, String op, MachineRegister R0, Address d) {
    i = begin(i, op);
    VM.sysWrite(right(R0 +" ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + hex(d) + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RAR(int i, String op, Address d, MachineRegister R0) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + hex(d) + "]", SOURCE_AREA_SIZE));
    end(i);
  }

  public void RARI(int i, String op, Address d, MachineRegister R0, int imm) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + hex(d) + "]", SOURCE_AREA_SIZE));
    VM.sysWrite(right(decimal(imm), SOURCE_AREA_SIZE));
    end(i);
  }

  public void RARR(int i, String op, Address d, MachineRegister R0, MachineRegister R2) {
    i = begin(i, op);
    VM.sysWrite(right(R0 + " ", DEST_AREA_SIZE));
    VM.sysWrite(right("[" + hex(d) + "]", SOURCE_AREA_SIZE));
    VM.sysWrite(right(R2 + " ", SOURCE_AREA_SIZE));
    end(i);
  }

  private int begin(int i, String op) {
    if (prefix != null) i--;
    VM.sysWrite(right(hex(i), 6) + "| ");
    if (prefix != null) {
      VM.sysWrite(right(prefix.toString(), PREFIX_AREA_SIZE) + " ");
    } else {
      VM.sysWrite(right("", PREFIX_AREA_SIZE) + " ");
    }
    VM.sysWrite(left(op, OP_AREA_SIZE));
    return i;
  }

  private void end(int i) {
    VM.sysWrite(" | ");
    asm.writeLastInstruction(i);
    VM.sysWrite("\n");
    prefix = null;
  }

  @Pure
  private static String left(String s, int w) {
    int n = s.length();
    if (w < n) return s.substring(0, w);
    StringBuilder result = new StringBuilder(s);
    for (int i = n; i < w; i++) {
      result.append(' ');
    }
    return result.toString();
  }

  @Pure
  private static String right(String s, int w) {
    int n = s.length();
    if (w < n) return s.substring(n - w);
    StringBuilder result = new StringBuilder();
    for (int i = n; i < w; i++) {
      result.append(' ');
    }
    result.append(s);
    return result.toString();
  }

  private static String decimal(Offset o) {
    return decimal(o.toInt());
  }

  @Pure
  static String decimal(int n) {
    if (n == 0) return "0";
    String sign = "";
    if (n < 0) {
      sign = "-";
      n = -n;
    }
    String result = "";
    while (0 < n) {
      int i = n % 10;
      n /= 10;
      if (i == 0) {
        result = "0" + result;
      } else if (i == 1) {
        result = "1" + result;
      } else if (i == 2) {
        result = "2" + result;
      } else if (i == 3) {
        result = "3" + result;
      } else if (i == 4) {
        result = "4" + result;
      } else if (i == 5) {
        result = "5" + result;
      } else if (i == 6) {
        result = "6" + result;
      } else if (i == 7) {
        result = "7" + result;
      } else if (i == 8) {
        result = "8" + result;
      } else if (i == 9) result = "9" + result;
    }
    return (sign + result);
  }

  private static String decimal(short s) {
    return decimal((int) s);
  }

  @Pure
  static String hex(Address i) {
    return (hex((short) (i.toInt() >> 16)) + hex((short) i.toWord().toInt()));
  }

  @Pure
  public static String hex(int i) {
    return (hex((short) (i >> 16)) + hex((short) i));
  }

  @Pure
  static String hex(short i) {
    return (hex((byte) (i >> 8)) + hex((byte) i));
  }

  @Pure
  static String hex(byte b) {
    int i = b & 0xFF;
    byte j = (byte) (i / 0x10);
    String s;
    if (j == 0x0) {
      s = "0";
    } else if (j == 0x1) {
      s = "1";
    } else if (j == 0x2) {
      s = "2";
    } else if (j == 0x3) {
      s = "3";
    } else if (j == 0x4) {
      s = "4";
    } else if (j == 0x5) {
      s = "5";
    } else if (j == 0x6) {
      s = "6";
    } else if (j == 0x7) {
      s = "7";
    } else if (j == 0x8) {
      s = "8";
    } else if (j == 0x9) {
      s = "9";
    } else if (j == 0xA) {
      s = "A";
    } else if (j == 0xB) {
      s = "B";
    } else if (j == 0xC) {
      s = "C";
    } else if (j == 0xD) {
      s = "D";
    } else if (j == 0xE) {
      s = "E";
    } else {
      s = "F";
    }
    j = (byte) (i % 0x10);
    String t;
    if (j == 0x0) {
      t = "0";
    } else if (j == 0x1) {
      t = "1";
    } else if (j == 0x2) {
      t = "2";
    } else if (j == 0x3) {
      t = "3";
    } else if (j == 0x4) {
      t = "4";
    } else if (j == 0x5) {
      t = "5";
    } else if (j == 0x6) {
      t = "6";
    } else if (j == 0x7) {
      t = "7";
    } else if (j == 0x8) {
      t = "8";
    } else if (j == 0x9) {
      t = "9";
    } else if (j == 0xA) {
      t = "A";
    } else if (j == 0xB) {
      t = "B";
    } else if (j == 0xC) {
      t = "C";
    } else if (j == 0xD) {
      t = "D";
    } else if (j == 0xE) {
      t = "E";
    } else {
      t = "F";
    }
    return s + t;
  }

  public void noteBytecode(int i, String bcode) {
    VM.sysWrite("[" + decimal(i) + "] " + bcode + "\n");
  }

  public void comment(int i, String comment) {
    VM.sysWrite(right(hex(i), 6) + "| " + comment + "\n");
  }

  public void comefrom(int i, int j) {
    VM.sysWrite(right(hex(i), 6) + "| <<< " + right(hex(j), 6) + "\n");
  }
}
