/*******************************************************************************
 HARPtools by Chad D. Kersey, Summer 2011
*******************************************************************************/
#include <iostream>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <iomanip>
#include <vector>

#include "include/debug.h"
#include "include/types.h"
#include "include/util.h"
#include "include/enc.h"
#include "include/archdef.h"
#include "include/instruction.h"

using namespace std;
using namespace Harp;

ByteDecoder::ByteDecoder(const ArchDef &ad) {
  wordSize = ad.getWordSize();
}

static void decodeError(string msg) {
  cout << "Instruction decoder error: " << msg << '\n';
  exit(1);
}

void Encoder::encodeChunk(DataChunk &dest, const TextChunk &src) {
  // typedef vector<Instruction*>::const_iterator vec_it;
  // const vector<Instruction*> &s(src.instructions);
  // vector<Byte> &d(dest.contents);

  // /* Keep encoding the instructions. */
  // Size n = 0;

  // /* For each instruction. */
  // for (vec_it i = s.begin(); i != s.end(); i++) {
  //   Ref *ref;

  //   /* Perform the encoding. */
  //   n += encode(ref, d, n, **i);
    
  //   /* Add reference if necessary. */
  //   if (ref != NULL) {
  //     ref->ibase = n;
  //     dest.refs.push_back(ref);
  //   }
  // }

  // dest.alignment = src.alignment;
  // dest.flags = src.flags;
  // dest.address = src.address;
  // dest.bound = src.bound;
  // if (src.isGlobal()) dest.setGlobal();

  // d.resize(n);
  // dest.size = n;
}

void Decoder::decodeChunk(TextChunk &dest, const DataChunk &src) {
//   typedef vector<Instruction*>::const_iterator vec_it;
//   const vector<Byte> &v(src.contents);
//   Size n = 0;

//   setRefs(src.refs);

//   while (n < src.contents.size()) {
//     Instruction *inst = decode(v, n);
//     if (inst->hasRefLiteral()) {
//       dest.refs.push_back(inst->getRefLiteral());
//     }

//     dest.instructions.push_back(inst);
//   }

//   dest.alignment = src.alignment;
//   dest.flags = src.flags;
//   dest.address = src.address;
//   dest.bound = src.bound;
//   if (src.isGlobal()) dest.setGlobal();

//   clearRefs();
// }

// void Decoder::setRefs(const std::vector<Ref*> &refVec) {
//   haveRefs = true;

//   typedef std::vector<Ref*>::const_iterator vec_ci;

//   for (vec_ci i = refVec.begin(); i != refVec.end(); i++) {
//     OffsetRef *oref = dynamic_cast<OffsetRef*>(*i);
//     if (oref) {
//       refMap[oref->getOffset()] = *i;
//     } else {
//       decodeError("Unknown Ref type in Decoder::setRefs");
//     }
//   }
}

Instruction *ByteDecoder::decode(const vector<Byte> &v, Size &n) {
  Instruction &inst = *(new Instruction());

  // uint8_t pred = readByte(v, n);
  // if (pred != 0xff) inst.setPred(pred);

  // unsigned op = readByte(v, n);
  // inst.setOpcode(Instruction::Opcode(op));

  // bool usedImm = false;

  // switch ((Instruction::instTable[op]).argClass) {
  //   case Instruction::AC_NONE:
  //     break;
  //   case Instruction::AC_2REG:
  //     inst.setDestReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_2IMM:
  //     inst.setDestReg(readByte(v, n));
  //     inst.setSrcImm(readWord(v, n, wordSize));
  //     usedImm = true;
  //     break;
  //   case Instruction::AC_3REG:
  //     inst.setDestReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_3PREG:
  //     inst.setDestPReg(readByte(v, n));
  //     inst.setSrcPReg(readByte(v, n));
  //     inst.setSrcPReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_3IMM:
  //     inst.setDestReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcImm(readWord(v, n, wordSize));
  //     usedImm = true;
  //     break;
  //   case Instruction::AC_3REGSRC:
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_1IMM:
  //     inst.setSrcImm(readWord(v, n, wordSize));
  //     usedImm = true;
  //     break;
  //   case Instruction::AC_1REG:
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_3IMMSRC:
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcImm(readWord(v, n, wordSize));
  //     usedImm = true;
  //     break;
  //   case Instruction::AC_PREG_REG:
  //     inst.setDestPReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_2PREG:
  //     inst.setDestPReg(readByte(v, n));
  //     inst.setSrcPReg(readByte(v, n));
  //     break;
  //   case Instruction::AC_2REGSRC:
  //     inst.setSrcReg(readByte(v, n));
  //     inst.setSrcReg(readByte(v, n));
  //     break;
  //  default:
  //     decodeError("Unknown argument class.");
  // }

  // if (haveRefs && usedImm && 
  //     refMap.find(n - wordSize) != refMap.end()) {
  //   OffsetRef *oref = dynamic_cast<OffsetRef*>(refMap[n - wordSize]);
  //   if (!oref) {
  //     decodeError("Expected OffsetRef when decoding instruction stream.");
  //   }
  //   Ref *r = new SimpleRef(oref->name, *(Addr*)(inst.setSrcImm()), 
  //                          inst.hasRelImm());
  //   inst.setImmRef(*r);
  // } 

  return &inst;
}

