#include <stdio.h>
#include <stdlib.h>
#include "insertor.h"
#include "utils.h"

#include "tc-i386.h"
#include "i386.h"

struct discrib instr[NBR_CHAR];
struct discrib extend_instr[NBR_CHAR];

int
ubytes_size(unsigned int n)
{
 if (n <= 0xff)
   return 1;
 if (n <= 0xffff)
   return 2;
 if (n <= 0xffffff)
   return 3;
 return 4;
}

void
modify_table(struct discrib *table, int off, enum TType type, template const *temp)
{
 if (temp->extension_opcode == None)
   {
     if (table[off].type != TUnknown && table[off].type != OneByte)
       printf("Error in modify_table\n");
     table[off].type = type;
     table[off].info.onebyte = temp;
   }
 else
   {
     if ( table[off].type == TUnknown)
       {
         int i;
         table[off].info.grp = (template**)xmalloc(8*sizeof(template*));
         table[off].type = TGroup;
         for (i = 0; i < 0x08; i++)
           table[off].info.group[i] = (template*)0;
       }
     else
       if ( table[off].type != TGroup )
         printf("A instruction occpuies a TGroup\n");
     table[off].info.group[temp->extension_opcode] = temp;
   }
}  
  
void
init_instr_table()
{
 int i;
 prefix_entry const *pe;
 template const *pt;
 unsigned int operand;
 unsigned char oper[4];
 unsigned char current;
 int op_size;
 int op_index;
 struct discrib *op_table;
 
 for (i = 0; i < NBR_CHAR; i++)
 {
  instr[i].type = TUnknown;
  extend_instr[i].type = TUnknown;
 }
 
 /* stuff */
 instr[0x0f].type = Extend;
 
 /* now prefixes */
 for (pe = i386_prefixtab; pe < i386_prefixtab_end; pe++)
 {
   if (instr[pe->prefix_code].type != TUnknown)
     printf("Prefix already known!\n");
   instr[pe->prefix_code].type = Prefix;
   printf("Add prefix %s\n",pe->prefix_name);
 }
 
 /* now instructions */
 /* for (pt = i386_optab; pt->opcode_modifier != 0; pt++) */
 pt = i386_optab;
 do
 {
   op_size = ubytes_size(pt->base_opcode);
   operand = pt->base_opcode;
   while(op_size)
   {
    oper[--op_size] = operand & 0xff;
    operand >>= CHAR_BIT;
   }
   
   op_size = ubytes_size(pt->base_opcode);
   op_index = 0;
   
   /* skipf prefixes */
   if (op_size == 1 && instr[oper[0]].type == Prefix)
     {
       instr[oper[0]].info.onebyte = pt;
       continue;
     }
    
   /* spkif fops */
   if ((oper[0] & 0xf8) == 0xd8)
     {
       if (instr[oper[0]].type == Unknown)
         {
           instr[oper[0]].type = FPint;     
           instr[oper[0]].info.grp = (template**)xmalloc(72*sizeof(template*));
           for (i = 0; i < 72; i++)
             instr[oper[0]].info.group[i] = (template*)0;
         }
       else
         if (instr[oper[0]].type != FPint)
           FATAL(1, "internal error\n");
       if (op_size == 1) 
         {
           if (pt->extension_opcode == None || pt->opcode_modifier != Modrm)
             FATAL(1, "Bad opcode\n");
           instr[oper[0]].info.fgroup[pt->extension_opcode] = pt;
           continue;
         }
       else
         {
           if (pt->extension_opcode != None || op_size != 2)
             FATAL(2, "Bad opcode\n");
           if (pt->opcode_modifier == NoModrm)
             {
               instr[oper[0]].info.fgroup[8 + (oper[1] & 0x30)] = pt;
               continue;
             }
           if (pt->opcode_modifier == ShortForm)
             {
               for (i = 0; i < 8; i++)
                 instr[oper[0]].info.fgroup[8 + (oper[1] & 0x30) + i] = pt;
               continue;
             }
           if (pt->opcode_modifier == (ShortForm | FloatD))
             {
               if (oper[0] & 4)
                 FATAL(1, "Bad opcode\n");
               for (i = 0; i < 8; i++)
                 instr[oper[0]].info.fgroup[8 + (oper[1] & 0x30) + i] = pt;
               for (i = 0; i < 8; i++)
                 instr[oper[0] + 4].info.fgroup[8 + (oper[1] & 0x30) + i] = pt;
               continue;
             }
           FATAL(1, "Bad opcode modifier\n");
         }
     }
     
   if (instr[oper[0]].type == Prefix)
     op_index++;
   
   if (oper[op_index] == 0x0f)
     {
       op_index++;
       op_table = extend_instr;
     }
   else
       op_table = instr;
   
   current = oper[op_index];
   
#ifdef TEST   
   printf("Try to add %s\n", pt->name);
#endif
   
   /* special cases*/
   if (pt->base_opcode == 0xd50a || pt->base_opcode == 0xd40a)
   {
     modify_table(instr, oper[0], TwoByte, pt);
     continue;
   }
   
   if (op_size - op_index != 1)
     printf("%s is too big: %d\n", pt->name, op_size - op_index);
     
   switch (pt->opcode_modifier)
     {
       case W:
       case W | Modrm:
       case W | NoModrm:
         modify_table(op_table, current, OneByte, pt);
         modify_table(op_table, current + 1, OneByte, pt);
         break;
       case D:
       case D | Modrm:
         modify_table(op_table, current, OneByte, pt);
         modify_table(op_table, current + 2, OneByte, pt);
         break;
       case DW:
       case DW | NoModrm:
       case DW | Modrm:
         modify_table(op_table, current, OneByte, pt);
         modify_table(op_table, current + 1, OneByte, pt);
         modify_table(op_table, current + 2, OneByte, pt);
         modify_table(op_table, current + 3, OneByte, pt);         
         break;
       case ShortFormW:
         for (i = 0; i < 0x10; i++)
           modify_table(op_table, current + i, OneByte, pt);
         break;
       case ShortForm:
         for (i = 0; i < 0x08; i++)
           modify_table(op_table, current + i, OneByte, pt);
         break;
       case ReverseRegRegmem | Modrm:
       case Modrm:
       case NoModrm:
       case JumpInterSegment:
       case JumpDword:
       case JumpByte:
       case imulKludge | Modrm:
         modify_table(op_table, current, OneByte, pt);
         break;
       case Seg2ShortForm:
         for (i = 0; i < 0x04; i++)
           if ( pt->base_opcode == 0x07 && i != 1) /* avoid pop cs */
             modify_table(op_table, current + (i << 3), OneByte, pt);
         break;
       case Seg3ShortForm:
         for (i = 0; i < 0x06; i++)
           modify_table(op_table, current + (i << 3), OneByte, pt);
         break;       
       case Jump:
         {
           if (op_table != instr)
             printf("Bad op_table for a jump\n");
           modify_table(op_table, current, OneByte, pt);
           if (pt->base_opcode == JUMP_PC_RELATIVE)
             modify_table(op_table, 0xe9, OneByte, pt);
           else
             modify_table(extend_instr, 0x80 + (current & 0x0f), OneByte, pt);
         }
         break;
       default:
         printf("Unknown %s\n", pt->name);
     }
 }
 while ((++pt)->opcode_modifier != 0);
}

#ifdef TEST
main()
{   
  init_instr_table();
  exit(0);
}
#endif /* TEST */