import {
  ASTNode,
  parse,
  codegen,
  functions as baseFunctions,
  constants
} from 'vega-expression';
import {error, stringValue} from 'vega-util';

var Literal = 'Literal';
var Identifier = 'Identifier';

export var signalPrefix = '$';
export var scalePrefix  = '%';
export var indexPrefix  = '@';
export var eventPrefix  = 'event.vega.';

function signalCode(id) {
  return '_[' + stringValue('$' + id) + ']';
}

export var functions = function(codegen) {
  var fn = baseFunctions(codegen);

  // view-specific event information
  fn.view  = eventPrefix + 'view';
  fn.item  = eventPrefix + 'item';
  fn.group = eventPrefix + 'group';
  fn.xy    = eventPrefix + 'xy';
  fn.x     = eventPrefix + 'x';
  fn.y     = eventPrefix + 'y';

  // hyperlink support
  fn.open  = 'window.open';

  // color functions
  fn.rgb = 'this.rgb';
  fn.lab = 'this.lab';
  fn.hcl = 'this.hcl';
  fn.hsl = 'this.hsl';

  // scales, projections, data
  fn.scale  = 'this.scale';
  fn.invert = 'this.scaleInvert';
  fn.copy   = 'this.scaleCopy';
  fn.indata = 'this.indata';

  return fn;
};

export var generator = codegen({
  blacklist:  ['_'],
  whitelist:  ['datum', 'event'],
  fieldvar:   'datum',
  globalvar:  signalCode,
  functions:  functions,
  constants:  constants
});

function signal(name, scope, params) {
  var signalName = signalPrefix + name;
  if (!params.hasOwnProperty(signalName)) {
    params[signalName] = scope.signalRef(name);
  }
}

function scale(name, scope, params) {
  var scaleName = scalePrefix + name;
  if (!params.hasOwnProperty(scaleName)) {
    params[scaleName] = scope.scaleRef(name);
  }
}

function index(data, field, scope, params) {
  var indexName = indexPrefix + field;
  if (!params.hasOwnProperty(indexName)) {
    params[indexName] = scope.getData(data).indataRef(field);
  }
}

export default function(expr, scope, preamble) {
  var ast = parse(expr),
      params = {},
      gen;

  // analyze ast for dependencies
  ast.visit(function visitor(node) {
    if (node.type !== 'CallExpression') return;

    var name = node.callee.name,
        args = node.arguments;

    switch (name) {
      case 'scale':
      case 'invert':
        if (args[0].type === Literal) {           // scale dependency
          scale(args[0].value, scope, params);
        } else if (args[0].type === Identifier) { // forward reference to signal
          name = args[0].name;
          args[0] = new ASTNode(Literal);
          args[0].raw = '{signal:"' + name + '"}';
        } else {
          error('First argument to ' + name + ' must be a string literal or identifier.');
        }
        break;
      case 'copy':
        if (args[0].type !== Literal) error('Argument to copy must be a string literals.');
        scale(args[0].value, scope, params);
        break;
      case 'indata':
        if (args[0].type !== Literal) error('First argument to indata must be a string literal.');
        if (args[1].type !== Literal) error('Second argument to indata must be a string literal.');
        index(args[0].value, args[1].value, scope, params);
        break;
    }
  });

  // perform code generation
  gen = generator(ast);
  gen.globals.forEach(function(name) { signal(name, scope, params); });

  // returned parsed expression
  return {
    $expr:   preamble ? preamble + 'return(' + gen.code + ');' : gen.code,
    $fields: gen.fields,
    $params: params
  };
}

@@ -738,60 +738,8 @@
 ';%0A%0A
-  // hyperlink support%0A  fn.open  = 'window.open';%0A%0A
   //
@@ -755,16 +755,16 @@
 nctions%0A
+
   fn.rgb
@@ -879,16 +879,43 @@
 s, data%0A
+  fn.span   = 'this.span';%0A
   fn.sca
@@ -996,24 +996,24 @@
 scaleCopy';%0A
-
   fn.indata 
@@ -1030,16 +1030,326 @@
 data';%0A%0A
+  // interaction support%0A  fn.pinchDistance = 'this.pinchDistance';%0A  fn.pinchAngle    = 'this.pinchAngle';%0A%0A  // environment functions%0A  fn.open   = 'window.open';%0A  fn.screen = function() %7B return 'window.screen'; %7D;%0A  fn.windowsize = function() %7B%0A    return '%5Bwindow.innerWidth, window.innerHeight%5D';%0A  %7D;%0A%0A
   return
@@ -2149,16 +2149,52 @@
 %7B%0A  var 
+params = %7B%7D, ast, gen;%0A%0A  try %7B%0A    
 ast = pa
@@ -2206,39 +2206,77 @@
 xpr)
-,%0A      params = %7B%7D,%0A      gen;
+;%0A  %7D catch (err) %7B%0A    error('Expression parse error: ' + expr);%0A  %7D
 %0A%0A  

Add new expression functions, parse error handling.