ByteEncoder::ByteEncoder(const ArchDef &ad) {
  wordSize = ad.getWordSize();
}

Size ByteEncoder::encode(Ref *&ref, vector<Byte> &v, Size n0, Instruction &i) {
  // Size n(n0);

  // if (i.hasPred()) writeByte(v, n, i.getPred());
  // else writeByte(v, n, 0xff);

  // writeByte(v, n, Byte(i.getOpcode()));

  // if (i.hasRDest()) {
  //   writeByte(v, n, Byte(i.getRDest()));
  // } else if (i.hasPDest()) {
  //   writeByte(v, n, Byte(i.getPDest()));
  // }

  // for (RegNum j = 0; j < i.getNRSrc(); j++) {
  //   writeByte(v, n, Byte(i.getRSrc(j)));
  // }

  // for (RegNum j = 0; j < i.getNPSrc(); j++) {
  //   writeByte(v, n, Byte(i.getPSrc(j)));
  // }

  // ref = NULL;
  // if (i.hasImm()) {
  //   if (i.hasRefLiteral()) {
  //     Ref *r = i.getRefLiteral();
  //     ref = new OffsetRef(r->name, v, n, wordSize*8, wordSize, i.hasRelImm());
  //   }

  //   writeWord(v, n, wordSize, i.getImm());
  // }

  // return n - n0;
  return 0;
}

static unsigned ceilLog2(RegNum x) {
  unsigned z = 0;
  bool nonZeroInnerValues(false);

  if (x == 0) return 0;

  while (x != 1) {
    z++;
    if (x&1) nonZeroInnerValues = true;
    x >>= 1;
  }

  if (nonZeroInnerValues) z++;

  return z;
}


WordDecoder::WordDecoder(const ArchDef &arch) {

    inst_s   = arch.getWordSize() * 8;
    opcode_s = 7;
    reg_s    = 5;
    func3_s  = 3;

    shift_opcode    = 0;
    shift_rd        = opcode_s;
    shift_func3     = opcode_s + reg_s;
    shift_rs1       = opcode_s + reg_s + func3_s;
    shift_rs2       = opcode_s + reg_s + func3_s + reg_s;
    shift_func7     = opcode_s + reg_s + func3_s + reg_s + reg_s;
    shift_j_u_immed = opcode_s + reg_s;
    shift_s_b_immed = opcode_s + reg_s + func3_s + reg_s + reg_s;
    shift_i_immed   = opcode_s + reg_s + func3_s + reg_s;

    reg_mask     = 0x1f;
    func3_mask   = 0x7;
    func7_mask   = 0x7f;
    opcode_mask  = 0x7f;
    i_immed_mask = 0xfff;
    s_immed_mask = 0xfff;
    b_immed_mask = 0x1fff;
    u_immed_mask = 0xfffff;
    j_immed_mask = 0xfffff;

}

static Word signExt(Word w, Size bit, Word mask) {
  if (w>>(bit-1)) w |= ~mask;
  return w;
}

