# vimspector - A multi-language debugging system for Vim
# Copyright 2021 <NAME>
#
# 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.

import functools
import typing
from collections.abc import Mapping

MEMO = {}


def memoize( func ):
  global MEMO

  @functools.wraps( func )
  def wrapper( *args, **kwargs ):
    dct = MEMO.setdefault( func, {} )
    key = ( args, frozenset( kwargs.items() ) )
    try:
      return dct[ key ]
    except KeyError:
      result = func( *args, **kwargs )
      dct[ key ] = result
      return result

  return wrapper


def override( target_dict: typing.MutableMapping,
              override_dict: typing.Mapping ):
  """Apply the updates in override_dict to the dict target_dict. This is like
  dict.update, but recursive. i.e. if the existing element is a dict, then
  override elements of the sub-dict rather than wholesale replacing.

  One special case is added. If a key within override dict starts with '!' then
  it is interpretted as follows:
     - if the associated value is "REMOVE", the key is removed from the parent
       dict
     - use !! for keys that actually start with ! and shouldn't be removed.

  e.g.
  override(
    {
      'outer': { 'inner': { 'key': 'oldValue', 'existingKey': True } }
    },
    {
      'outer': { 'inner': { 'key': 'newValue' } },
      'newKey': { 'newDict': True },
    }
  )
  yields:
    {
      'outer': {
        'inner': {
           'key': 'newValue',
           'existingKey': True
        }
      },
      'newKey': { newDict: True }
    }
  """

  for key, value in override_dict.items():
    #
    # Handle special ! syntax:
    #   "!keyname" : "REMOVE",   --> remove the key 'keyname' from target_dict
    #
    if key[ 0 : 1 ] == "!" and key[ 1 : 2 ] != "!":
      key = key [ 1: ]
      if value == "REMOVE":
        target_dict.pop( key, None )
        continue

    current_value = target_dict.get( key )
    if not isinstance( current_value, Mapping ):
      # Thing or Mapping overrides Thing or None
      target_dict[ key ] = value
    elif isinstance( value, Mapping ):
      # Mapping overrides mapping, recurse
      target_dict[ key ] = override( current_value, value )
    else:
      # Thing overrides Mapping
      target_dict[ key ] = value

  return target_dict
