System.register(['./utils.js', 'src/client/preferences.js'], function (_export, _context) {
  "use strict";

  var isVariable, Preferences;
  return {
    setters: [function (_utilsJs) {
      isVariable = _utilsJs.isVariable;
    }, function (_srcClientPreferencesJs) {
      Preferences = _srcClientPreferencesJs.default;
    }],
    execute: function () {
      const __SystemJSRewritingHack = {};
      _recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js = _recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js || {};
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'markMemberToNotBeRewritten', {
        get() {
          return markMemberToNotBeRewritten;
        },

        set(thisIsVererySecretVariableName) {
          markMemberToNotBeRewritten = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'isVariable', {
        get() {
          return isVariable;
        },

        set(thisIsVererySecretVariableName) {
          isVariable = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'Preferences', {
        get() {
          return Preferences;
        },

        set(thisIsVererySecretVariableName) {
          Preferences = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });


      const AEXPR_IDENTIFIER_NAME = 'aexpr';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'AEXPR_IDENTIFIER_NAME', {
        get() {
          return AEXPR_IDENTIFIER_NAME;
        },

        set(thisIsVererySecretVariableName) {
          AEXPR_IDENTIFIER_NAME = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const GET_MEMBER = 'getMember';
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'GET_MEMBER', {
        get() {
          return GET_MEMBER;
        },

        set(thisIsVererySecretVariableName) {
          GET_MEMBER = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const GET_AND_CALL_MEMBER = 'getAndCallMember';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'GET_AND_CALL_MEMBER', {
        get() {
          return GET_AND_CALL_MEMBER;
        },

        set(thisIsVererySecretVariableName) {
          GET_AND_CALL_MEMBER = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const TRACE_MEMBER = 'traceMember';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'TRACE_MEMBER', {
        get() {
          return TRACE_MEMBER;
        },

        set(thisIsVererySecretVariableName) {
          TRACE_MEMBER = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const SET_MEMBER_BY_OPERATORS = {
        '=': 'setMember',
        '+=': 'setMemberAddition',
        '-=': 'setMemberSubtraction',
        '*=': 'setMemberMultiplication',
        '/=': 'setMemberDivision',
        '%=': 'setMemberRemainder',
        '**=': 'setMemberExponentiation',
        '<<=': 'setMemberLeftShift',
        '>>=': 'setMemberRightShift',
        '>>>=': 'setMemberUnsignedRightShift',
        '&=': 'setMemberBitwiseAND',
        '^=': 'setMemberBitwiseXOR',
        '|=': 'setMemberBitwiseOR'
      };

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'SET_MEMBER_BY_OPERATORS', {
        get() {
          return SET_MEMBER_BY_OPERATORS;
        },

        set(thisIsVererySecretVariableName) {
          SET_MEMBER_BY_OPERATORS = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const DELETE_MEMBER = 'deleteMember';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'DELETE_MEMBER', {
        get() {
          return DELETE_MEMBER;
        },

        set(thisIsVererySecretVariableName) {
          DELETE_MEMBER = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const SET_LOCAL = 'setLocal';
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'SET_LOCAL', {
        get() {
          return SET_LOCAL;
        },

        set(thisIsVererySecretVariableName) {
          SET_LOCAL = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const GET_LOCAL = 'getLocal';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'GET_LOCAL', {
        get() {
          return GET_LOCAL;
        },

        set(thisIsVererySecretVariableName) {
          GET_LOCAL = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const SET_GLOBAL = 'setGlobal';
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'SET_GLOBAL', {
        get() {
          return SET_GLOBAL;
        },

        set(thisIsVererySecretVariableName) {
          SET_GLOBAL = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const GET_GLOBAL = 'getGlobal';

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'GET_GLOBAL', {
        get() {
          return GET_GLOBAL;
        },

        set(thisIsVererySecretVariableName) {
          GET_GLOBAL = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const IGNORE_STRING = 'aexpr ignore';
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'IGNORE_STRING', {
        get() {
          return IGNORE_STRING;
        },

        set(thisIsVererySecretVariableName) {
          IGNORE_STRING = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const IGNORE_INDICATOR = Symbol('aexpr ignore');

      // TODO: use multiple flag for indication of generated content, marking explicit scopes, etc.
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'IGNORE_INDICATOR', {
        get() {
          return IGNORE_INDICATOR;
        },

        set(thisIsVererySecretVariableName) {
          IGNORE_INDICATOR = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const FLAG_GENERATED_SCOPE_OBJECT = Symbol('FLAG: generated scope object');
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'FLAG_GENERATED_SCOPE_OBJECT', {
        get() {
          return FLAG_GENERATED_SCOPE_OBJECT;
        },

        set(thisIsVererySecretVariableName) {
          FLAG_GENERATED_SCOPE_OBJECT = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const FLAG_SHOULD_NOT_REWRITE_IDENTIFIER = Symbol('FLAG: should not rewrite identifier');
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'FLAG_SHOULD_NOT_REWRITE_IDENTIFIER', {
        get() {
          return FLAG_SHOULD_NOT_REWRITE_IDENTIFIER;
        },

        set(thisIsVererySecretVariableName) {
          FLAG_SHOULD_NOT_REWRITE_IDENTIFIER = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION = Symbol('FLAG: should not rewrite member expression');
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION', {
        get() {
          return FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION;
        },

        set(thisIsVererySecretVariableName) {
          FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION = Symbol('FLAG: should not rewrite call expression');
      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION', {
        get() {
          return FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION;
        },

        set(thisIsVererySecretVariableName) {
          FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION = Symbol('FLAG: should not rewrite assignment expression');

      Object.defineProperty(_recorder_._src_client_reactive_babel_plugin_active_expression_rewriting_index_js, 'FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION', {
        get() {
          return FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION;
        },

        set(thisIsVererySecretVariableName) {
          FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      function markMemberToNotBeRewritten(path) {
        path[FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION] = true;
        return path;
      }

      _export('default', function (babel) {
        let { types: t, template, traverse } = babel;

        function getPropertyFromMemberExpression(node) {
          // We are looking for MemberExpressions, which have two distinct incarnations:
          // 1. we have a computed MemberExpression like a[b], with the property being an Expression
          // 2. a non-computed MemberExpression like a.b, with the property being an Identifier
          return node.computed ?
          // We can easily deal with the first case by replacing the MemberExpression with a call
          node.property :
          // In the second case, we introduce a StringLiteral matching the Identifier
          t.stringLiteral(node.property.name);
        }

        const GENERATED_FUNCTION = Symbol("generated function");

        function isGenerated(path) {
          return path.findParent(p => t.isFunctionDeclaration(p.node) && p.node[GENERATED_FUNCTION]);
        }

        const tNonRewritable = new Proxy({}, {
          get(target, prop, receiver) {
            return (...args) => {
              const node = t[prop](...args);
              // --> #TODO: should use a common 'doNotRewriteNode' and 'doNotRewriteSubtree' flags
              node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
              return node;
            };
          }
        });

        function nonRewritableIdentifier(name) {
          return tNonRewritable.identifier(name);
          const node = t.identifier(name);
          node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
          return node;
        }

        const GENERATED_IMPORT_IDENTIFIER = Symbol("generated import identifier");

        //     let customTemplates = {};
        //     customTemplates[SET_MEMBER] = template(`
        //   (function(obj, prop, operator, val) {
        //     return obj[prop] = val;
        //   });
        // `);
        //
        //     customTemplates[GET_MEMBER] = template(`
        //   (function(obj, prop) {
        //     return obj[prop];
        //   });
        // `);
        //
        //     customTemplates[GET_AND_CALL_MEMBER] = template(`
        //   (function(obj, prop, args) {
        //     return obj[prop](...args)
        //   });
        // `);
        //
        //     customTemplates[AEXPR_IDENTIFIER_NAME] = template(`
        //   (function(expr) {
        //     return { onChange(cb) {}};
        //   });
        // `);

        function addCustomTemplate(file, name) {
          let declar = file.declarations[name];
          if (declar) return declar;

          let identifier = file.declarations[name] = file.addImport("active-expression-rewriting", name, name);
          identifier[GENERATED_IMPORT_IDENTIFIER] = true;
          identifier[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
          return identifier;

          // let ref = customTemplates[name];
          // console.log(file.addImport("active-expression-rewriting", "aexpr"));
          // let uid = file.declarations[name] = file.scope.generateUidIdentifier(name);
          //
          // ref = ref().expression;
          // ref[GENERATED_FUNCTION] = true;
          //
          // if (t.isFunctionExpression(ref) && !ref.id) {
          //     ref.body._compact = true;
          //     ref._generated = true;
          //     ref.id = uid;
          //     ref.type = "FunctionDeclaration";
          //     file.path.unshiftContainer("body", ref);
          // } else {
          //     ref._compact = true;
          //     file.scope.push({
          //         id: uid,
          //         init: ref,
          //         unique: true
          //     });
          // }
          //
          // return uid;
        }

        // #TODO: add global flag for expression analysis mode
        function checkExpressionAnalysisMode(node) {
          return t.ifStatement(markMemberToNotBeRewritten(t.memberExpression(nonRewritableIdentifier('self'), nonRewritableIdentifier('__expressionAnalysisMode__'))), t.expressionStatement(node
          // ,t.unaryExpression("void", t.numericLiteral(0), true)
          ));
        }

        function isInForLoopIterator(path) {
          const isInForLoop = path.find(p => {
            if (!p.parentPath) return false;
            if (p.parentPath.isForStatement() && ['init', 'test', 'update'].includes(p.parentKey)) return true;
            if (p.parentPath.isForInStatement() && p.parentKey === 'left') return true;
            if (p.parentPath.isForOfStatement() && p.parentKey === 'left') return true;
            return false;
          });
          return !!isInForLoop;
        }

        function isInDestructuringAssignment(path) {
          const patternParent = path.find(p => {
            if (!p.isPattern()) {
              return false;
            }
            if (!p.parentPath) {
              return false;
            }

            const inVarDefinition = p.parentPath.isVariableDeclarator() && p.parentKey === 'id';
            const inAssignment = p.parentPath.isAssignmentExpression() && p.parentKey === 'left';
            const inParams = p.parentPath.isFunction() && p.parentKey === 'params';
            return inVarDefinition || inAssignment || inParams;
          });
          return !!patternParent;
        }

        return {
          pre(file) {
            //console.log("fff", file, traverse);
          },
          visitor: {
            Program: {
              enter(path, state) {
                function hasDirective(path, name) {
                  let foundDirective = false;
                  path.traverse({
                    Directive(path) {
                      if (path.get("value").node.value === name) {
                        foundDirective = true;
                      }
                    }
                  });
                  return foundDirective;
                }

                function shouldTransform() {
                  const proxyDirective = hasDirective(path, 'use proxies for aexprs');
                  const proxyPreference = Preferences.get('UseProxiesForAExprs');
                  const inWorkspace = state.opts.executedIn === 'workspace';
                  const inFile = state.opts.executedIn === 'file';

                  if (inWorkspace) {
                    return !proxyPreference;
                  } else if (inFile) {
                    return !proxyDirective;
                  }
                  return true;
                  throw new Error('This should not be possible');
                }

                if (!shouldTransform()) {
                  return;
                }

                //console.log("file", path, state);
                // console.log("AEXPR", state && state.file && state.file.log && state.file.log.filename);
                //console.log("AEXPR", path, state, state && state.opts && state.opts.enableViaDirective)
                if (state.opts.enableViaDirective && !hasDirective(path, "enable aexpr")) {
                  return;
                }

                function getIdentifierForExplicitScopeObject(parentWithScope) {
                  let bindings = parentWithScope.scope.bindings;
                  let scopeName = Object.keys(bindings).find(key => {
                    return bindings[key].path && bindings[key].path.node && bindings[key].path.node.id && bindings[key].path.node.id[FLAG_GENERATED_SCOPE_OBJECT]; // should actually be IS_EXPLICIT_SCOPE_OBJECT
                  });

                  let uniqueIdentifier;
                  if (scopeName) {
                    uniqueIdentifier = t.identifier(scopeName);
                  } else {
                    uniqueIdentifier = parentWithScope.scope.generateUidIdentifier('scope');
                    uniqueIdentifier[FLAG_GENERATED_SCOPE_OBJECT] = true;

                    parentWithScope.scope.push({
                      kind: 'let',
                      id: uniqueIdentifier,
                      init: t.objectExpression([])
                    });
                  }
                  uniqueIdentifier[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
                  return uniqueIdentifier;
                }

                function rewriteReadGlobal(path) {
                  if (path.node.name !== 'eval') {
                    path.node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
                    prependGetGlobal(path);
                  }
                }
                function prependGetGlobal(path) {
                  path.insertBefore(checkExpressionAnalysisMode(t.callExpression(addCustomTemplate(state.file, GET_GLOBAL), [t.stringLiteral(path.node.name)])));
                }
                function wrapSetGlobal(path) {
                  const valueToReturn = t.identifier(path.node.left.name);
                  valueToReturn[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
                  path.replaceWith(t.sequenceExpression([path.node, t.callExpression(addCustomTemplate(state.file, SET_GLOBAL), [t.stringLiteral(path.node.left.name)]), valueToReturn]));
                }

                // ------------- ensureBlock -------------
                const maybeWrapInStatement = (node, wrapInReturnStatement) => {
                  if (t.isStatement(node)) {
                    return node;
                  } else if (t.isExpression(node)) {
                    // wrap in return statement if we have an arrow function: () => 42 -> () => { return 42; }
                    const expressionNode = wrapInReturnStatement ? t.returnStatement(node) : t.expressionStatement(node);
                    expressionNode.loc = node.loc;
                    return expressionNode;
                  } else {
                    console.error("Tried to wrap something unknown:", node);
                    return node;
                  }
                };
                const wrapPropertyOfPath = (path, property) => {
                  const oldBody = path.get(property);
                  const oldBodyNode = path.node[property];
                  if (!oldBodyNode) {
                    return;
                  }
                  if (oldBody.isBlockStatement && oldBody.isBlockStatement()) {
                    // This is already a block
                    return;
                  } else if (oldBody instanceof Array) {
                    const newBodyNode = t.blockStatement(oldBodyNode);
                    path.node[property] = [newBodyNode];
                  } else {
                    const newBodyNode = t.blockStatement([maybeWrapInStatement(oldBodyNode, path.isArrowFunctionExpression())]);
                    oldBody.replaceWith(newBodyNode);
                  }
                  return path;
                };
                path.traverse({
                  BlockParent(path) {
                    if (path.isProgram() || path.isBlockStatement() || path.isSwitchStatement()) {
                      return;
                    }
                    if (!path.node.body) {
                      console.warn("A BlockParent without body: ", path);
                    }

                    wrapPropertyOfPath(path, "body");
                  },
                  IfStatement(path) {
                    for (let property of ["consequent", "alternate"]) {
                      wrapPropertyOfPath(path, property);
                    }
                  },
                  SwitchCase(path) {
                    wrapPropertyOfPath(path, "consequent");
                  }
                });

                path.traverse({

                  UnaryExpression(path) {
                    if (isInForLoopIterator(path)) {
                      return;
                    }
                    if (isInDestructuringAssignment(path)) {
                      return;
                    }

                    // handle delete operator
                    if (path.node.operator === 'delete') {
                      const argument = path.get('argument');
                      if (argument.isMemberExpression()) {
                        path.replaceWith(t.callExpression(addCustomTemplate(state.file, DELETE_MEMBER), [argument.node.object, getPropertyFromMemberExpression(argument.node)]));
                      }
                      return;
                    }

                    // transform ~[expr] notation to _aexpr(() => expr)
                    if (path.node.operator !== '~') return;
                    const array = path.get('argument');
                    if (!array.isArrayExpression()) return;
                    if (array.get('elements').length !== 1) return;
                    const expr = array.get('elements')[0];

                    path.replaceWith(t.callExpression(addCustomTemplate(state.file, AEXPR_IDENTIFIER_NAME), [t.arrowFunctionExpression([], expr.node
                    // path.node.left.object,
                    // getPropertyFromMemberExpression(path.node.left),
                    // //t.stringLiteral(path.node.operator),
                    // path.node.right
                    )]));
                  },

                  UpdateExpression(path) {
                    const operator = path.node.operator;
                    const prefix = path.node.prefix;
                    const argument = path.get('argument');
                    if (isInForLoopIterator(path)) {
                      return;
                    }
                    if (isInDestructuringAssignment(path)) {
                      return;
                    }

                    if (argument.isMemberExpression() || argument.isIdentifier()) {

                      // ++v -> v += 1
                      let assignment = t.assignmentExpression(operator[0] + '=', argument.node, t.numericLiteral(1));

                      if (!prefix) {
                        // need to modify result for postfix operators
                        // v++ -> (v += 1) - 1
                        const reverseOperator = operator[0] === '+' ? '-' : '+';
                        assignment = t.binaryExpression(reverseOperator, assignment, t.numericLiteral(1));
                      }

                      path.replaceWith(assignment);
                    }
                  },

                  /*MD # Identifier MD*/
                  Identifier(path) {
                    //console.log(path.node.name);

                    function logIdentifier(msg, path) {
                      console.log(msg, path.node.name, path.node.loc ? path.node.loc.start.line : '');
                    }

                    if (path.node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER]) {
                      return;
                    }

                    // Check for a call to undeclared aexpr:
                    if (t.isCallExpression(path.parent) && path.node.name === AEXPR_IDENTIFIER_NAME && !path.scope.hasBinding(AEXPR_IDENTIFIER_NAME)) {
                      let fileName = state && state.file && state.file.log && state.file.log.filename || 'no_file_given';
                      if (fileName.startsWith('workspace:') && fileName.includes('unnamed_module_')) {
                        fileName = 'workspace:' + fileName.split('unnamed_module_')[1];
                      }
                      const sourceLocation = template(`({
                  file: '${fileName}',
                  end: {
                    column: END_COLUMN,
                    line: END_LINE
                  },
                  start: {
                    column: START_COLUMN,
                    line: START_LINE
                  },
                  source: ''
                })`);
                      function buildSourceLocation(path) {
                        let node = path.node;
                        let wrapper = {
                          directives: [],
                          start: 0,
                          end: 0,
                          type: 'Program',
                          sourceType: 'module',
                          body: [path.parent.arguments[0]] // #BUG: expression should be put into an ExpressionStatement
                        };
                        // let source = babel.transformFromAst(wrapper, {sourceType: 'module'}).code;
                        return sourceLocation({
                          END_COLUMN: t.numericLiteral(node.loc.end.column),
                          END_LINE: t.numericLiteral(node.loc.end.line),
                          START_COLUMN: t.numericLiteral(node.loc.start.column),
                          START_LINE: t.numericLiteral(node.loc.start.line)
                          // SOURCE: source
                        }).expression;
                      }
                      let location = buildSourceLocation(path);
                      //logIdentifier("call to aexpr", path);
                      path.replaceWith(addCustomTemplate(state.file, AEXPR_IDENTIFIER_NAME));
                      if (path.parentPath.get('arguments').some(any => any.isSpreadElement())) {
                        return;
                      }
                      if (path.parentPath.get('arguments').length > 1) {
                        let argument = path.parentPath.get('arguments')[1];
                        if (argument.isObjectExpression()) {
                          argument.pushContainer('properties', t.objectProperty(t.identifier('location'), location));
                        } else {
                          let assignment = template(`Object.assign({location : LOCATION}, EXPR || {})`);
                          let a = assignment({ LOCATION: location, EXPR: argument }).expression;
                          argument.replaceWith(a);
                        }
                        // path.parentPath.pushContainer('arguments', t.objectExpression([
                        //   t.objectProperty(t.identifier('location'), location)
                        // ]));
                      } else {
                        path.parentPath.pushContainer('arguments', t.objectExpression([t.objectProperty(t.identifier('location'), location)]));
                      }
                      return;
                    }

                    // property local of ExportStatement
                    if (t.isExportSpecifier(path.parent) && path.parentKey === 'local') {
                      return;
                    }

                    if (
                    // TODO: is there a general way to exclude non-variables?
                    isVariable(path) && !t.isMetaProperty(path.parent) && !(t.isForInStatement(path.parent) && path.parentKey === 'left') && !(t.isAssignmentPattern(path.parent) && path.parentKey === 'left') && !t.isUpdateExpression(path.parent) && !(t.isFunctionExpression(path.parent) && path.parentKey === 'id') && !(t.isImportDefaultSpecifier(path.parent) && path.parentKey === 'local') && !(t.isCatchClause(path.parent) && path.parentKey === 'param') && !(t.isContinueStatement(path.parent) && path.parentKey === 'label') && !t.isObjectProperty(path.parent) && !t.isClassDeclaration(path.parent) && !t.isClassExpression(path.parent) && !t.isClassMethod(path.parent) && !t.isImportSpecifier(path.parent) && !t.isObjectMethod(path.parent) && !t.isVariableDeclarator(path.parent) && !t.isFunctionDeclaration(path.parent) && !(t.isArrowFunctionExpression(path.parent) && path.parentKey === 'params') && !(t.isExportSpecifier(path.parent) && path.parentKey === 'exported') && !(t.isFunctionExpression(path.parent) && path.parentKey === 'params') && !t.isRestElement(path.parent) && (!t.isAssignmentExpression(path.parent) || !(path.parentKey === 'left'))) {
                      if (isInForLoopIterator(path)) {
                        return;
                      }
                      if (isInDestructuringAssignment(path)) {
                        return;
                      }

                      if (path.scope.hasBinding(path.node.name)) {
                        //logIdentifier('get local var', path)
                        path.node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;

                        let parentWithScope = path.findParent(par => par.scope.hasOwnBinding(path.node.name));
                        if (parentWithScope) {
                          //function printParents(path) {
                          //  let result = [path.type];
                          //  path.findParent(p => {
                          //    result.push(p.type);
                          //    return false;
                          //  });
                          //  console.log(result.join('\n\r'))
                          //}
                          //printParents(path);
                          //const node = path.parentPath.parentPath;
                          //                                    const statement = path.getStatementParent();
                          //                                      console.warn(statement, statement.type);
                          //node.unshiftContainer('body', t.expressionStatement(t.stringLiteral("HELLO!?")));
                          // lively.openInspector(statement);
                          //statement.insertBefore(t.expressionStatement(t.stringLiteral("WORLD")))
                          //printParents(path.getStatementParent())
                          //printParents(path.getFunctionParent())
                          //path.getFunctionParent().ensureBlock();
                          //path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go.")));

                          path.insertBefore(checkExpressionAnalysisMode(t.callExpression(addCustomTemplate(state.file, GET_LOCAL), [getIdentifierForExplicitScopeObject(parentWithScope), t.stringLiteral(path.node.name), nonRewritableIdentifier(path.node.name)])));
                        } else if (path.scope.hasGlobal(path.node.name)) {
                          // #TODO: remove this code duplication
                          rewriteReadGlobal(path);
                        } else {
                          // #TODO: can this be the case? Neither locally scoped nor global.
                        }
                      } else {
                        //logIdentifier('get global var', path);
                        rewriteReadGlobal(path);
                      }
                      return;
                    }

                    //logIdentifier('others', path);
                    return;

                    if (path.node[GENERATED_IMPORT_IDENTIFIER]) {
                      logIdentifier('Generated Import Identifier', path);
                      return;
                    }

                    // global?
                    //if (!path.isReferencedIdentifier()) {
                    //logIdentifier('is no referenced identifier', path);
                    //return;
                    //}

                    // The identifier should not reference a variable in current scope
                    if (path.scope.hasBinding(path.node.name)) {
                      logIdentifier('Binding for ', path);
                      return;
                    }

                    logIdentifier('Unexpected Case', path);

                    return;

                    //if(RESERVED_IDENTIFIERS.includes(path.node.name)) { return; }

                    if (t.isClassDeclaration(path.parent)) {
                      console.log("classDecl", path.node.name);
                      return;
                    }

                    if (t.isClassMethod(path.parent)) {
                      console.log("classMethod", path.node.name);
                      return;
                    }

                    if (t.isObjectMethod(path.parent)) {
                      console.log("objectMethod", path.node.name);
                      return;
                    }
                    if (t.isVariableDeclarator(path.parent)) {
                      console.log("varDecl", path.node.name);
                      return;
                    }

                    // is this correct here?
                    // TODO: is it correct for the locals plugin?
                    if (!path.isReferencedIdentifier()) {
                      console.log("def", path.node.name);
                      return;
                    }

                    // is locally defined variable?
                    if (path.scope.hasBinding(path.node.name)) {
                      console.log("local", path.node.name);
                    } else {
                      // we have a global
                      console.log("global", path.node.name);
                    }
                  },
                  /*MD # AssigmentExpression MD*/
                  AssignmentExpression(path) {
                    if (isInForLoopIterator(path)) {
                      return;
                    }
                    if (isInDestructuringAssignment(path)) {
                      return;
                    }
                    // check, whether we assign to a member (no support for pattern right now)
                    if (t.isMemberExpression(path.node.left) && !isGenerated(path) && SET_MEMBER_BY_OPERATORS[path.node.operator]) {

                      //state.file.addImport
                      path.replaceWith(t.callExpression(addCustomTemplate(state.file, SET_MEMBER_BY_OPERATORS[path.node.operator]), [path.node.left.object, getPropertyFromMemberExpression(path.node.left),
                      //t.stringLiteral(path.node.operator),
                      path.node.right]));
                    }

                    if (t.isIdentifier(path.node.left) && !path.node[FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION]) {
                      if (path.scope.hasBinding(path.node.left.name)) {
                        //console.log('set local', path.node.left.name);
                        path.node[FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION] = true;

                        const parentWithScope = path.findParent(par => par.scope.hasOwnBinding(path.node.left.name));
                        if (parentWithScope) {
                          let valueToReturn = t.identifier(path.node.left.name);
                          valueToReturn[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
                          let valueForAExpr = t.identifier(path.node.left.name);
                          valueForAExpr[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
                          // #TODO: turn into .insertAfter
                          // caution: doing so automatically inserts a temporary variable (_temp), which is in turn rewritten!
                          //path.insertAfter(
                          //t.ifStatement(
                          //  t.booleanLiteral(true),
                          //  t.expressionStatement(
                          //    t.callExpression(
                          //      addCustomTemplate(state.file, SET_LOCAL),
                          //      [
                          //        getIdentifierForExplicitScopeObject(parentWithScope),
                          //        t.stringLiteral(path.node.left.name)
                          //      ]
                          //    )
                          //  )
                          //)
                          //);
                          path.replaceWith(t.sequenceExpression([path.node, t.conditionalExpression(t.booleanLiteral(true), t.callExpression(addCustomTemplate(state.file, SET_LOCAL), [getIdentifierForExplicitScopeObject(parentWithScope), t.stringLiteral(path.node.left.name), valueForAExpr]), t.unaryExpression('void', t.numericLiteral(0))), valueToReturn]));
                        } else if (path.get('left').scope.hasGlobal(path.node.left.name)) {
                          path.node[FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION] = true;
                          wrapSetGlobal(path);
                        } else {
                          // #TODO: can this be the case? Neither locally scoped nor global.
                        }
                      } else {
                        // global assginment
                        //console.log('---global---', path.node.left.name);
                        path.node[FLAG_SHOULD_NOT_REWRITE_ASSIGNMENT_EXPRESSION] = true;
                        wrapSetGlobal(path);
                      }
                    }
                  },

                  /*MD # MemberExpression MD*/
                  MemberExpression(path) {
                    // lval (left values) are ignored for now
                    if (path.parentPath.isCallExpression() && path.parentKey === "callee") {
                      return;
                    }
                    // #TODO: BindExpressions are ignored for now; they are static anyway ;)
                    if (path.parentPath.isBindExpression() && path.parentKey === "callee") {
                      return;
                    }
                    if (t.isAssignmentExpression(path.parent) && path.key === 'left') {
                      return;
                    }
                    if (t.isUpdateExpression(path.parent) && path.key === 'argument') {
                      return;
                    }
                    if (t.isSuper(path.node.object)) {
                      return;
                    }
                    if (isGenerated(path)) {
                      return;
                    }
                    if (isInForLoopIterator(path)) {
                      return;
                    }
                    if (isInDestructuringAssignment(path)) {
                      return;
                    }
                    if (path.node[FLAG_SHOULD_NOT_REWRITE_MEMBER_EXPRESSION]) {
                      return;
                    }

                    path.replaceWith(t.callExpression(addCustomTemplate(state.file, GET_MEMBER), [path.node.object, getPropertyFromMemberExpression(path.node)]));
                  },

                  /*MD # CallExpression MD*/
                  CallExpression(path) {
                    if (isGenerated(path)) {
                      return;
                    }
                    if (path.node.callee && t.isSuper(path.node.callee.object)) {
                      return;
                    }
                    if (path.node[FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION]) {
                      return;
                    }
                    if (isInForLoopIterator(path)) {
                      return;
                    }
                    if (isInDestructuringAssignment(path)) {
                      return;
                    }

                    // check whether we call a MemberExpression
                    if (t.isMemberExpression(path.node.callee)) {
                      function isDuplicatableMemberExpression(memberPath) {
                        const objectPath = memberPath.get('object');
                        const isSimpleObject = objectPath.isIdentifier() || objectPath.isThisExpression();
                        const isComputedProperty = memberPath.node.computed;
                        const propertyPath = memberPath.get('property');
                        let isSimpleProperty;
                        if (isComputedProperty) {
                          isSimpleProperty = propertyPath.isStringLiteral() || propertyPath.isIdentifier();
                        } else {
                          isSimpleProperty = propertyPath.isIdentifier();
                        }
                        return isSimpleObject && isSimpleProperty;
                      }

                      if (isDuplicatableMemberExpression(path.get("callee"))) {
                        function getTraceIdentifierForSimpleObject(objectPath) {
                          if (objectPath.isIdentifier()) {
                            return nonRewritableIdentifier(objectPath.node.name);
                          } else if (objectPath.isThisExpression()) {
                            return t.thisExpression();
                          } else {
                            throw objectPath.buildCodeFrameError("Tried to trace a simple MemberExpression>object, but it is neither an Identifier nor a ThisExpression");
                          }
                        }

                        const traceIdentifier = getTraceIdentifierForSimpleObject(path.get('callee').get('object'));
                        // break a recursive call expression when doing `(obj.fn(), obj.fn());`
                        path.node[FLAG_SHOULD_NOT_REWRITE_CALL_EXPRESSION] = true;
                        // insert traceMember before actual call
                        path.insertBefore(checkExpressionAnalysisMode(t.callExpression(addCustomTemplate(state.file, TRACE_MEMBER), [traceIdentifier, getPropertyFromMemberExpression(path.node.callee)])));
                      } else {
                        path.replaceWith(t.callExpression(addCustomTemplate(state.file, GET_AND_CALL_MEMBER), [path.node.callee.object, getPropertyFromMemberExpression(path.node.callee), t.arrayExpression(path.node.arguments)]));
                      }
                    } else {
                      // call to a local/global variable is handled elsewhere
                      if (t.isIdentifier(path.node.callee) && true) {}
                    }
                  }

                });
              }
            }
          }
        };
      });
    }
  };
});