text: import logging
  import os
  from copy import deepcopy
  from typing import Union
  
  from checkov.common.util.consts import RESOLVED_MODULE_ENTRY_NAME
  from checkov.terraform.graph_builder.graph_components.attribute_names import CustomAttributes
  from checkov.terraform.graph_builder.graph_components.block_types import BlockType
  from checkov.terraform.graph_builder.utils import remove_module_dependency_in_path
  from checkov.terraform.checks.utils import utils
  from checkov.terraform.checks.utils.utils import calculate_hash, decode_graph_property_value, join_trimmed_strings
  
  
  class Block:
      def __init__(self, name, config, path, block_type, attributes, id='', source='', encode=False):
          """
              :param name: unique name given to the terraform block, for example: 'aws_vpc.example_name'
              :param config: the section in tf_definitions that belong to this block
              :param path: the file location of the block
              :param block_type: BlockType
              :param attributes: dictionary of the block's original attributes in the terraform file
          """
          self.name = name
          self.config = deepcopy(config)
          self.module_dependency = ""
          self.module_dependency_num = ""
          self.path = path
          if path:
              self.path, module_dependency, num = remove_module_dependency_in_path(path)
              self.path = os.path.realpath(self.path)
              if module_dependency:
                  self.module_dependency = module_dependency
                  self.module_dependency_num = num
          self.block_type = block_type
          if attributes.get(RESOLVED_MODULE_ENTRY_NAME):
              del attributes[RESOLVED_MODULE_ENTRY_NAME]
          self.attributes = attributes
          self.id = id
          self.source = source
          self.changed_attributes = {}
          self.breadcrumbs = {}
          self.module_connections = {}
          self.source_module = set()
  
          attributes_to_add = self._extract_inner_attributes()
          self.attributes.update(attributes_to_add)
          self.encode = encode
  
      def _extract_inner_attributes(self):
          attributes_to_add = {}
          for attribute_key in self.attributes:
              attribute_value = self.attributes[attribute_key]
              if type(attribute_value) is list and len(attribute_value) > 0 and type(attribute_value[0]) is dict:
                  inner_attributes = get_inner_attributes(attribute_key, attribute_value)
                  attributes_to_add.update(inner_attributes)
          return attributes_to_add
  
      def __str__(self):
          return str(self.block_type) + ': ' + self.name
  
      def get_attribute_dict(self):
          """
             :return: map of all the block's native attributes (from the source file),
             combined with the attributes generated by the module builder.
             If the attributes are not a primitive type, they are converted to strings.
             """
          base_attributes = self.get_base_attributes()
          self.get_origin_attributes(base_attributes)
  
          if self.changed_attributes:
              # add changed attributes only for calculating the hash
              changed_attributes_keys = list(self.changed_attributes.keys())
              changed_attributes_keys.sort()
              base_attributes['changed_attributes'] = changed_attributes_keys
  
          if self.breadcrumbs:
              sorted_breadcrumbs = dict(sorted(self.breadcrumbs.items()))
              base_attributes[CustomAttributes.RENDERING_BREADCRUMBS] = sorted_breadcrumbs
  
          if self.encode:
              for attribute in base_attributes:
                  value_to_encode = base_attributes[attribute]
                  encoded_value = utils.encode_graph_property_value(value_to_encode)
                  base_attributes[attribute] = encoded_value
  
          base_attributes[CustomAttributes.HASH] = calculate_hash(base_attributes)
  
          if base_attributes.get('changed_attributes'):
              # removed changed attributes if it was added previously for calculating hash.
              del base_attributes['changed_attributes']
  
          return base_attributes
  
      def get_origin_attributes(self, base_attributes):
          for attribute_key in list(self.attributes.keys()):
              attribute_value = self.attributes[attribute_key]
              if type(attribute_value) is list and len(attribute_value) == 1:
                  attribute_value = attribute_value[0]
              if type(attribute_value) is dict or type(attribute_value) is list:
                  inner_attributes = get_inner_attributes(attribute_key, attribute_value)
                  base_attributes.update(inner_attributes)
              if attribute_key == 'self':
                  base_attributes['self_'] = attribute_value
                  continue
              else:
                  base_attributes[attribute_key] = attribute_value
  
      def get_hash(self):
          attributes_dict = self.get_attribute_dict()
          return attributes_dict.get(CustomAttributes.HASH)
  
      def get_decoded_attribute_dict(self):
          attributes = self.get_attribute_dict()
          if self.encode:
              for attribute_key in attributes:
                  attributes[attribute_key] = decode_graph_property_value(attributes[attribute_key])
          return attributes
  
      def update_attribute(self, attribute_key, attribute_value, change_origin_id, previous_breadcrumbs):
          if not previous_breadcrumbs or previous_breadcrumbs[-1] != change_origin_id:
              previous_breadcrumbs.append(change_origin_id)
  
          self.update_inner_attribute(attribute_key, self.attributes, attribute_value)
          attribute_key_parts = attribute_key.split('.')
          if len(attribute_key_parts) == 1:
              self.attributes[attribute_key] = attribute_value
              self.changed_attributes[attribute_key] = previous_breadcrumbs
              return
          for i in range(len(attribute_key_parts)):
              key = join_trimmed_strings(char_to_join=".", str_lst=attribute_key_parts, num_to_trim=i)
              if key.find('.') > -1:
                  self.attributes[key] = attribute_value
                  attribute_value = {attribute_key_parts[len(attribute_key_parts)-1 - i]: attribute_value}
                  self.changed_attributes[key] = previous_breadcrumbs
  
      def update_inner_attribute(self, attribute_key: str, nested_attributes: Union[list, dict], value_to_update):
          split_key = attribute_key.split('.')
          i = 1
          curr_key = '.'.join(split_key[0:i])
          if type(nested_attributes) is list:
              if curr_key.isnumeric():
                  curr_key = int(curr_key)
                  if curr_key < len(nested_attributes):
                      if not isinstance(nested_attributes[curr_key], dict):
                          nested_attributes[curr_key] = value_to_update
                      else:
                          self.update_inner_attribute('.'.join(split_key[i:]), nested_attributes[curr_key], value_to_update)
              else:
                  for inner in nested_attributes:
                      self.update_inner_attribute(curr_key, inner, value_to_update)
          elif type(nested_attributes) is dict:
              while curr_key not in nested_attributes and i <= len(split_key):
                  i += 1
                  curr_key = '.'.join(split_key[0:i])
              if attribute_key in nested_attributes.keys():
                  nested_attributes[attribute_key] = value_to_update
              if len(split_key) == 1:
                  nested_attributes[curr_key] = value_to_update
              elif curr_key in nested_attributes.keys():
                  self.update_inner_attribute('.'.join(split_key[i:]), nested_attributes[curr_key], value_to_update)
  
      def add_module_connection(self, attribute_key, vertex_id):
          if not self.module_connections.get(attribute_key):
              self.module_connections[attribute_key] = []
          self.module_connections[attribute_key].append(vertex_id)
  
      def find_attribute(self, attribute):
          """
          :param attribute: key to search in self.attribute
          The function searches for  attribute in self.attribute. It might not exist if the block is variable or output,
          or its search path might be different if its a resource.
          :return: the actual attribute key or None
          """
          if not attribute:
              return None
  
          if self.attributes.get(attribute[0]):
              return attribute[0]
  
          if self.block_type == BlockType.VARIABLE:
              return 'default' if self.attributes.get('default') else None
  
          if self.block_type == BlockType.OUTPUT:
              return 'value' if self.attributes.get('value') else None
  
          if self.block_type == BlockType.RESOURCE and len(attribute) > 1:
              # handle cases where attribute_at_dest == ['aws_s3_bucket.template_bucket', 'acl']
              if self.name == attribute[0] and self.attributes.get(attribute[1]):
                  return attribute[1]
  
          return None
  
      def get_export_data(self):
          return {'type': self.block_type.value, 'name': self.name, 'path': self.path}
  
      def get_base_attributes(self):
          return {
              CustomAttributes.BLOCK_NAME: self.name,
              CustomAttributes.BLOCK_TYPE: self.block_type.value,
              CustomAttributes.FILE_PATH: self.path,
              CustomAttributes.CONFIG: self.config,
              CustomAttributes.LABEL: self.__str__(),
              CustomAttributes.ID: self.id,
              CustomAttributes.SOURCE: self.source
          }
  
  
  def get_inner_attributes(attribute_key, attribute_value):
      inner_attributes = {}
      if type(attribute_value) is list and len(attribute_value) == 1:
          attribute_value = attribute_value[0]
  
      if type(attribute_value) in [dict, list]:
          inner_attributes[attribute_key] = [None] * len(attribute_value) if type(attribute_value) is list else {}
          iterator = range(len(attribute_value)) if type(attribute_value) is list else list(attribute_value.keys())
          for key in iterator:
              if key != '':
                  inner_key = f'{attribute_key}.{key}'
                  inner_value = attribute_value[key]
                  inner_attributes.update(get_inner_attributes(inner_key, inner_value))
                  inner_attributes[attribute_key][key] = inner_attributes[inner_key]
              else:
                  del attribute_value[key]
      else:
          inner_attributes[attribute_key] = attribute_value
      return inner_attributes
  