#!/usr/bin/env python
# -*- python -*-
################################################################################
#BEGIN_LEGAL
#
#Copyright (c) 2017 Intel Corporation
#
#  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.
#  
#END_LEGAL

import os, sys, types, re
import codegen
import genutil
import ildutil
import math


class operand_field_t(object): 
   def __init__(self,
                name,
                aggtype,
                ctype,
                bitwidth,
                default_visibility=None,
                default_initializer=None,
                xprint='NOPRINT',
                internal_or_public="INTERNAL",
                dio="DO",
                eio="EO",):

      self.name = name
      self.aggtype = aggtype
      self.ctype = ctype
      self.bitwidth = int(bitwidth)
      self.default_visibility = default_visibility
      self.xprint = xprint
      self.internal_or_public = internal_or_public
      self.dio = dio
      self.eio = eio
      
      if self.eio in ['EI','EO']:
         pass
      else:
          err = "Bad Encoder IO value: %s -- need one of {EI,EO}"
          genutil.die(err % self.eio)

      if self.dio in ['DI','DO','DS']:
         pass
      else:
          err = "Bad decoder IO value: %s -- need one of {DI,DO,DS}"
          genutil.die(err % self.eio)
         
      if self.eio == 'EI':
         self.encoder_input = True
      else:
         self.encoder_input = False

      if self.dio == 'DS':
         self.decoder_skip = True
      else:
         self.decoder_skip = False
         
      #NOTE: this next field is only used if initialize_each_field is True.
      self.default_initializer = default_initializer
      self.is_enum = 'enum' in self.ctype
      
      # this is the C type that will be used in the operand storage struct.
      self.storage_type = None 
      
      #if True using bit fields 
      self.compressed = False
      
   def print_field(self):
      if self.xprint == 'PRINT':
         return True
      return False

def key_operand_name(a):
    return a.name
def key_bitwidth(a):
    return a.bitwidth

def cmp_operands_name(a,b): # FIXME:2017-06-10:PY3 port, no longer used
    if a.name > b.name:
        return 1
    if a.name < b.name:
        return -1
    return 0

def cmp_operands(a,b): # FIXME:2017-06-10:PY3 port, no longer used
    ''' comparing the operands on based on their bit width
        larger width first.
        if width are the same compare the names, lower name first '''   
    
    w1 = a.bitwidth
    w2 = b.bitwidth
    if w1 > w2:
        return -1 
    if w1 < w2:
        return 1
    return cmp_operands_name(a,b)

def sort_cmp_operands(a):
    b = sorted(a, key=key_operand_name)
    c = sorted(b, key=key_bitwidth)
    return c
 
