viz: Show x for method invocation suggestions

Add x to remove a method invocation suggestion.
This x will remove the invocation and refresh the suggestions.
(This allows you to clear out all of your suggestions.)

An event was added recently called 'delete-item'.
paper-autocomplete input suggestions can also be deleted.
Use the mouse keys to hover over an item then press Shift+Delete.

Note: The two deletion mechanisms are not linked.

Change-Id: I9c1bc10ea8d12f14f061c844109e1164a62da3cf
diff --git a/src/app.js b/src/app.js
index fd7d6a5..4554b82 100644
--- a/src/app.js
+++ b/src/app.js
@@ -131,7 +131,7 @@
 
 // Add additional events that mercury's delegator should listenTo.
 addDelegatedEvents(['core-overlay-open-completed',
-    'down', 'up', 'tap', 'openchange', 'activate']);
+    'down', 'up', 'tap', 'openchange', 'activate', 'delete-item']);
 
 function wireEvents() {
   // TODO(aghassemi): Make these events global.
diff --git a/src/components/browse/item-details/method-form/index.js b/src/components/browse/item-details/method-form/index.js
index 22dc90c..4ec517d 100644
--- a/src/components/browse/item-details/method-form/index.js
+++ b/src/components/browse/item-details/method-form/index.js
@@ -91,12 +91,14 @@
   });
 
   var events = mercury.input([
-    'displayMethodForm',  // the main event used to prepare the form data
-    'methodStart',        // for parent element to be notified of RPC start
-    'methodEnd',          // for parent element to be notified of RPC end result
-    'runAction',          // run the RPC with given arguments
-    'expandAction',       // show/hide method arguments
-    'starAction',         // star/unstar a method invocation
+    'displayMethodForm',     // the main event used to prepare the form data
+    'methodStart',           // notify parent element of RPC start
+    'methodEnd',             // notify parent element of RPC end result
+    'runAction',             // run the RPC with given arguments
+    'expandAction',          // show/hide method arguments
+    'starAction',            // star/unstar a method invocation
+    'removeRecommendation',  // remove and reload recommended invocations
+    'removeInputSuggestion', // remove and reload input suggestions
     'toast'
   ]);
   wireUpEvents(state, events);
@@ -286,6 +288,58 @@
       log.error('Error while starring invocation', err);
     });
   });
+
+  // This event removes the specified recommendation.
+  // Afterwards, the recommendations are refreshed.
+  events.removeRecommendation(function(args) {
+    var input = {
+      interface: state.interface(),
+      methodName: state.methodName(),
+      value: JSON.stringify(args),
+      reset: true
+    };
+
+    smartService.update('learner-method-invocation', input).then(function() {
+      log.debug('Removing method invocation', input);
+      return refreshRecommendations(state);
+    }).then(function() {
+      return events.toast({
+        text: 'Removed suggestion: ' + getMethodSignature(state(), args),
+        type: 'info'
+      });
+    }).catch(function(err) {
+      var errText = 'Failed to remove suggestion';
+      log.error(errText, err);
+      events.toast({
+        text: errText,
+        type: 'error'
+      });
+    });
+  });
+
+  // This event removes the specified input suggestion for an argument.
+  // Afterwards, all input suggestions are refreshed.
+  events.removeInputSuggestion(function(data) {
+    var input = {
+      interface: state.interface(),
+      methodName: state.methodName(),
+      argName: data.argName,
+      value: data.arg,
+      reset: true
+    };
+
+    smartService.update('learner-method-input', input).then(function() {
+      log.debug('Removing method input', input);
+      return refreshInputSuggestions(state);
+    }).catch(function(err) {
+      var errText = 'Failed to remove suggestion';
+      log.error(errText, err);
+      events.toast({
+        text: errText,
+        type: 'error'
+      });
+    });
+  });
 }
 
 /*
@@ -309,7 +363,7 @@
   // Form for filling up the arguments
   var argForm = []; // contains form elements
   for (var i = 0; i < state.args.length; i++) {
-    argForm.push(renderMethodInput(state, i));
+    argForm.push(renderMethodInput(state, events, i));
   }
 
   // Setup the STAR and RUN buttons.
@@ -375,7 +429,7 @@
     'ev-click': mercury.event(events.expandAction)
   }, h('core-icon.action-icon', {
     attributes: {
-      'icon': state.expanded ? 'expand-less' : 'expand-more'
+      'icon': state.expanded ? 'expand-more' : 'chevron-right'
     }
   }));
 
@@ -437,7 +491,7 @@
   var count = 0;
   state.recommended.forEach(function(rec) {
     if (count < remainingRecommendations && state.starred.indexOf(rec) === -1) {
-      s.push(renderInvocation(state, events, rec));
+      s.push(renderInvocation(state, events, rec, true));
       count++;
     }
   });
@@ -450,7 +504,7 @@
  * argsStr is optional and is used for starred and recommended invocations.
  * When given, then the card is smaller and has a star icon.
  */