Instruction *WordDecoder::decode(const std::vector<Byte> &v, Size &idx) {
  Word code(readWord(v, idx, inst_s/8));

  // std::cout << "code: " << (int) code << "  v: " << v << " indx: " << idx << "\n";
  

  Instruction &inst = * new Instruction();  

  // bool predicated = (code>>(n-1));
  bool predicated = false;
  if (predicated) { inst.setPred((code>>(inst_s-p-1))&pMask); }

  Opcode op = (Opcode)((code>>shift_opcode)&opcode_mask);
  // std::cout << "opcode: " << op << "\n";
  inst.setOpcode(op);

  bool usedImm(false);
  Word imeed, dest_bits, imm_bits, bit_11, bits_4_1, bit_10_5,
          bit_12, bits_19_12, bits_10_1, bit_20, unordered, func3;

  // std::cout << "op: " << std::hex << op << " what " << instTable[op].iType << "\n";
  switch(instTable[op].iType)
  {
    case InstType::N_TYPE:
      break;
    case InstType::R_TYPE:
      inst.setPred((code>>shift_rs1)   & reg_mask);
      inst.setDestReg((code>>shift_rd)   & reg_mask);
      inst.setSrcReg((code>>shift_rs1)   & reg_mask);
      inst.setSrcReg((code>>shift_rs2)   & reg_mask);
      inst.setFunc3 ((code>>shift_func3) & func3_mask);
      inst.setFunc7 ((code>>shift_func7) & func7_mask);
      break;
    case InstType::I_TYPE:
      inst.setDestReg((code>>shift_rd)   & reg_mask);
      inst.setSrcReg((code>>shift_rs1)   & reg_mask);
      inst.setFunc7 ((code>>shift_func7) & func7_mask);
      func3 = (code>>shift_func3) & func3_mask;
      inst.setFunc3 (func3);

      if ((func3 == 5) && (op != L_INST))
      {
        // std::cout << "func7: " << func7 << "\n";
        inst.setSrcImm(signExt(((code>>shift_rs2)&reg_mask), 5, reg_mask));
      }
      else
      {
        inst.setSrcImm(signExt(code>>shift_i_immed, 12, i_immed_mask));
      }

      usedImm = true;
      break;
    case InstType::S_TYPE:
      // std::cout << "************STORE\n";
      inst.setSrcReg((code>>shift_rs1)   & reg_mask);
      inst.setSrcReg((code>>shift_rs2)   & reg_mask);
      inst.setFunc3 ((code>>shift_func3) & func3_mask);

      dest_bits = (code>>shift_rd)  & reg_mask;
      imm_bits  = (code>>shift_s_b_immed & func7_mask);
      imeed     = (imm_bits << reg_s) | dest_bits;
      // std::cout << "ENC: store imeed: " << imeed << "\n";
      inst.setSrcImm(signExt(imeed, 12, s_immed_mask));
      usedImm = true;
      break;
    case InstType::B_TYPE:

      inst.setSrcReg((code>>shift_rs1)   & reg_mask);
      inst.setSrcReg((code>>shift_rs2)   & reg_mask);
      inst.setFunc3 ((code>>shift_func3) & func3_mask);

      dest_bits = (code>>shift_rd)  & reg_mask;
      imm_bits  = (code>>shift_s_b_immed & func7_mask);

      bit_11   = dest_bits & 0x1;
      bits_4_1 = dest_bits >> 1;
      bit_10_5 = imm_bits & 0x3f;
      bit_12   = imm_bits >> 6;

      imeed    = 0 | (bits_4_1 << 1) | (bit_10_5 << 5) | (bit_11 << 11) | (bit_12 << 12);

      inst.setSrcImm(signExt(imeed, 13, b_immed_mask));
      usedImm = true;
      break;
    case InstType::U_TYPE:
      inst.setDestReg((code>>shift_rd)   & reg_mask);
      inst.setSrcImm(signExt(code>>shift_j_u_immed, 20, u_immed_mask));
      usedImm = true;
      break;
    case InstType::J_TYPE:
      inst.setDestReg((code>>shift_rd)   & reg_mask);

      // [20 | 10:1 | 11 | 19:12]

      unordered = code>>shift_j_u_immed;

      bits_19_12 = unordered & 0xff;
      bit_11     = (unordered>>8) & 0x1;
      bits_10_1  = (unordered >> 9) & 0x3ff;
      bit_20     = (unordered>>19) & 0x1;

      imeed  = 0 | (bits_10_1 << 1) | (bit_11 << 11) | (bits_19_12 << 12) | (bit_20 << 20);

      inst.setSrcImm(signExt(imeed, 20, j_immed_mask));
      usedImm = true;
      break;
   defualt:
      cout << "Unrecognized argument class in word decoder.\n";
      exit(1);
  }

  if (haveRefs && usedImm && refMap.find(idx-n/8) != refMap.end()) {
    Ref *srcRef = refMap[idx-n/8];

    /* Create a new ref tied to this instruction. */
    Ref *r = new SimpleRef(srcRef->name, *(Addr*)inst.setSrcImm(),
                           inst.hasRelImm());
    inst.setImmRef(*r);
  }

  D(2, "Decoded 0x" << hex << code << " into: " << inst << '\n');

  return &inst;
}

