
import isInteger from "is-integer";
import isNumber from "lodash/lang/isNumber";
import clone from "lodash/lang/clone";
import * as t from "babel-types";
import {Token} from "horchata";

const SCIENTIFIC_NOTATION = /e/i;

export function UnaryExpression(node) {
  // operator can be any token type that has prefix: true
  // TODO: generic node lookup
  var s = clone(Token.stateFromCode(node.operator === "!" ? "not" : node.operator));
  s.meta = {unary: true};
  this.push(s);
  this.print(node, "argument");
}

export function DoExpression(node) {
  this.push("do");
  this.printBlock(node);
}

export function ParenthesizedExpression(node) {
  this.push("(");
  this.print(node, "expression");
  this.push(")");
}

export function UpdateExpression(node) {
  if (node.prefix) {
    this.push(node.operator);
    this.print(node, "argument");
  } else {
    this.print(node, "argument");
    this.push(node.operator);
  }
}

// TODO: ToggleExpression - see frappe "nice alternative to nested ternaries"

export function ConditionalExpression(node) {
  this.push("if");
  // TODO: or if parent is an expression
  if (!node.parenthesizedExpression) this.push("!");
  this.print(node, "test");
  this.push("then");
  this.print(node, "consequent");
  this.push("else");
  this.print(node, "alternate");

}

export function NewExpression(node) {
  this.push("new");
  this.print(node, "callee");
  if (!node.emptyArguments || node.arguments.length) this.printArguments(node);
}

export function SequenceExpression(node) {
  this.printMultiple(node, "expressions", {separator: ";"});
}

export function ThisExpression() {
  this.push("this");
}

export function Super() {
  this.push("super");
}

export function Decorator(node) {
  this.push("@");
  this.print(node, "expression");
  this.newline();
}

export function CallExpression(node) {
  this.print(node, "callee");
  this.printArguments(node);
}

function buildYieldAwait(keyword) {
  return function (node) {
    this.push(keyword);

    if (node.delegate || node.all) {
      this.push("*");
    }

    if (node.argument) {
      this.print(node, "argument");
    }
  };
}

export let YieldExpression = buildYieldAwait("yield");
export let AwaitExpression = buildYieldAwait("await");

export function EmptyStatement() {
  this.push("pass");
  this.newline();
}

export function ExpressionStatement(node) {
  this.print(node, "expression");
  this.newline();
}

export function AssignmentPattern(node) {
  this.print(node, "left");
  this.push("=");
  this.print(node, "right");
}

export function AssignmentExpression(node) {
  this.print(node, "left");
  // can be any token that has {isAssign: true}
  this.push(node.operator);
  this.print(node, "right");
}

export function BinaryExpression(node) {
  // ensure valid
  if (t.isFunctionExpression(node.left) && node.left.body.body.length === 0) {
    let extra = node.left.extra == null ? node.left.extra = {} : node.left.extra;
    if (extra && !extra.parenthesized) {
      extra.parenthesized = true;
      extra.autoParens = true;
    }
  }
  // all other binary operators
  this.print(node, "left");
  let operator = node.operator;
  if (!this.dialect["equality-symbols"]) {
    switch (operator) {
      case "===": operator = "is"; break;
      case "!==": operator = "isnt"; break;
      case "==": operator = "like"; break;
      case "!=": operator = "unlike"; break;
    }
  }
  this.push(operator);
  this.print(node, "right");
}

export function LogicalExpression(node) {
  // ensure valid
  if (t.isFunctionExpression(node.left) && node.left.body.body.length === 0) {
    let extra = node.left.extra == null ? node.left.extra = {} : node.left.extra;
    if (extra && !extra.parenthesized) {
      extra.parenthesized = true;
      extra.autoParens = true;
    }
  }
  this.print(node, "left");
  this.push(node.operator == "||" ? "or" : "and");
  this.print(node, "right");
}

export function BindExpression(node) {
  if (node.object != null) this.print(node, "object");
  this.push("::");
  this.print(node, "callee");
}

export function MemberExpression(node) {
  this.print(node, "object");

  if (!node.computed && t.isMemberExpression(node.property)) {
    throw new TypeError("Got a MemberExpression for MemberExpression property");
  }

  let computed = node.computed;
  if (t.isLiteral(node.property) && isNumber(node.property.value)) {
    computed = true;
  }

  if (computed) {
    this.push("[");
    this.print(node, "property");
    this.push("]");
  } else {
    if (t.isLiteral(node.object)) {
      // TODO: preserve original format
      let val = this._stringLiteral(node.object);
      if (isInteger(+val) && !SCIENTIFIC_NOTATION.test(val) && !this.endsWith(".")) {
        this.push(".");
      }
    }

    this.push(".");
    this.print(node, "property");
  }
}

// like `new.target`
export function MetaProperty(node) {
  this.print(node, "meta");
  this.push(".");
  this.print(node, "property");
}

// TODO: SoakExpression
// TODO: NullCoalesceExpression

@@ -1895,16 +1895,23 @@
 de);%0A%7D%0A%0A
+export 
 function
@@ -1915,53 +1915,23 @@
 ion 
-buildYieldAwait(keyword) %7B%0A  return funct
+YieldExpress
 ion
- 
 (nod
@@ -1931,26 +1931,24 @@
 ion(node) %7B%0A
-  
   this.push(
@@ -1951,21 +1951,19 @@
 ush(
-keyword
+%22yield%22
 );%0A%0A
-  
   if
@@ -1981,26 +1981,12 @@
 gate
- %7C%7C node.all
 ) %7B%0A
-  
     
@@ -2001,25 +2001,21 @@
 h(%22*%22);%0A
-  
   %7D%0A%0A
-  
   if (no
@@ -2025,26 +2025,24 @@
 argument) %7B%0A
-  
     this.pri
@@ -2069,16 +2069,9 @@
 ;%0A  
-  %7D%0A  %7D;
+%7D
 %0A%7D%0A%0A
@@ -2081,110 +2081,126 @@
 ort 
-let YieldExpression = buildYieldAwait(%22yield%22);%0Aexport let AwaitExpression = buildYieldAwait(%22await%22);
+function AwaitExpression(node) %7B%0A  this.push(%22await%22);%0A%0A  if (node.argument) %7B%0A    this.print(node, %22argument%22);%0A  %7D%0A%7D
 %0A%0Aex

Remove await* from generator