<link rel="import" href="../iron-collapse/iron-collapse.html">
<link rel="import" href="../iron-list/iron-list.html">
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/all-imports.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
<link rel="import" href="../tf-graph/tf-graph-icon.html">
<link rel="import" href="tf-node-list-item.html">

<dom-module id="tf-node-info">
  <style>
  .sub-list-group {
    font-weight: 500;
    font-size: 12pt;
    padding-bottom: 8px;
    width: 100%;
  }

  .sub-list {
    max-height: 300px;
    overflow-y: scroll;
  }

  .attr-left {
    float: left;
    width: 30%;
    word-wrap: break-word;
    color: #565656;
    font-size: 11pt;
    font-weight: 400;
  }

  .attr-right {
    margin-left: 30%;
    word-wrap: break-word;
    color: #565656;
    font-weight: 400;
  }

  .sub-list-table {
    display: table;
    width: 100%;
  }

  .sub-list-table-row {
    display: table-row;
  }

  .sub-list-table-row .sub-list-table-cell:last-child {
    text-align: right;
  }

  .sub-list-table-cell {
    color: #565656;
    display: table-cell;
    font-size: 11pt;
    font-weight: 400;
    max-width: 200px;
    padding: 0 4px;
  }

  paper-item {
    padding: 0;
    background: #e9e9e9;
  }

  paper-item-body[two-line] {
    min-height: 0;
    padding: 8px 12px 4px;
  }

  .expandedInfo {
    padding: 8px 12px;
  }

  .controlDeps {
    padding: 0 0 0 8px;
  }

  .node-name {
    white-space: normal;
    word-wrap: break-word;
    font-size: 14pt;
    font-weight: 500;
  }

  .node-icon {
    float: right;
  }

  .subtitle {
    font-size: 12pt;
    color: #5e5e5e;
  }

  .controlLine {
    font-size: 11pt;
    font-weight: 400;
  }

  .toggle-button {
    float: right;
    max-height: 20px;
    max-width: 20px;
    padding: 0;
  }

  .control-toggle-button {
    float: left;
    max-height: 20px;
    max-width: 20px;
    padding: 0;
  }

  .toggle-include-group {
    padding-top: 4px;
  }

  .toggle-include {
    margin: 5px 6px;
    text-transform: none;
    padding: 4px 6px;
    font-size: 10pt;
    background-color: #fafafa;
    color: #666;
  }

  .toggle-include:hover {
    background-color: var(--google-yellow-100);
  }

  .non-control-list-item {
    padding-left: 10px;
  }
  </style>
  <template>
    <paper-item>
      <paper-item-body two-line>
        <div>
          <paper-icon-button
            icon="{{_getToggleIcon(_expanded)}}"
            on-click="_toggleExpanded"
            class="toggle-button">
          </paper-icon-button>
          <div class="node-name" id="nodetitle"></div>
        </div>
        <div secondary>
          <tf-graph-icon class="node-icon" node="[[_node]]"
              render-info="[[_getRenderInfo(nodeName, renderHierarchy)]]"
              color-by="[[colorBy]]"
              template-index="[[_templateIndex]]"
              ></tf-graph-icon>
          <template is="dom-if" if="{{_node.op}}">
            <div class="subtitle">
              Operation:
              <span>[[_node.op]]</span>
            </div>
          </template>
          <template is="dom-if" if="{{_node.metagraph}}">
            <div class="subtitle">
              Subgraph:
              <span>[[_node.cardinality]]</span> nodes
            </div>
          </template>
        </div>
      </paper-item-body>
    </paper-item>
    <iron-collapse opened="{{_expanded}}">
    <template is="dom-if" if="{{_expanded}}" restamp="true">
      <div class="expandedInfo">
        <div class="sub-list-group attributes">
          Attributes
          (<span>[[_attributes.length]]</span>)
          <iron-list class="sub-list" id ="attributesList"
                    items="[[_attributes]]">
            <template>
              <div>
                <div class="attr-left">[[item.key]]</div>
                <div class="attr-right">[[item.value]]</div>
              </div>
            </template>
          </iron-list>
        </div>

        <template is="dom-if" if="{{_device}}">
          <div class="sub-list-group device">
            <div class="attr-left">Device</div>
            <div class="attr-right">[[_device]]</div>
          </div>
        </template>

        <div class="sub-list-group predecessors">
          Inputs
          (<span>[[_totalPredecessors]]</span>)
          <iron-list class="sub-list" id ="inputsList"
                    items="[[_predecessors.regular]]">
            <template>
              <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="predecessors"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]">
              </tf-node-list-item>
            </template>
          </iron-list>
          <template is="dom-if" if="[[_predecessors.control.length]]">
            <div class="controlDeps">
              <div class="controlLine">
                <paper-icon-button
                  icon="{{_getToggleIcon(_openedControlPred)}}"
                  on-click="_toggleControlPred"
                  class="control-toggle-button">
                </paper-icon-button>
                Control dependencies
              </div>
              <iron-collapse opened="{{_openedControlPred}}" no-animation>
                <template is="dom-if" if="{{_openedControlPred}}" restamp="true">
                  <iron-list class="sub-list" items="[[_predecessors.control]]">
                    <template>
                      <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="predecessors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]">
                      </tf-node-list-item>
                    </template>
                  </iron-list>
                </template>
              </iron-collapse>
            </div>
          </template>
        </div>

        <div class="sub-list-group successors">
          Outputs
          (<span>[[_totalSuccessors]]</span>)
          <iron-list class="sub-list" id ="outputsList"
                    items="[[_successors.regular]]">
            <template>
              <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="successor"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]">
              </tf-node-list-item>
            </template>
          </iron-list>
          <template is="dom-if" if="[[_successors.control.length]]">
            <div class="controlDeps">
              <div class="controlLine">
                <paper-icon-button
                  icon="{{_getToggleIcon(_openedControlSucc)}}"
                  on-click="_toggleControlSucc"
                  class="control-toggle-button">
                </paper-icon-button>
                Control dependencies
              </div>
              <iron-collapse opened="{{_openedControlSucc}}" no-animation>
                <template is="dom-if" if="{{_openedControlSucc}}" restamp="true">
                  <iron-list class="sub-list" items="[[_successors.control]]">
                    <template>
                      <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="successors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]">
                      </tf-node-list-item>
                    </template>
                  </iron-list>
                </template>
              </iron-collapse>
            </div>
          </template>
        </div>
        <template is="dom-if" if="{{_hasDisplayableNodeStats}}">
          <div class="sub-list-group node-stats">
            Node Stats
            <div class="sub-list-table">
              <template is="dom-if" if="{{_nodeStats.totalBytes}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Memory</div>
                  <div class="sub-list-table-cell">[[_nodeStatsFormattedBytes]]</div>
                </div>
              </template>
              <template is="dom-if" if="{{_nodeStats.totalMicros}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Compute Time</div>
                  <div class="sub-list-table-cell">[[_nodeStatsFormattedComputeTime]]</div>
                </div>
              </template>
              <template is="dom-if" if="{{_nodeStats.outputSize}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Tensor Output Sizes</div>
                  <div class="sub-list-table-cell">
                    <template is="dom-repeat" items="{{_nodeStatsFormattedOutputSizes}}">
                      [[item]] <br/>
                    </template>
                  </div>
                </div>
              </template>
            </div>
          </div>
        </template>
        <div class="toggle-include-group">
          <paper-button raised class="toggle-include" on-click="_toggleInclude">
            <span>[[_auxButtonText]]</span>
          </paper-button>
        </div>
        <template is="dom-if" if="{{_isInSeries(_node)}}">
          <div class="toggle-include-group">
            <paper-button raised class="toggle-include" on-click="_toggleGroup">
              <span>[[_groupButtonText]]</span>
            </paper-button>
          </div>
        </template>
      </div>
    </template>
    </iron-collapse>
  </template>

  <script>
    (function() {
      Polymer({
        is: 'tf-node-info',

        properties: {
          nodeName: String,
          graphHierarchy: Object,
          renderHierarchy: Object,
          /** What to color the nodes by (compute time, memory, device etc.) */
          colorBy: String,
          _templateIndex: {
            type: Function,
            computed: '_getTemplateIndex(graphHierarchy)'
          },
          _node: {
            type: Object,
            computed: '_getNode(nodeName, graphHierarchy)',
            observer: '_resetState'
          },
          _nodeStats: {
            type: Object,
            computed: '_getNodeStats(nodeName, graphHierarchy)',
            observer: '_resetState'
          },
          _hasDisplayableNodeStats: {
            type: Object,
            computed: '_getHasDisplayableNodeStats(_nodeStats)',
          },
          _nodeStatsFormattedBytes: {
            type: String,
            computed: '_getNodeStatsFormattedBytes(_nodeStats)',
          },
          _nodeStatsFormattedComputeTime: {
            type: String,
            computed: '_getNodeStatsFormattedComputeTime(_nodeStats)',
          },
          _nodeStatsFormattedOutputSizes: {
            type: Array,
            computed: '_getNodeStatsFormattedOutputSizes(_nodeStats)',
          },
          // The enum value of the include property of the selected node.
          nodeInclude: {
            type: Number,
            observer: '_nodeIncludeStateChanged'
          },
          _attributes: {
            type: Array,
            computed: '_getAttributes(_node)'
          },
          _device: {
            type: String,
            computed: '_getDevice(_node)'
          },
          _successors: {
            type: Object,
            computed: '_getSuccessors(_node, graphHierarchy)'
          },
          _predecessors: {
            type: Object,
            computed: '_getPredecessors(_node, graphHierarchy)'
          },
          _subnodes: {
            type: Array,
            computed: '_getSubnodes(_node)'
          },
          _expanded: {
            type: Boolean,
            value: true
          },
          _totalPredecessors: {
            type: Number,
            computed: '_getTotalPred(_predecessors)'
          },
          _totalSuccessors: {
            type: Number,
            computed: '_getTotalSucc(_successors)'
          },
          _openedControlPred: {
            type: Boolean,
            value: false
          },
          _openedControlSucc: {
            type: Boolean,
            value: false
          },
          _auxButtonText: String,
          _groupButtonText: String
        },
        expandNode: function() {
          this.fire('_node.expand', this.node);
        },
        _getTemplateIndex: function(graphHierarchy) {
          return graphHierarchy.getTemplateIndex();
        },
        _getNode: function(nodeName, graphHierarchy) {
          return graphHierarchy.node(nodeName);
        },
        _getNodeStats: function(nodeName, graphHierarchy) {
          var node = this._getNode(nodeName, graphHierarchy);
          if (node) {
            return node.stats;
          }
          return null;
        },
        _getHasDisplayableNodeStats: function(stats) {
          return tf.graph.util.hasDisplayableNodeStats(stats);
        },
        _getNodeStatsFormattedBytes(stats) {
          if (!stats || !stats.totalBytes) {
            return;
          }

          return tf.graph.util.convertUnitsToHumanReadable(
              stats.totalBytes, tf.graph.util.MEMORY_UNITS);
        },
        _getNodeStatsFormattedComputeTime(stats) {
          if (!stats || !stats.totalMicros) {
            return;
          }

          return tf.graph.util.convertUnitsToHumanReadable(
              stats.totalMicros, tf.graph.util.TIME_UNITS);
        },
        _getNodeStatsFormattedOutputSizes(stats) {
          if (!stats || !stats.outputSize || !stats.outputSize.length) {
            return;
          }

          return _.map(stats.outputSize, function(shape) {
            if (shape.length === 0) {
              return "scalar";
            }
            return "[" + shape.join(", ") + "]";
          });
        },
        _getPrintableHTMLNodeName: function(nodeName) {
          // Insert an optional line break before each slash so that
          // long node names wrap cleanly at path boundaries.
          return (nodeName || '').replace(/\//g, '<wbr>/');
        },
        _getRenderInfo: function(nodeName, renderHierarchy) {
          return this.renderHierarchy.getOrCreateRenderNodeByName(nodeName);
        },
        _getAttributes: function(node) {
          this.async(this._resizeList.bind(this, "#attributesList"));
          if (!node || !node.attr) {
            return [];
          }
          var attrs = [];
          _.each(node.attr, function(entry) {
            // Unpack the "too large" attributes into separate attributes
            // in the info card, with values "too large to show".
            if (entry.key === tf.graph.LARGE_ATTRS_KEY) {
              attrs = attrs.concat(entry.value.list.s.map(function(key) {
                return {key: key, value: "Too large to show..."};
              }));
            } else {
              attrs.push({
                key: entry.key,
                value: JSON.stringify(entry.value)
              });
            }
          });
          return attrs;
        },
        _getDevice: function(node) {
          return node ? node.device : null;
        },
        _getSuccessors: function(node, hierarchy) {
          this.async(this._resizeList.bind(this, "#inputsList"));
          if (!node) {
            return {regular: [], control: []}
          }
          return this._convertEdgeListToEdgeInfoList(
            hierarchy.getSuccessors(node.name), false, node.isGroupNode);
        },
        _getPredecessors: function(node, hierarchy) {
          this.async(this._resizeList.bind(this, "#outputsList"));
          if (!node) {
            return {regular: [], control: []}
          }
          return this._convertEdgeListToEdgeInfoList(
            hierarchy.getPredecessors(node.name), true, node.isGroupNode);
        },
        _convertEdgeListToEdgeInfoList: function(list, isPredecessor, isGroupNode) {

          /**
           * Unpacks the metaedge into a list of base edge information
           * that can be rendered.
           */
          var unpackMetaedge = function(metaedge) {
            return _.map(metaedge.baseEdgeList, function(baseEdge) {
              name = isPredecessor ? baseEdge.v : baseEdge.w;
              return {
                name: name,
                node: this._getNode(name, this.graphHierarchy),
                edgeLabel: tf.graph.scene.edge.getLabelForBaseEdge(baseEdge,
                    this.renderHierarchy),
                renderInfo: this._getRenderInfo(name, this.renderHierarchy)
              };
            }, this);
          }.bind(this);

          /**
           * Converts a list of metaedges to a list of edge information
           * that can be rendered.
           */
          var toEdgeInfoList = function(edges) {
            var edgeInfoList = [];
            _.each(edges, function(metaedge) {
              var name = isPredecessor ? metaedge.v : metaedge.w;
              // Enumerate all the base edges if the node is an OpNode, or the
              // metaedge has only 1 edge in it.
              if (!isGroupNode || metaedge.baseEdgeList.length == 1) {
                edgeInfoList = edgeInfoList.concat(unpackMetaedge(metaedge));
              } else {
                edgeInfoList.push({
                  name: name,
                  node: this._getNode(name, this.graphHierarchy),
                  edgeLabel: tf.graph.scene.edge.getLabelForEdge(metaedge,
                      this.renderHierarchy),
                  renderInfo: this._getRenderInfo(name, this.renderHierarchy)
                });
              }
            }, this);
            return edgeInfoList;
          }.bind(this);

          return {
            regular: toEdgeInfoList(list.regular),
            control: toEdgeInfoList(list.control)
          };
        },
        _getSubnodes: function(node) {
          return node && node.metagraph ? node.metagraph.nodes() : null;
        },
        _getTotalPred: function(predecessors) {
          return predecessors.regular.length + predecessors.control.length;
        },
        _getTotalSucc: function(successors) {
          return successors.regular.length + successors.control.length;
        },
        _toggleControlPred: function() {
          this._openedControlPred = !this._openedControlPred;
        },
        _toggleControlSucc: function() {
          this._openedControlSucc = !this._openedControlSucc;
        },
        _toggleExpanded: function() {
          this._expanded = !this._expanded;
        },
        _getToggleIcon: function(expanded) {
          return expanded ? "expand-less" : "expand-more";
        },
        _resetState: function() {
          this._openedControlPred = false;
          this._openedControlSucc = false;

          this.set("_groupButtonText",
            tf.graph.scene.node.getGroupSettingLabel(this._node));

          if (this._node) {
            Polymer.dom(this.$.nodetitle).innerHTML =
              this._getPrintableHTMLNodeName(this._node.name);
          }
        },
        _resizeList: function(selector) {
          var list = document.querySelector(selector);
          if (list) {
            list.fire('iron-resize');
          }
        },
        _toggleInclude: function() {
          var graphElem = document.querySelector("#graph");
          graphElem.fire("node-toggle-extract", { name: this.nodeName });
          var graphBoardElem = document.querySelector("#graphboard");
          graphBoardElem.fire("node-toggle-extract");
        },
        _nodeIncludeStateChanged: function(include, oldInclude) {
          this.set("_auxButtonText",
            tf.graph.getIncludeNodeButtonString(include));
        },
        _toggleGroup: function() {
          var graphElem = document.querySelector("#graph");
          var seriesName = tf.graph.scene.node.getSeriesName(this._node);
          graphElem.fire("node-toggle-seriesgroup", { name: seriesName });
        },
        _isInSeries: function(node) {
          return tf.graph.scene.node.canBeInSeries(node);
        }
      });
    })();
  </script>
</dom-module>