-function renderInvocation(state, events, argsStr) {
+function renderInvocation(state, events, argsStr, recommended) {
   var noArgs = argsStr === undefined;
   var args = noArgs ? [] : JSON.parse(argsStr);
   var labelText = getMethodSignature(state, args);
@@ -476,7 +530,22 @@
     })
   }, renderStarIcon(starred));
 
-  return h('div.item.card.invocation', [starButton, label, runButton]);
+  var negFeedback;
+  if (recommended) {
+    negFeedback = h('div.action-bar', h('paper-fab', {
+      attributes: {
+        'aria-label': 'Remove suggestion',
+        'title': 'Remove suggestion',
+        'icon': 'clear',
+        'mini': true
+      },
+      'ev-click': events.removeRecommendation.bind(null, args)
+    }));
+  }
+
+  return h('div.item.card.invocation',
+    [starButton, label, runButton, negFeedback]
+  );
 }
 
 /*
@@ -490,7 +559,7 @@
  * Draws a single method argument input using the paper-autocomplete element.
  * Includes a placeholder and suggestions from the internal state.
  */
-function renderMethodInput(state, index) {
+function renderMethodInput(state, events, index) {
   var methodName = state.methodName;
   var inArg = getMethodData(state.interface, state.methodName).inArgs[index];
   var argName = inArg.name;
@@ -500,14 +569,30 @@
 
   // The children are the suggestions for this paper-autocomplete input.
   var children = inputSuggestions.map(function(suggestion) {
-    return h('paper-item', suggestion);
+    return h('paper-item', {
+      attributes: {
+        // Attach as an attribute for later retrieval
+        'input-suggestion': suggestion
+      }
+    }, suggestion);
   });
 
+  // Event used to update state when the value is changed.
   var changeEvent = new PropertyValueEvent(function(data) {
     log.debug(methodName, argName, 'value changed.', data);
     args[index] = data;
   }, 'value');
 
+  // TODO(alexfandrianto): It may be nice to link feedback between the
+  // method-input and method-invocation learners.
+  // Event used to remove an input suggestion.
+  var removeEvent = function(e) {
+    events.removeInputSuggestion({
+      argName: argName,
+      arg: e.target.getAttribute('input-suggestion')
+    });
+  };
+
   // TODO(alexfandrianto): Note that Mercury and Polymer create a bug together.
   // Polymer normally captures internal events and stops them from propagating.
   // Unfortunately, Mercury reads and replays events using capturing mode.
@@ -519,7 +604,8 @@
       'value': args[index],
       'maxItems': METHOD_INPUT_MAX_ITEMS
     },
-    'ev-change': changeEvent
+    'ev-change': changeEvent,
+    'ev-delete-item': removeEvent
   }, children);
 
   return elem;
diff --git a/src/services/smart/service-implementation.js b/src/services/smart/service-implementation.js
index 6d2421b..38bf4c5 100644
--- a/src/services/smart/service-implementation.js
+++ b/src/services/smart/service-implementation.js
@@ -323,6 +323,10 @@
 /*
  * Given input data, boost the rank of the given value and penalize others.
  * Note: Learners using this predict function need to be similar structurally.
+ * input can contain:
+ * - fields used to compute the key
+ * - value to update for that key
+ * - (optional) reset: a flag that resets the value
  */
 function topKLearnerUpdate(input) {
   var key = this.computeKey(input);
@@ -338,6 +342,12 @@
     values[value] = 0;
   }
 
+  // Reset the value (for negative feedback).
+  if (input.reset) {
+    delete values[value];
+    return;
+  }
+
   // Give a reward to the chosen value.
   values[value] += this.params.reward * (1 - values[value]);