
import {writeValue} from './memory';
import {scalarTypes, pointerType, arrayType} from './type';
import {IntegralValue, PointerValue, ArrayValue, zeroAtType} from './value';
import {findClosestBlockScope, findClosestFunctionScope} from './scope';

const applyLoadEffect = function (state, effect) {
  // ['load', ref]
  const {core} = state;
  const ref = effect[1];
  core.memoryLog = core.memoryLog.push(effect);
};

const applyStoreEffect = function (state, effect) {
  // ['store', ref, value]
  const {core} = state;
  const ref = effect[1];
  const value = effect[2];
  core.memory = writeValue(core.memory, ref, value);
  core.memoryLog = core.memoryLog.push(effect);
};

const applyEnterEffect = function (state, effect) {
  // ['enter', blockNode]
  const {core} = state;
  const parentScope = core.scope;
  const blockNode = effect[1];
  core.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'block',
    blockNode
  }
};

const applyLeaveEffect = function (state, effect) {
  // ['leave', blockNode]
  const {core} = state;
  const scope = findClosestBlockScope(core.scope, effect[1]);
  if (!scope) {
    console.log('stack underflow', core.scope, effect);
    throw new Error('stack underflow');
  }
  core.scope = scope.parent;
};

const applyCallEffect = function (state, effect) {
  // ['call', cont, [func, args...]]
  const {core} = state;
  const parentScope = core.scope;
  const cont = effect[1];
  const values = effect[2];
  core.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: parentScope.limit,
    kind: 'function',
    cont,
    values
  };
};

const applyReturnEffect = function (state, effect) {
  // ['return', result]
  const {core} = state;
  const result = effect[1];
  const scope = findClosestFunctionScope(core.scope);
  if (!scope) {
    console.log('stack underflow', core.scope, effect);
    throw new Error('stack underflow');
  }
  // Pop all scopes up to and including the function's scope.
  core.scope = scope.parent;
  // Transfer control to the caller's continuation…
  core.control = scope.cont;
  // passing the return value to the caller (handling the special case for
  // control leaving the 'main' function without a return statement, where
  // C99 defines the result as being 0).
  if (!result && scope.cont.values[0].name === 'main') {
    core.result = new IntegralValue(scalarTypes['int'], 0);
  } else {
    core.result = result;
  }
  // Set direction to 'out' to indicate that a function was exited.
  core.direction = 'out';
};

const applyVardeclEffect = function (state, effect) {
  // ['vardecl', name, type, init]
  const {core} = state;
  const parentScope = core.scope;
  const name = effect[1];
  const type = effect[2];
  const init = effect[3];
  const address = parentScope.limit - type.size;
  const ref = new PointerValue(pointerType(type), address);
  core.scope = {
    parent: parentScope,
    key: parentScope.key + 1,
    limit: address,
    kind: 'variable',
    name, type, ref
  };
  if (init) {
    applyStoreEffect(state, ['store', ref, init]);
  }
};

export const defaultEffects = {
  load: applyLoadEffect,
  store: applyStoreEffect,
  enter: applyEnterEffect,
  leave: applyLeaveEffect,
  call: applyCallEffect,
  return: applyReturnEffect,
  vardecl: applyVardeclEffect
};

// applyEffect applies an effect by shallowly mutating the passed state
// (both state and state.core are mutated).
export const applyEffect = function (state, effect) {
  const {effectHandlers} = state.options;
  const handler = effectHandlers[effect[0]];
  if (typeof handler === 'function') {
    return handler(state, effect);
  } else {
    console.log('invalid handler', effect[0])
    return state;
  }
};

@@ -2743,20 +2743,18 @@
 t%5B1%5D;%0A  
-cons
+le
 t type =
@@ -2797,21 +2797,17 @@
 ;%0A  
-const address
+let limit
  = p
@@ -2826,29 +2826,372 @@
 imit
- - type.size;%0A  const
+;%0A  let ref, skipInit = false;%0A  if (type.kind === 'array' && init && init.type.kind === 'pointer') %7B%0A    // When an array variable is initialized with a ref (as opposed to an%0A    // array value), no stack allocation or initialization occurs.%0A    // The array type 'type' is used instead of the pointer type 'init.type'%0A    // when building the variable's ref.%0A   
  ref
@@ -3229,16 +3229,21 @@
 (type), 
+init.
 address)
@@ -3244,16 +3244,196 @@
 dress);%0A
+    skipInit = true;%0A  %7D else %7B%0A    // Allocate memory on stack and build a ref to that location.%0A    limit -= type.size;%0A    ref = new PointerValue(pointerType(type), limit);%0A  %7D%0A
   core.s
@@ -3507,23 +3507,21 @@
  limit: 
-address
+limit
 ,%0A    ki
@@ -3567,17 +3567,22 @@
 ;%0A  if (
-i
+!skipI
 nit) %7B%0A 

add support for passing an array (reference) to a function