class operands_storage_t(object):
    """This is where we build up the storage for the fields that hold
    the operand values. 
    """

    def __init__(self,lines,compress_operands=False):
        #a dict of operand name to operand_field_t
        self.operand_fields = self._read_storage_fields(lines)
        
        self.compressed = compress_operands
        # the prefix of the accessor function
        self.xed_accessor_fn_pfx = ildutil.xed_strings['op_accessor']
        
        #list of bin, each bin is operands
        #used for squeezing operands with a few bits to one 32 bit variable 
        self.bins = []
        
      
    def _read_storage_fields(self,lines):
        ''' Return a dictionary of operand_field_t objects 
            indexed by field name '''
        
        comment_pattern = re.compile(r'[#].*$')
        operand_types = {}
        for line in lines:
            pline = comment_pattern.sub('',line).strip()
            if pline == '':
                continue
            wrds = pline.split()
            if len(wrds) != 9:
                genutil.die("Bad number of tokens on line: " + line)
            # aggtype is "SCALAR"
            (name, aggtype, ctype, width, default_visibility,
             xprint, internal_or_public, dio, eio) = wrds
            if name in operand_types:
                genutil.die("Duplicate name %s in input-fields file." % (name))
            
            if aggtype != 'SCALAR':
                err = ("type different than SCALAR is not" +
                       " supported in: %s" % (line))
                genutil.die(err)    
            
            if ctype == 'xed_reg_enum_t':
               default_initializer = 'XED_REG_INVALID'
            elif ctype == 'xed_iclass_enum_t':
               default_initializer = 'XED_ICLASS_INVALID'
            else:
               default_initializer = '0'
            operand_types[name] = operand_field_t(name, aggtype,
                                                  ctype, width,
                                                  default_visibility,
                                                  default_initializer,
                                                  xprint,
                                                  internal_or_public,
                                                  dio,
                                                  eio,)
        return operand_types    
        
    def get_operand(self,opname):
        return self.operand_fields[opname]
    
    def get_operands(self):
        return self.operand_fields
    
    def decoder_skip(self,operand):
        return self.operand_fields[operand].decoder_skip
    
    def get_ctype(self,operand):
        return self.operand_fields[operand].ctype
    
    def get_storage_type(self,operand):
        return self.operand_fields[operand].storage_type
    
    def _gen_op_getter_fo(self,opname):
        ''' generate the function object for the getter accessors
            adding cast to the C type according to the data files(ctype)'''
        inst = 'd'
        fname = get_op_getter_fn(opname)
        ret_type = self.get_ctype(opname)
        fo = codegen.function_object_t(fname,
                                           return_type=ret_type,
                                           static=True,
                                           inline=True)
        fo.add_arg('const xed_decoded_inst_t* %s' % inst)
        op = opname.lower()
        fo.add_code_eol('return (%s)%s->_operands.%s' % (ret_type,inst, op))
        return fo

    def _gen_op_setter_fo(self,opname):
        ''' generate the function object for the setter accessors
            adding cast to the C type according to the data files(ctype)'''
        inst = 'd'
        opval = 'opval'
        fname = get_op_setter_fn(opname)
        fo = codegen.function_object_t(fname,
                                           return_type='void',
                                           static=True,
                                           inline=True)
        fo.add_arg('xed_decoded_inst_t* %s' % inst)
        fo.add_arg('%s %s' % (self.get_ctype(opname),opval))
        op = opname.lower()
        type = self.get_storage_type(opname)
        fo.add_code_eol('%s->_operands.%s = (%s)%s' % (inst, op, type ,opval))
        return fo

    def _gen_generic_getter(self):
        ''' for xed's internal usage (printing) we need to be able to 
            get an operand based on its index.
            generating here a switch/case over the operand index to call the 
            correct getter function '''   
        inst = 'd'
        fname = 'xed3_get_generic_operand'
        ret_arg = 'ret_arg'
        
        fo = codegen.function_object_t(fname,
                                       return_type='void',
                                       static=False,
                                       inline=False,
                                       dll_export=True)
        fo.add_arg('const xed_decoded_inst_t* %s' % inst)
        fo.add_arg('xed_operand_enum_t operand')
        fo.add_arg('void* %s' % ret_arg)
        
        switch_gen = codegen.c_switch_generator_t('operand',fo)
        op_names = list(self.operand_fields.keys())
        op_names.sort()
        for op in op_names:
            switch_key = "XED_OPERAND_%s" % op
            ctype = self.get_ctype(op)
            func_getter = "%s(d)" % get_op_getter_fn(op)
            code = "*((%s*)%s)=%s;" % (ctype,ret_arg,func_getter)
            switch_gen.add_case(switch_key,[code]) 
        switch_gen.add_default(['xed_assert(0);'])
        switch_gen.finish()    
        return fo
    
    def _gen_generic_setter(self):
        ''' generating a switch/case over the operand index to call the 
            correct setter function '''   
        inst = 'd'
        fname = 'xed3_set_generic_operand'
        in_value = 'val'
        
        fo = codegen.function_object_t(fname,
                                       return_type='void',
                                       static=False,
                                       inline=False, 
                                       dll_export=True)
        fo.add_arg('xed_decoded_inst_t* %s' % inst)
        fo.add_arg('xed_operand_enum_t operand')
        fo.add_arg('xed_uint32_t %s' % in_value)
        
        switch_gen = codegen.c_switch_generator_t('operand',fo)
        op_names = list(self.operand_fields.keys())
        op_names.sort()
        for op in op_names:
            switch_key = "XED_OPERAND_%s" % op
            ctype = self.get_ctype(op)
            func_setter = get_op_setter_fn(op)
            code = "%s(%s,(%s)%s);" % (func_setter,inst,ctype,in_value)
            switch_gen.add_case(switch_key,[code]) 
        switch_gen.add_default(['xed_assert(0);'])
        switch_gen.finish()    
        return fo
        
    def dump_operand_accessors(self,agi):
        
        ''' Dump operand accessor to inspect the data 
            structure xed_operand_storage_t '''
        fo_list = []
        h_fname = get_operand_accessors_fn()
        c_fname = h_fname.replace('.h', '.c') 
        
        for opname in list(self.operand_fields.keys()):
            getter_fo = self._gen_op_getter_fo(opname)
            setter_fo = self._gen_op_setter_fo(opname)
            fo_list.append(getter_fo)
            fo_list.append(setter_fo)
        
        # generate a generic getter
        generic_getter = self._gen_generic_getter()
        generic_setter = self._gen_generic_setter()
        xeddir = os.path.abspath(agi.common.options.xeddir)
        gendir = agi.common.options.gendir
    
        h_file = codegen.xed_file_emitter_t(xeddir,gendir,
                                    h_fname, shell_file=False,
                                    is_private=False)
        
        h_file.add_header(['xed-decoded-inst.h','xed-operand-storage.h'])
        h_file.start()
    
        for fo in fo_list:
            decl = fo.emit_header()
            h_file.add_code(decl)
        h_file.add_code(generic_getter.emit_header())
        h_file.add_code(generic_setter.emit_header())    
        
        
        for fo in fo_list:
            fo.emit_file_emitter(h_file)
        

        h_file.close()
        
        c_file = codegen.file_emitter_t(gendir,
                                    c_fname, shell_file=False)
        c_file.add_header(h_fname)
        c_file.start()
        generic_getter.emit_file_emitter(c_file)
        generic_setter.emit_file_emitter(c_file)
        c_file.close()
        
    def _fix_bit_width_for_enums(self,agi):
        ''' the default width of the nums is to big and wasteful.
            we get the list of all values for each enum in agi 
            and set the bitwidth to the minimal width needed.
         '''
        
        # mx_bits is a mapping from enum name to the minimal number 
        # of bits required to represent it 
        max_bits_for_enum = self._gen_max_bits_per_enum(agi.all_enums)
        for op in list(self.operand_fields.values()):
            if op.ctype in max_bits_for_enum:
                needed_bits = max_bits_for_enum[op.ctype]
                if op.bitwidth < needed_bits:
                    # verify that the specified bitwidth form the data files 
                    # is not smaller than the calculated 
                    vals = agi.all_enums[op.ctype]
                    err = 'bit width for % is to small, has %d values' 
                    genutil.die(err % (op.name,vals))
                else:
                    op.bitwidth = max_bits_for_enum[op.ctype]
            
    def _compute_type_in_storage(self):
       ''' detect the minimal C type data type can be used to represent 
           the operand. 
           the accessors will cast the operand to its C type according to the 
           data files'''
       
       for op in list(self.operand_fields.values()):
           width = op.bitwidth
           if width <= 8:
               op.storage_type = 'xed_uint8_t'
           elif width <=16:
               op.storage_type ='xed_uint16_t'
           elif width <=32:
               op.storage_type = 'xed_uint32_t'
           elif width <=64:
               op.storage_type = 'xed_uint64_t'
           else:
               genutil.die("unhandled width")           
             
    def emit(self,agi):
        ''' emit the date type xed_operand_storage_t'''
        
        filename = 'xed-operand-storage.h'
        
        xeddir = agi.common.options.xeddir
        gendir = agi.common.options.gendir
        fe = codegen.xed_file_emitter_t(xeddir, gendir, filename)
        fe.headers.remove('xed-internal-header.h')
        headers = ['xed-chip-enum.h', 'xed-error-enum.h', 'xed-iclass-enum.h',
                   'xed-reg-enum.h','xed-operand-element-type-enum.h']
        fe.add_header(headers)
        
        fe.start()
        
        cgen = codegen.c_class_generator_t('xed_operand_storage_t',
                                           var_prefix='')
        
        #compute the minimal ctype required to represent each enum
        self._fix_bit_width_for_enums(agi)
        
        #compute the ctype of the operand ad represented in the operand storage
        self._compute_type_in_storage()
        
        if self.compressed:
            self.bins = self._compress_operands()
            
            operands = list(self.operand_fields.values())
            un_compressed = list(filter(lambda x: x.compressed == False, operands ))
            un_compressed = sort_cmp_operands(un_compressed)
            
            # first emit all the operands that does not use bit fields 
            for op in un_compressed:
                cgen.add_var(op.name.lower(), op.storage_type, 
                             accessors='none')
            
            #emit the operand with bit fields
            for i,bin in enumerate(self.bins):
                for op in bin.operands:
                    cgen.add_var(op.name.lower(), bin.storage_ctype, 
                                 bit_width=op.bitwidth, accessors='none')
        
        else:
            operands_sorted = list(self.operand_fields.values())
            operands_sorted = sort_cmp_operands(operands_sorted)
            for op in operands_sorted:
                cgen.add_var(op.name.lower(), op.storage_type, 
                             accessors='none')
        
        lines = cgen.emit_decl()
        fe.writelines(lines)
        
        fe.close()
    
    def _get_num_elements_in_enum(self,values_list):
        ''' return the number of elements in the enum.
            is the elements does not have the x_LAST enum add it'''
        has_last = False
        for val in values_list:
            if 'LAST' in val:
                has_last = True
        if has_last:
            return len(values_list)
        else:
            return len(values_list) + 1  
    
    def _gen_max_bits_per_enum(self,all_enums):
        ''' calculate the number of bits required to capture the each enum.
            returning a dict of enum name to the number of required bits '''
        widths = {}
        for (enum_name, values_list) in list(all_enums.items()):
            num_values = self._get_num_elements_in_enum(values_list)
            log2 = math.log(num_values,2)
            needed_bits = int(math.ceil(log2))
            widths[enum_name]= needed_bits
        # special handling for xed_error_enum_t.
        # this width is hard coded since we can not capture the values
        # of this enum in the generator
        widths['xed_error_enum_t'] = 4
        return widths
    
    def _get_candidates_for_compression(self):
        ''' collect all the operands that we need to compress.
            the operands that we need to compress has bitwidth smaller then 
            their ctype can hold. '''
        
        candiadtes = []
        for op in list(self.operand_fields.values()):
            # for optimization those operands are not using bit with
            # FIXME: add field to the operands for excluding hot fields 
            # form being compressed
            #if op.name.lower() in ['error','outreg','mode']:
            #    continue
            if op.bitwidth != 32 and op.bitwidth != 64:
                candiadtes.append(op)
        return candiadtes
    
    def _place_operand_in_bin(self,op,bins):
        ''' find a bin that has place for the operand '''
        for bin in bins:
            if bin.operand_fits(op):
                bin.add_operand(op)
                return
        
        #did not find any matching bin, need to create new one
        bin = operands_bin_t()
        bin.add_operand(op)
        bins.append(bin)
        return
            
    def _partition_to_bins(self,ops_sorted):
        ''' partition all the operands in bins '''
        bins = []
        for op in ops_sorted:
            self._place_operand_in_bin(op,bins)
            op.compressed = True
        return bins
            
    def _compress_operands(self):
        ''' most of the operands's width are less than their c type.
        in order to save space we are bin packing the operands.
        each bin is 32bit width.
        using First Fit Decreasing(FFD) strategy '''
        
        operands = self._get_candidates_for_compression()
        operands = sort_cmp_operands(operands)
        bins = self._partition_to_bins(operands)
        return bins
    
