/**
 * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.web.analytics;

import java.util.Collections;
import java.util.List;

import com.google.common.collect.Lists;
import com.opengamma.core.position.Portfolio;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.core.security.Security;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.util.ArgumentChecker;

/**
 * Minimal representation of a node in the structure of a portfolio. For use in an analytics grid. Contains a
 * list of sub-nodes and indices of the rows at which a node starts and ends.
 */
/* package */ class AnalyticsNode {

  /** Index of the row containing this node. */
  private final int _startRow;
  /** Index of the row containing this node's last child. */
  private final int _endRow;
  /** Immediate child nodes. */
  private final List<AnalyticsNode> _children;
  /** Whether this node represents a position in a fungible security, i.e. it has child nodes which are trades. */
  private final boolean _collapsed;

  /* package */ AnalyticsNode(int startRow, int endRow, List<AnalyticsNode> children, boolean collapsed) {
    ArgumentChecker.notNull(children, "children");
    _collapsed = collapsed;
    _startRow = startRow;
    _endRow = endRow;
    _children = children;
  }

  /**
   * Factory method that creates a node structure from a portfolio and returns the root node.
   * @param portfolio The portfolio
   * @return The root node of the portfolio's node structure, null if the portfolio is null
   */
  /* package */ static AnalyticsNode portfolioRoot(Portfolio portfolio) {
    if (portfolio == null) {
      return null;
    }
    PortfolioNode root = portfolio.getRootNode();
    return new PortfolioNodeBuilder(root).getRoot();
  }

  /**
   * @return The row index (zero-based and inclusive) at which the node starts.
   */
  /* package */ int getStartRow() {
    return _startRow;
  }

  /**
   * @return The row index (zero-based and inclusive) at which the node ends. This includes all nested child nodes.
   * i.e. the end row of a node is the same as the end row of its most deeply nested child.
   */
  /* package */ int getEndRow() {
    return _endRow;
  }

  /**
   * @return The direct children of this node.
   */
  /* package */ List<AnalyticsNode> getChildren() {
    return _children;
  }

  /**
   * @return Whether this node is collapsed
   */
  public boolean isCollapsed() {
    return _collapsed;
  }

  @Override
  public String toString() {
    return "AnalyticsNode [_startRow=" + _startRow + ", _endRow=" + _endRow + ", _children=" + _children + "]";
  }

  /**
   * Mutable builder that creates the node structure for a portfolio and returns the root node. Package-scoped for
   * testing.
   */
  /* package */ static final class PortfolioNodeBuilder {

    /** The root node of the portfolio. */
    private final AnalyticsNode _root;
    /** Index of last row, updated as the structure is built. */
    private int _lastRow;

    /* package */ PortfolioNodeBuilder(PortfolioNode root) {
      _root = createPortfolioNode(root, true);
      _lastRow = 0;
    }

    private AnalyticsNode createPortfolioNode(PortfolioNode node, boolean root) {
      int nodeStart = _lastRow;
      List<AnalyticsNode> nodes = Lists.newArrayList();
      for (Position position : node.getPositions()) {
        ++_lastRow;
        if (position.getTrades().size() > 0 && isFungible(position.getSecurity())) {
          nodes.add(createFungiblePositionNode(position));
        }
      }
      for (PortfolioNode child : node.getChildNodes()) {
        ++_lastRow;
        nodes.add(createPortfolioNode(child, false));
      }
      // collapse nodes if there are no child nodes (unless it's the root node which is never collapsed)
      boolean collapse = node.getChildNodes().isEmpty() && !root;
      return new AnalyticsNode(nodeStart, _lastRow, Collections.unmodifiableList(nodes), collapse);
    }

    private AnalyticsNode createFungiblePositionNode(Position position) {
      int nodeStart = _lastRow;
      _lastRow += position.getTrades().size();
      return new AnalyticsNode(nodeStart, _lastRow, Collections.<AnalyticsNode>emptyList(), true);
    }

    /**
     * @param security A security
     * @return true if the security is fungible, false if OTC
     */
    private static boolean isFungible(Security security) {
      if (security instanceof FinancialSecurity) {
        Boolean isOTC = ((FinancialSecurity) security).accept(new OtcSecurityVisitor());
        if (isOTC == null) {
          return false;
        }
        return !isOTC;
      } else {
        return false;
      }
    }

    /* package */ AnalyticsNode getRoot() {
      return _root;
    }
  }
}

@@ -3242,20 +3242,17 @@
 e(root, 
-true
+0
 );%0A     
@@ -3343,20 +3343,17 @@
 de, 
-boolean root
+int level
 ) %7B%0A
@@ -3798,21 +3798,25 @@
 (child, 
-false
+level + 1
 ));%0A    
@@ -3832,103 +3832,52 @@
  // 
-collapse nodes if there are no child nodes (unless it's the root node which is never collaps
+leave root and first-level children expand
 ed
-)
 %0A   
@@ -3902,47 +3902,18 @@
 e = 
-node.getChildNodes().isEmpty() && !root
+level %3E= 2
 ;%0A  

Set default tree state to be expanded only down to second-level nodes