#BEGIN_LEGAL
#
#Copyright (c) 2016 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
from verbosity import *
import re
import patterns
import genutil
import encutil
import mbuild
import copy

def msgb(s,b=''):
    mbuild.msgb(s,b)

def gen_return_action(ret_val):
    ''' create new action with type return '''
    
    #temporary creating it as dummy
    action = action_t("nothing")
    action.type = 'return'
    action.value = str(ret_val)
    return action

def dummy_emit(act_in,name):
    ''' creating new action based on the input action with 
        dummy emit type and name as the field name'''
    
    action = copy.deepcopy(act_in)
    action.emit_type = 'dummy'
    action.field_name = name
    return action
    
def gen_dummy_fb(fb):
    ''' create new fb action that sets the value to -1 '''
    str = "%s=-1" % fb
    action = action_t(str)
    return action

def gen_null_fb():
    ''' create new fb with fake fb name:"null" and value 0
        using this to represent null pointers'''
    str = "null=0" 
    action = action_t(str)
    return action
    
def gen_nt_action(nt):
    ''' create new action with type nt '''
    str = "%s()" % nt
    action = action_t(str)
    return action

class action_t(object):
    """This is the right hand side of the rule_t. It can be a (1)
    field binding (2) a byte encoding, (3) 'error' or (4) 'nothing'."""

    
    def __init__(self, arg_action):
        self.type = None #  'FB', 'emit', 'nt', 'error', 'nothing', return
        self.field_name = None
        self.value = None
        self.nt = None
        self.ntluf = None
        self.int_value = None
        self.emit_type = None # 'numeric', 'letters', 'reg'
        self.nbits = 0
        if vaction():
            msgb("ARGACTION", arg_action)
        if arg_action == 'nothing' or arg_action == "NOTHING":
            self.type = 'nothing'
            return
        # in the inputs, "error" gets expanded to "ERROR=1" via the statebits.
        if arg_action == 'error' or arg_action == "ERROR" or arg_action == 'ERROR=1':
            self.type = 'error'
            return

        b = patterns.bit_expand_pattern.search(arg_action)
        if b:
            expanded = b.group('bitname') * int(b.group('count'))
            action = patterns.bit_expand_pattern.sub(expanded,arg_action)
        else:
            action = arg_action
            
        #msgerr("CHECKING: %s" % action)

        a = patterns.equals_pattern.search(action)
        if a:
            # field binding
            #msgerr("FIELD BINDING: %s" % action)
            self.field_name = a.group('lhs')
            rhs = a.group('rhs')
            if patterns.decimal_pattern.match(rhs) or \
               patterns.binary_pattern.match(rhs) or \
               patterns.hex_pattern.match(rhs):
                self.int_value = genutil.make_numeric(rhs)
                self.value = str(self.int_value)
                #msgb("SET RHS", "%s -> %s" % (rhs,self.value))
            else:
                self.value = rhs
                
            self.type = 'FB'
            return
        
        nt = patterns.nt_name_pattern.match(action)
        if nt:
            # NTLUF or NT. Only shows up on decode-oriented rules
            self.nt = nt.group('ntname')
            self.type = 'nt'
            return
        ntluf = patterns.ntluf_name_pattern.match(action)
        if ntluf:
            # NTLUF or NT. Only shows up on decode-oriented rules
            self.ntluf = ntluf.group('ntname')
            self.type = 'ntluf'
            return
        
        cp = patterns.lhs_capture_pattern_end.match(action) 
        if cp:
            self.type = 'emit'
            self.value = cp.group('bits')
            self.field_name = cp.group('name').lower()
            #msgerr("EMIT ACTION %s" % action)
            self.classify()
            return 
        
        # simple byte encoding
        self.type = 'emit'
        self.field_name = None
        #msgerr("EMIT ACTION %s" % action)
        self.value = action
        self.classify()
        

    def classify(self):
        if patterns.decimal_pattern.match(self.value):
            self.emit_type = 'numeric'
            self.int_value = int(self.value)
            t = hex(self.int_value)
            self.nbits = 4*len(t[2:])
            if vclassify():
                msgb("CLASSIFY", "%s as decimal values" % (self.value))
            return
            
        if patterns.hex_pattern.match(self.value):
            self.emit_type = 'numeric'
            self.int_value = int(self.value,16)
            self.nbits = 4*(len(self.value)-2)  # drop the 0x, convert nibbles to bits
            if vclassify():
                msgb("CLASSIFY", "%s as hex" % (self.value))
            return
        if patterns.letter_and_underscore_pattern.match(self.value):
            self.emit_type = 'letters'
            t = self.value
            t = genutil.no_underscores(t)
            self.nbits = len(t)
            if vclassify():
                msgb("CLASSIFY", "%s as letters" % (self.value))
            return
        b = patterns.binary_pattern.match(self.value)   # leading "0b"
        if b:
            self.emit_type = 'numeric'
            t = '0b' + b.group('bits') # pattern match strips out 0b
            self.int_value = genutil.make_numeric(t)
            bits_str = genutil.make_binary(t)
            self.nbits = len(bits_str)
            if vclassify():
                msgb("CLASSIFY", "%s as explicit-binary -> int = %d nbits=%d [%s,%s]" % (self.value,self.int_value,self.nbits,t,bits_str))
            return
        if patterns.bits_and_letters_underscore_pattern.match(self.value):
            self.emit_type = 'letters'
            v = genutil.no_underscores(self.value)
            self.nbits = len(v)
            if vclassify():
                msgb("CLASSIFY", "%s as mixed-letters" % (self.value))
            return


        if patterns.simple_number_pattern.match(self.value):
            self.emit_type = 'numeric'
            self.int_value = genutil.make_numeric(self.value)
            t = hex(self.int_value)
            self.nbits = 4*len(t[2:])
            if vclassify():
                msgb("CLASSIFY", "%s as simple-number" % (self.value))
            return

        genutil.die("unknown pattern")
            
    def naked_bits(self):
        ''' returns True if the type is emit but there is no field name. '''
        if self.type == 'emit' and self.field_name == None:
            return True
        return False            
            
    def is_nothing(self):
        return self.type == 'nothing'
    def is_error(self):
        return self.type == 'error'
    def is_return(self):
        return self.type == 'return'
    
    def is_nonterminal(self):
        if self.nt:
            return True
        return False
    def is_ntluf(self):
        if self.ntluf:
            return True
        return False
    
    def is_field_binding(self):
        """Return True if this action is a field binding."""
        if self.type == 'FB':
            return True
        return False

    def is_emit_action(self):
        """Return True if this action is an emit action."""
        if self.type == 'emit':
            return True
        return False

    def __str__(self):
        s = []
        s.append(str(self.type))
        if self.nt:
            s.append(" NT[%s]" % (self.nt))
        if self.field_name:
            s.append(" ")
            s.extend([self.field_name,'=',self.value])
        elif self.value != None:
            s.append(" ")
            s.append(self.value)
        if self.emit_type:
            s.append(" emit_type=%s" % self.emit_type)
        if self.int_value != None:
            s.append(" value=0x%x" % self.int_value)
        if self.nbits != 0:
            s.append(" nbits=%d" % self.nbits)
        return ''.join(s)

    def get_str_value(self):
        if self.is_field_binding() or self.is_return():
            return self.value
        if self.is_nonterminal():
            return self.nt
        if self.is_ntluf():
            return self.ntluf
        
        err = "unsupported type: %s for function get_str_value" % self.type
        genutil.die(err)
    
    
    def emit_code(self, bind_or_emit):
        if self.is_error():
            #return [ '    return 0; /* error */' ]
            # FIXME? bind ERROR=1?
            return [ '    okay=0;  /* error */' ]
        elif self.is_nothing():
            return [ '    return 1; /* nothing */' ]

        elif self.is_field_binding():
            return self._generate_code_for_field_binding(bind_or_emit)
            
        elif self.is_emit_action():
            return self._generate_code_for_emit_action(bind_or_emit)
        
        elif self.is_nonterminal():
            return self._emit_nonterminal_code(bind_or_emit)
        
        else:
            return [ '/* FIXME action code not done yet for ' + self.__str__() + '*/' ]

    def _generate_code_for_field_binding(self, bind_or_emit):
        if bind_or_emit == 'EMIT':
            return []
        # we are in BIND
        if self.field_name == 'NO_RETURN':
            return [ '/* no code required for NO_RETURN binding */']
        operand_setter = "%s_set_%s" % (encutil.enc_strings['op_accessor'],
                                            self.field_name.lower())
        obj_name = encutil.enc_strings['obj_str']
        s = '    %s(%s,%s);' % (operand_setter,
                            obj_name, self.value)
        if self.field_name == 'ERROR':
            return [ s, "    return 0; /* error */" ]
        else:
            return [ s ]
    def _generate_code_for_emit_action(self,bind_or_emit):
        """Emit code for emit action """
        if bind_or_emit == 'BIND':
            if self.emit_type == 'letters' or self.field_name == None:
                return ''
            elif self.emit_type == 'numeric':
                op_accessor = encutil.enc_strings['op_accessor']
                operand_setter = "%s_set_%s" % (op_accessor,
                                                self.field_name.lower())
                obj_name = encutil.enc_strings['obj_str']
                hex_val = hex(self.int_value)
                code = "%s(%s, %s);" % (operand_setter, obj_name, hex_val)
                return ['    ' + code]
            else:
                genutil.die("Unknown emit_type %s" % self.emit_type)
        else:
            emit_util_function = encutil.enc_strings['emit_util_function']
            obj_name = encutil.enc_strings['obj_str']
            nbits = self.nbits
            code = ''
            if self.field_name == None:
                if  self.emit_type == 'numeric':
                    hex_val = hex(self.int_value)
                    code = "%s(%s, %d, %s);" % (emit_util_function,obj_name,
                                                nbits,hex_val)
                else:
                    genutil.die("must have field name for letter action")
            else:
                op_accessor = encutil.enc_strings['op_accessor']
                operand_getter = "%s_get_%s(%s)" % (op_accessor, 
                                                    self.field_name.lower(),
                                                    obj_name)
                code = "%s(%s, %d, %s);" % (emit_util_function,
                                            obj_name,nbits,operand_getter)
            return ['    ' + code]
    
    def _emit_nonterminal_code(self,bind_or_emit):
        """Emit code for calling a nonterminal in bind or emit modes"""
        
        nt_prefix = "%s" % encutil.enc_strings['nt_prefix']
        obj_name = encutil.enc_strings['obj_str']
        if bind_or_emit =='BIND':
            s = []
            s.append('    if (okay)')
            s.append('        okay = %s_%s_%s(%s);' %(nt_prefix,self.nt,
                                                       bind_or_emit,obj_name))
            return s
    
        else: #'EMIT'
            code = '%s_%s_%s(%s);' %(nt_prefix, self.nt, bind_or_emit, obj_name)
            return ['    ' + code] 