class operands_bin_t(object):
    ''' This class represents a single bin that aggregates a list of operands
        into single c struct '''
    def __init__(self):
        self.operands = []  #list of operands
        self.size = 0       #total width in bits
        self.max_size = 32  #the max width
        self.storage_ctype = 'xed_uint32_t' #the C type used for the operands
    
    def add_operand(self,op):
        ''' adding single operand to this bin'''
        self.operands.append(op)
        self.size += op.bitwidth
        
    def operand_fits(self,operand):
        ''' checks if the given operand can be inserted into this bin '''
        return operand.bitwidth + self.size <= self.max_size

################
# Those are global functions that are accessed for places that do not have the 
# operands_storage_t type
################                
def get_operand_accessors_fn():             
    return 'xed-operand-accessors.h'

def get_op_getter_full_func(opname,strings_dict):
    obj_name = strings_dict['obj_str']
    accessor = get_op_getter_fn(opname) 
    return '%s(%s)' % (accessor,obj_name) 

def get_op_getter_fn(opname):
    xed_accessor_fn_pfx = ildutil.xed_strings['op_accessor']
    return '%s_get_%s' % (xed_accessor_fn_pfx, opname.lower())

def get_op_setter_fn(opname):
    xed_accessor_fn_pfx = ildutil.xed_strings['op_accessor']
    return '%s_set_%s' % (xed_accessor_fn_pfx, opname.lower())       
        

    
