text: /******************************************************************************
   *
   * Copyright (c) 2017, the Perspective Authors.
   *
   * This file is part of the Perspective library, distributed under the terms of
   * the Apache License 2.0.  The full license can be found in the LICENSE file.
   *
   */
  
  #include <perspective/first.h>
  #include <perspective/context_common.h>
  #include <perspective/context_two.h>
  #include <perspective/extract_aggregate.h>
  #include <perspective/sparse_tree.h>
  #include <perspective/tree_context_common.h>
  #include <perspective/logtime.h>
  #include <perspective/traversal.h>
  
  namespace perspective
  {
  t_ctx2::t_ctx2()
      : m_row_depth_set(false)
      , m_column_depth_set(false)
      , m_row_depth(0)
      , m_column_depth(0)
  {
  }
  
  t_ctx2::t_ctx2(const t_schema& schema, const t_config& pivot_config)
      : t_ctxbase<t_ctx2>(schema, pivot_config)
      , m_row_depth_set(false)
      , m_column_depth_set(false)
      , m_row_depth(0)
      , m_column_depth(0)
  {
  }
  
  t_ctx2::~t_ctx2() {}
  
  t_uindex
  t_ctx2::get_num_trees() const
  {
      return m_config.get_num_rpivots() + 1;
  }
  
  t_str
  t_ctx2::repr() const
  {
      std::stringstream ss;
      ss << "t_ctx2<" << this << ">";
      return ss.str();
  }
  
  void
  t_ctx2::init()
  {
      m_trees = std::vector<t_stree_sptr>(get_num_trees());
  
      for (t_uindex treeidx = 0, tree_loop_end = m_trees.size();
           treeidx < tree_loop_end; ++treeidx)
      {
          t_pivotvec pivots;
          if (treeidx > 0)
          {
              pivots.insert(pivots.end(), m_config.get_row_pivots().begin(),
                  m_config.get_row_pivots().begin() + treeidx);
          }
  
          pivots.insert(pivots.end(), m_config.get_column_pivots().begin(),
              m_config.get_column_pivots().end());
  
          m_trees[treeidx] = std::make_shared<t_stree>(
              pivots, m_config.get_aggregates(), m_schema, m_config);
  
          m_trees[treeidx]->init();
      }
  
      m_rtraversal
          = std::make_shared<t_traversal>(rtree(), m_config.handle_nan_sort());
  
      m_ctraversal
          = std::make_shared<t_traversal>(ctree(), m_config.handle_nan_sort());
      m_minmax = t_minmaxvec(m_config.get_num_aggregates());
      m_init = true;
  }
  
  void
  t_ctx2::step_begin()
  {
      reset_step_state();
  }
  
  void
  t_ctx2::step_end()
  {
      m_minmax = m_trees.back()->get_min_max();
      if (m_row_depth_set)
      {
          set_depth(HEADER_ROW, m_row_depth);
      }
      if (m_column_depth_set)
      {
          set_depth(HEADER_COLUMN, m_column_depth);
      }
  }
  
  t_index
  t_ctx2::get_row_count() const
  {
      return m_rtraversal->size();
  }
  
  t_index
  t_ctx2::get_column_count() const
  {
      return get_num_view_columns();
  }
  
  t_index
  t_ctx2::open(t_header header, t_tvidx idx)
  {
      t_index retval;
  
      if (header == HEADER_ROW)
      {
          if (!m_rtraversal->is_valid_idx(idx))
              return 0;
          m_row_depth_set = false;
          m_row_depth = 0;
          if (m_row_sortby.empty())
          {
              retval = m_rtraversal->expand_node(idx);
          }
          else
          {
              retval = m_rtraversal->expand_node(m_row_sortby, idx);
          }
          m_rows_changed = (retval > 0);
      }
      else
      {
          if (!m_ctraversal->is_valid_idx(idx))
              return 0;
          retval = m_ctraversal->expand_node(idx);
          m_column_depth_set = false;
          m_column_depth = 0;
          m_columns_changed = (retval > 0);
      }
  
      return retval;
  }
  
  t_index
  t_ctx2::close(t_header header, t_tvidx idx)
  {
      t_index retval;
  
      switch (header)
      {
          case HEADER_ROW:
          {
              if (!m_rtraversal->is_valid_idx(idx))
                  return 0;
              m_row_depth_set = false;
              m_row_depth = 0;
              retval = m_rtraversal->collapse_node(idx);
              m_rows_changed = (retval > 0);
          }
          case HEADER_COLUMN:
          {
              if (!m_ctraversal->is_valid_idx(idx))
                  return 0;
              m_column_depth_set = false;
              m_column_depth = 0;
              retval = m_ctraversal->collapse_node(idx);
              m_columns_changed = (retval > 0);
          }
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Invalid header type detected.");
              return INVALID_INDEX;
          }
          break;
      }
      return retval;
  }
  
  t_totals
  t_ctx2::get_totals() const
  {
      return m_config.get_totals();
  }
  
  t_tvivec
  t_ctx2::get_ctraversal_indices() const
  {
      switch (m_config.get_totals())
      {
          case TOTALS_BEFORE:
          {
              t_index nelems = m_ctraversal->size();
              PSP_VERBOSE_ASSERT(nelems > 0, "nelems is <= 0");
              t_tvivec rval(nelems);
              for (t_index cidx = 0; cidx < nelems; ++cidx)
              {
                  rval[cidx] = cidx;
              }
              return rval;
          }
          break;
          case TOTALS_AFTER:
          {
              t_tvivec col_order;
              m_ctraversal->post_order(0, col_order);
              return col_order;
          }
          break;
          case TOTALS_HIDDEN:
          {
              t_tvivec leaves;
              m_ctraversal->get_leaves(leaves);
              t_tvivec rval(leaves.size() + 1);
              rval[0] = 0;
              for (t_uindex idx = 1, loop_end = rval.size(); idx < loop_end;
                   ++idx)
              {
                  rval[idx] = leaves[idx - 1];
              }
              return rval;
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Unknown total type");
          }
      }
      return t_tvivec();
  }
  
  t_tscalvec
  t_ctx2::get_data(t_tvidx start_row, t_tvidx end_row, t_tvidx start_col,
      t_tvidx end_col) const
  {
      auto ext = sanitize_get_data_extents(
          *this, start_row, end_row, start_col, end_col);
  
      t_uidxpvec cells;
      for (t_index ridx = ext.m_srow; ridx < ext.m_erow; ++ridx)
      {
          for (t_index cidx = ext.m_scol; cidx < ext.m_ecol; ++cidx)
          {
              cells.push_back(t_idxpair(ridx, cidx));
          }
      }
  
      auto cells_info = resolve_cells(cells);
  
      t_index nrows = ext.m_erow - ext.m_srow;
      t_index stride = ext.m_ecol - ext.m_scol;
      t_tscalvec retval(nrows * stride);
  
      t_tscalar empty = mknone();
  
      typedef std::pair<t_uindex, t_uindex> t_aggpair;
      std::map<t_aggpair, const t_column*> aggmap;
  
      for (t_uindex treeidx = 0, tree_loop_end = m_trees.size();
           treeidx < tree_loop_end; ++treeidx)
      {
          auto aggtable = m_trees[treeidx]->get_aggtable();
          t_schema aggschema = aggtable->get_schema();
  
          for (t_uindex aggidx = 0, agg_loop_end = m_config.get_num_aggregates();
               aggidx < agg_loop_end; ++aggidx)
          {
              const t_str& aggname = aggschema.m_columns[aggidx];
  
              aggmap[t_aggpair(treeidx, aggidx)]
                  = aggtable->get_const_column(aggname).get();
          }
      }
  
      const t_aggspecvec& aggspecs = m_config.get_aggregates();
  
      for (t_index ridx = ext.m_srow; ridx < ext.m_erow; ++ridx)
      {
          if (ext.m_scol == 0)
          {
              retval[(ridx - ext.m_srow) * stride].set(
                  rtree()->get_value(m_rtraversal->get_tree_index(ridx)));
          }
  
          for (t_index cidx = std::max(ext.m_scol, t_tvidx(1)); cidx < ext.m_ecol;
               ++cidx)
          {
              t_index insert_idx
                  = (ridx - ext.m_srow) * stride + (cidx - ext.m_scol);
              const t_cellinfo& cinfo = cells_info[insert_idx];
  
              if (cinfo.m_idx < 0)
              {
                  retval[insert_idx].set(empty);
              }
              else
              {
                  auto aggcol
                      = aggmap[t_aggpair(cinfo.m_treenum, cinfo.m_agg_index)];
  
                  t_ptidx p_idx
                      = m_trees[cinfo.m_treenum]->get_parent_idx(cinfo.m_idx);
  
                  t_uindex agg_ridx
                      = m_trees[cinfo.m_treenum]->get_aggidx(cinfo.m_idx);
  
                  t_uindex agg_pridx = p_idx == INVALID_INDEX
                      ? INVALID_INDEX
                      : m_trees[cinfo.m_treenum]->get_aggidx(p_idx);
  
                  auto value = extract_aggregate(
                      aggspecs[cinfo.m_agg_index], aggcol, agg_ridx, agg_pridx);
  
                  if (!value.is_valid())
                      value.set(empty);
  
                  retval[insert_idx].set(value);
              }
          }
      }
  
      return retval;
  }
  void
  t_ctx2::sort_by(const t_sortsvec& sortby)
  {
      PSP_TRACE_SENTINEL();
      PSP_VERBOSE_ASSERT(m_init, "touching uninited object");
      m_sortby = sortby;
      if (m_sortby.empty())
      {
          return;
      }
      m_rtraversal->sort_by(m_config, sortby, *(rtree().get()), this);
  }
  
  void
  t_ctx2::reset_sortby()
  {
      PSP_TRACE_SENTINEL();
      PSP_VERBOSE_ASSERT(m_init, "touching uninited object");
      m_sortby = t_sortsvec();
  }
  
  void
  t_ctx2::notify(const t_table& flattened, const t_table& delta,
      const t_table& prev, const t_table& current, const t_table& transitions,
      const t_table& existed)
  {
      psp_log_time(repr() + " notify.enter");
      for (t_uindex tree_idx = 0, loop_end = m_trees.size(); tree_idx < loop_end;
           ++tree_idx)
      {
          if (is_rtree_idx(tree_idx))
          {
              notify_sparse_tree(rtree(), m_rtraversal, true,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  m_row_sortby, flattened, delta, prev, current, transitions,
                  existed, m_config, *m_state);
          }
          else if (is_ctree_idx(tree_idx))
          {
              notify_sparse_tree(ctree(), m_ctraversal, true,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  m_column_sortby, flattened, delta, prev, current, transitions,
                  existed, m_config, *m_state);
          }
          else
          {
              notify_sparse_tree(m_trees[tree_idx], t_trav_sptr(0), false,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  t_sortsvec(), flattened, delta, prev, current, transitions,
                  existed, m_config, *m_state);
          }
      }
  
      if (!m_sortby.empty())
      {
          sort_by(m_sortby);
      }
      psp_log_time(repr() + " notify.exit");
  }
  
  t_uindex
  t_ctx2::calc_translated_colidx(t_uindex n_aggs, t_uindex cidx) const
  {
      switch (m_config.get_totals())
      {
          case TOTALS_BEFORE:
          {
              return (cidx - 1) / n_aggs;
          }
          break;
          case TOTALS_AFTER:
          {
              return (cidx - 1) / n_aggs;
          }
          break;
          case TOTALS_HIDDEN:
          {
              return (cidx - 1) / n_aggs + 1;
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Unknown totals type encountered.");
              return -1;
          }
      }
  }
  
  t_cinfovec
  t_ctx2::resolve_cells(const t_uidxpvec& cells) const
  {
      t_cinfovec rval(cells.size());
  
      t_index n_aggs = m_config.get_num_aggregates();
      t_tvivec c_tvindices = get_ctraversal_indices();
  
      std::vector<t_tscalvec> col_paths(m_ctraversal->size());
  
      for (t_index cidx = 0, loop_end = c_tvindices.size(); cidx < loop_end;
           ++cidx)
      {
          auto translated = c_tvindices[cidx];
          const t_tvnode& c_tvnode = m_ctraversal->get_node(translated);
          col_paths[cidx].reserve(m_config.get_num_cpivots());
          col_paths[cidx] = get_column_path(c_tvnode);
      }
  
      t_uindex ncols = get_num_view_columns();
  
      for (t_index idx = 0, loop_end = cells.size(); idx < loop_end; ++idx)
      {
          const auto& cell = cells[idx];
  
          if (cell.first >= m_rtraversal->size() || cell.second == 0
              || cell.second >= ncols)
          {
              rval[idx].m_idx = INVALID_INDEX;
              continue;
          }
  
          const t_tvnode& r_tvnode = m_rtraversal->get_node(cell.first);
  
          t_ptidx r_ptidx = r_tvnode.m_tnid;
          t_depth r_depth = r_tvnode.m_depth;
          t_tscalvec r_path = get_row_path(r_tvnode);
          t_index agg_idx = (cell.second - 1) % n_aggs;
          t_uindex translated_cidx = calc_translated_colidx(n_aggs, cell.second);
          if (translated_cidx >= c_tvindices.size())
          {
              rval[idx].m_idx = INVALID_INDEX;
              continue;
          }
  
          t_tvidx c_tvidx = c_tvindices[translated_cidx];
  
          rval[idx].m_ridx = cell.first;
          rval[idx].m_cidx = cell.second;
  
          if (c_tvidx >= t_tvidx(m_ctraversal->size()))
          {
              rval[idx].m_idx = INVALID_INDEX;
              continue;
          }
  
          const t_tvnode& c_tvnode = m_ctraversal->get_node(c_tvidx);
          t_ptidx c_ptidx = c_tvnode.m_tnid;
          const t_tscalvec& c_path = col_paths[translated_cidx];
  
          rval[idx].m_agg_index = agg_idx;
  
          if (cell.first == 0)
          {
              rval[idx].m_idx = c_ptidx;
              rval[idx].m_treenum = 0;
          }
          else if (c_path.size() == 0)
          {
              rval[idx].m_idx = r_ptidx;
              rval[idx].m_treenum = m_trees.size() - 1;
          }
          else
          {
              t_index tree_idx = r_depth;
              rval[idx].m_treenum = tree_idx;
  
              if (r_depth + 1 == static_cast<t_depth>(m_trees.size()))
              {
                  rval[idx].m_idx
                      = m_trees[tree_idx]->resolve_path(r_ptidx, c_path);
              }
              else
              {
                  t_ptidx path_ptidx = m_trees[tree_idx]->resolve_path(0, r_path);
                  if (path_ptidx < 0)
                  {
                      rval[idx].m_idx = INVALID_INDEX;
                  }
                  else
                  {
                      rval[idx].m_idx
                          = m_trees[tree_idx]->resolve_path(path_ptidx, c_path);
                  }
              }
          }
      }
  
      return rval;
  }
  
  t_index
  t_ctx2::sidedness() const
  {
      return 2;
  }
  
  t_uindex
  t_ctx2::is_rtree_idx(t_uindex idx) const
  {
      return idx == m_trees.size() - 1;
  }
  
  t_uindex
  t_ctx2::is_ctree_idx(t_uindex idx) const
  {
      return idx == 0;
  }
  
  t_stree_sptr
  t_ctx2::rtree()
  {
      return m_trees.back();
  }
  
  t_stree_csptr
  t_ctx2::rtree() const
  {
      return m_trees.back();
  }
  
  t_stree_sptr
  t_ctx2::ctree()
  {
      return m_trees.front();
  }
  
  t_stree_csptr
  t_ctx2::ctree() const
  {
      return m_trees.front();
  }
  
  t_uindex
  t_ctx2::get_num_view_columns() const
  {
      switch (m_config.get_totals())
      {
          case TOTALS_AFTER:
          case TOTALS_BEFORE:
          {
              return m_ctraversal->size() * m_config.get_num_aggregates() + 1;
          }
          break;
          case TOTALS_HIDDEN:
          {
              t_index nitems
                  = (m_ctraversal->size() - 1) * m_config.get_num_aggregates();
              return nitems + 1;
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Unknown totals type");
              return -1;
          }
      }
      PSP_COMPLAIN_AND_ABORT("Not implemented");
      return 0;
  }
  
  t_tscalvec
  t_ctx2::get_row_path(t_tvidx idx) const
  {
      if (idx < 0)
          return t_tscalvec();
      return ctx_get_path(rtree(), m_rtraversal, idx);
  }
  
  t_tscalvec
  t_ctx2::get_row_path(const t_tvnode& node) const
  {
      t_tscalvec rval;
      m_trees.back()->get_path(node.m_tnid, rval);
      return rval;
  }
  
  t_tscalvec
  t_ctx2::get_column_path(t_tvidx idx) const
  {
      if (idx < 0)
          return t_tscalvec();
      return ctx_get_path(ctree(), m_ctraversal, idx);
  }
  
  t_tscalvec
  t_ctx2::get_column_path(const t_tvnode& node) const
  {
      t_tscalvec rval;
      m_trees[0]->get_path(node.m_tnid, rval);
      return rval;
  }
  
  t_tscalvec
  t_ctx2::get_column_path_userspace(t_tvidx idx) const
  {
      t_tvidx translated_idx = translate_column_index(idx);
      if (translated_idx == INVALID_INDEX)
      {
          return t_tscalvec();
      }
      return get_column_path(translated_idx);
  }
  
  t_tvidx
  t_ctx2::translate_column_index(t_tvidx idx) const
  {
      t_tvidx rval = INVALID_INDEX;
  
      switch (m_config.get_totals())
      {
          case TOTALS_BEFORE:
          {
              rval = (idx - 1) / m_config.get_num_aggregates();
          }
          break;
          case TOTALS_AFTER:
          {
              t_tvivec col_order;
              m_ctraversal->post_order(0, col_order);
              rval = col_order[(idx - 1) / m_config.get_num_aggregates()];
          }
          break;
          case TOTALS_HIDDEN:
          {
              t_tvivec leaves;
              m_ctraversal->get_leaves(leaves);
              rval = leaves[(idx - 1) / m_config.get_num_aggregates()];
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Unknown totals type encountered.");
          }
      }
  
      return rval;
  }
  
  t_aggspecvec
  t_ctx2::get_aggregates() const
  {
      return m_config.get_aggregates();
  }
  
  void
  t_ctx2::set_depth(t_header header, t_depth depth)
  {
      t_depth new_depth;
  
      switch (header)
      {
          case HEADER_ROW:
          {
              if (m_config.get_num_rpivots() == 0)
                  return;
              new_depth
                  = std::min<t_depth>(m_config.get_num_rpivots() - 1, depth);
              m_rtraversal->set_depth(m_row_sortby, new_depth);
              m_row_depth = new_depth;
              m_row_depth_set = true;
          }
          break;
          case HEADER_COLUMN:
          {
              if (m_config.get_num_cpivots() == 0)
                  return;
              new_depth
                  = std::min<t_depth>(m_config.get_num_cpivots() - 1, depth);
              m_ctraversal->set_depth(m_column_sortby, new_depth);
              m_column_depth = new_depth;
              m_column_depth_set = true;
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Invalid header");
          }
          break;
      }
  }
  
  t_depth
  t_ctx2::get_depth(t_header header) const
  {
      switch (header)
      {
          case HEADER_ROW:
          {
              return m_row_depth;
          }
          break;
          case HEADER_COLUMN:
          {
              return m_column_depth;
          }
          break;
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Invalid header");
          }
          break;
      }
  }
  
  t_tscalvec
  t_ctx2::get_pkeys(const t_uidxpvec& cells) const
  {
      t_tscalset all_pkeys;
  
      auto tree_info = resolve_cells(cells);
      for (t_index idx = 0, loop_end = tree_info.size(); idx < loop_end; ++idx)
      {
  
          const auto& cinfo = tree_info[idx];
          if (cinfo.m_idx != INVALID_INDEX)
          {
              auto node_pkeys = m_trees[cinfo.m_treenum]->get_pkeys(cinfo.m_idx);
              std::copy(node_pkeys.begin(), node_pkeys.end(),
                  std::inserter(all_pkeys, all_pkeys.end()));
          }
      }
  
      t_tscalvec rval(all_pkeys.size());
      std::copy(all_pkeys.begin(), all_pkeys.end(), rval.begin());
      return rval;
  }
  
  t_tscalvec
  t_ctx2::get_cell_data(const t_uidxpvec& cells) const
  {
      t_tscalvec rval(cells.size());
      t_tscalar empty;
      empty.set(t_int64(0));
  
      auto tree_info = resolve_cells(cells);
  
      for (t_index idx = 0, loop_end = tree_info.size(); idx < loop_end; ++idx)
      {
          const auto& cinfo = tree_info[idx];
          if (cinfo.m_idx == INVALID_INDEX)
          {
              rval[idx].set(empty);
          }
          else
          {
              rval[idx].set(m_trees[cinfo.m_treenum]->get_aggregate(
                  cinfo.m_idx, cinfo.m_agg_index));
          }
      }
      return rval;
  }
  
  t_stepdelta
  t_ctx2::get_step_delta(t_tvidx bidx, t_tvidx eidx)
  {
  
      t_uindex start_row = bidx;
      t_uindex end_row = eidx;
      t_uindex start_col = 1;
      t_uindex end_col = get_num_view_columns();
      t_stepdelta rval;
      rval.rows_changed = true;
      rval.columns_changed = true;
      t_cellupdvec& updvec = rval.cells;
  
      auto ext = sanitize_get_data_extents(
          *this, start_row, end_row, start_col, end_col);
  
      t_uidxpvec cells;
  
      for (t_index ridx = ext.m_srow; ridx < ext.m_erow; ++ridx)
      {
          for (t_uindex cidx = 1; cidx < end_col; ++cidx)
          {
              cells.push_back(t_idxpair(ridx, cidx));
          }
      }
  
      auto cells_info = resolve_cells(cells);
  
      for (const auto& c : cells_info)
      {
          if (c.m_idx < 0)
              continue;
  
          const auto& deltas = m_trees[c.m_treenum]->get_deltas();
  
          auto iterators = deltas->get<by_tc_nidx_aggidx>().equal_range(c.m_idx);
  
          for (auto iter = iterators.first; iter != iterators.second; ++iter)
          {
              updvec.push_back(t_cellupd(
                  c.m_ridx, c.m_cidx, iter->m_old_value, iter->m_new_value));
          }
      }
  
      clear_deltas();
      return rval;
  }
  
  t_minmaxvec
  t_ctx2::get_min_max() const
  {
      return m_minmax;
  }
  
  void
  t_ctx2::reset()
  {
      for (t_uindex treeidx = 0, tree_loop_end = m_trees.size();
           treeidx < tree_loop_end; ++treeidx)
      {
          t_pivotvec pivots;
          if (treeidx > 0)
          {
              pivots.insert(pivots.end(), m_config.get_row_pivots().begin(),
                  m_config.get_row_pivots().begin() + treeidx);
          }
  
          pivots.insert(pivots.end(), m_config.get_column_pivots().begin(),
              m_config.get_column_pivots().end());
  
          m_trees[treeidx] = std::make_shared<t_stree>(
              pivots, m_config.get_aggregates(), m_schema, m_config);
          m_trees[treeidx]->init();
          m_trees[treeidx]->set_deltas_enabled(get_feature_state(CTX_FEAT_DELTA));
      }
  
      m_rtraversal
          = std::make_shared<t_traversal>(rtree(), m_config.handle_nan_sort());
      m_ctraversal
          = std::make_shared<t_traversal>(ctree(), m_config.handle_nan_sort());
  }
  
  void
  t_ctx2::reset_step_state()
  {
      m_rows_changed = false;
      m_columns_changed = false;
  }
  
  void
  t_ctx2::clear_deltas()
  {
      for (auto& tr : m_trees)
      {
          tr->clear_deltas();
      }
  }
  
  void
  t_ctx2::set_feature_state(t_ctx_feature feature, t_bool state)
  {
      m_features[feature] = state;
  }
  
  void
  t_ctx2::set_alerts_enabled(bool enabled_state)
  {
      m_features[CTX_FEAT_ALERT] = enabled_state;
      for (auto& tr : m_trees)
      {
          tr->set_alerts_enabled(enabled_state);
      }
  }
  
  void
  t_ctx2::set_deltas_enabled(bool enabled_state)
  {
      m_features[CTX_FEAT_DELTA] = enabled_state;
      for (auto& tr : m_trees)
      {
          tr->set_deltas_enabled(enabled_state);
      }
  }
  
  void
  t_ctx2::set_minmax_enabled(bool enabled_state)
  {
      m_features[CTX_FEAT_MINMAX] = enabled_state;
      for (auto& tr : m_trees)
      {
          tr->set_minmax_enabled(enabled_state);
      }
  }
  
  t_streeptr_vec
  t_ctx2::get_trees()
  {
      t_streeptr_vec rval(m_trees.size());
      t_uindex count = 0;
      for (auto& t : m_trees)
      {
          rval[count] = t.get();
          ++count;
      }
      return rval;
  }
  
  t_uindex
  t_ctx2::get_leaf_count(t_header header) const
  {
      switch (header)
      {
          case HEADER_ROW:
              return m_trees.back()->get_num_leaves(m_row_depth + 1);
          case HEADER_COLUMN:
              return m_trees.front()->get_num_leaves(m_column_depth + 1);
          default:
          {
              PSP_COMPLAIN_AND_ABORT("Bad header passed in");
              return 0;
          }
      }
  }
  
  t_tscalvec
  t_ctx2::get_leaf_data(t_uindex start_row, t_uindex end_row, t_uindex start_col,
      t_uindex end_col) const
  {
      t_depth row_depth = m_row_depth + 1;
      t_depth column_depth = m_column_depth + 1;
      t_index nrows = end_row - start_row;
      t_index stride = end_col - start_col;
      t_tscalvec retval((nrows + 1) * stride);
      t_index n_aggs = m_config.get_num_aggregates();
      t_tscalvec r_path;
      r_path.reserve(m_config.get_num_rpivots() + m_config.get_num_cpivots() + 1);
      t_tscalar empty = t_tscalar::canonical(DTYPE_NONE);
      t_index ridx = 0;
      t_depth minus_one = -1;
      t_depth last_depth = -1;
      auto tree = m_trees[row_depth];
      std::vector<t_tscalvec> col_paths(end_col);
      static const char unit_sep = 0x1F;
      t_idxvec cidxvec = m_trees[0]->get_indices_for_depth(column_depth);
      for (t_uindex cidx = 0; cidx < end_col - row_depth; ++cidx)
      {
          t_index translated_idx = cidx / n_aggs;
          t_ptidx c_ptidx = cidxvec[translated_idx];
          col_paths[cidx].reserve(m_config.get_num_cpivots());
          m_trees[0]->get_path(c_ptidx, col_paths[cidx]);
          std::stringstream label;
          t_tscalvec& plabels = col_paths[cidx];
          if (!plabels.empty())
          {
              auto last = plabels.rend() - 1;
              for (auto lit = plabels.rbegin(); lit != last; ++lit)
              {
                  label << lit->to_string() << unit_sep;
              }
              label << last->to_string();
          }
          retval[cidx + row_depth].set(get_interned_tscalar(label.str().c_str()));
      }
      // Iterate by depth
      std::deque<t_uindex> dft;
      dft.push_front(0);
      std::vector<t_tscalar> pheader;
      while (!dft.empty())
      {
          auto nidx = dft.front();
          dft.pop_front();
          t_stnode node = tree->get_node(nidx);
          if (node.m_depth < row_depth)
          {
              if (node.m_depth < last_depth && last_depth != minus_one)
              {
                  for (t_uindex i = 0; i < last_depth - node.m_depth; ++i)
                  {
                      pheader.pop_back();
                  }
              }
              if (node.m_depth != 0)
              {
                  pheader.push_back(node.m_value);
              }
              t_uidxvec nodes = tree->get_child_idx(nidx);
              std::copy(nodes.rbegin(), nodes.rend(), std::front_inserter(dft));
          }
          else if (node.m_depth == row_depth)
          {
              ridx++;
              t_uindex r_start = (ridx - start_row) * stride;
              for (t_uindex hidx = 0; hidx < row_depth; ++hidx)
              {
                  retval[r_start + hidx].set(pheader[hidx]);
              }
              retval[r_start + row_depth - 1].set(node.m_value);
              t_ptidx r_ptidx = node.m_idx;
              tree->get_path(r_ptidx, r_path);
              t_depth r_depth = node.m_depth;
              for (t_uindex cidx = 0; cidx < end_col - row_depth; ++cidx)
              {
                  t_index insert_idx
                      = (ridx - start_row) * stride + row_depth + cidx;
                  const t_tscalvec& c_path = col_paths[cidx];
                  t_index agg_idx = cidx % n_aggs;
                  t_ptidx query_ptidx = INVALID_INDEX;
                  if (c_path.size() == 0)
                  {
                      query_ptidx = r_ptidx;
                  }
                  else
                  {
                      if (r_depth + 1 == static_cast<t_depth>(m_trees.size()))
                      {
                          query_ptidx = tree->resolve_path(r_ptidx, c_path);
                      }
                      else
                      {
                          t_ptidx path_ptidx = tree->resolve_path(0, r_path);
                          if (path_ptidx < 0)
                          {
                              query_ptidx = INVALID_INDEX;
                          }
                          else
                          {
                              query_ptidx
                                  = tree->resolve_path(path_ptidx, c_path);
                          }
                      }
                  }
                  if (query_ptidx < 0)
                  {
                      retval[insert_idx].set(empty);
                  }
                  else
                  {
                      retval[insert_idx].set(
                          tree->get_aggregate(query_ptidx, agg_idx));
                  }
              }
              r_path.clear();
          }
          last_depth = node.m_depth;
      }
      return retval;
  }
  
  t_bool
  t_ctx2::has_deltas() const
  {
      t_bool has_deltas = false;
      for (t_uindex idx = 0, loop_end = m_trees.size(); idx < loop_end; ++idx)
      {
          has_deltas = has_deltas || m_trees[idx]->has_deltas();
      }
      return has_deltas;
  }
  
  void
  t_ctx2::notify(const t_table& flattened)
  {
      for (t_uindex tree_idx = 0, loop_end = m_trees.size(); tree_idx < loop_end;
           ++tree_idx)
      {
          if (is_rtree_idx(tree_idx))
          {
              notify_sparse_tree(rtree(), m_rtraversal, true,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  m_row_sortby, flattened, m_config, *m_state);
          }
          else if (is_ctree_idx(tree_idx))
          {
              notify_sparse_tree(ctree(), m_ctraversal, true,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  m_column_sortby, flattened, m_config, *m_state);
          }
          else
          {
              notify_sparse_tree(m_trees[tree_idx], t_trav_sptr(0), false,
                  m_config.get_aggregates(), m_config.get_sortby_pairs(),
                  t_sortsvec(), flattened, m_config, *m_state);
          }
      }
  }
  
  void
  t_ctx2::pprint() const
  {
  }
  
  t_dtype
  t_ctx2::get_column_dtype(t_uindex idx) const
  {
      t_uindex naggs = m_config.get_num_aggregates();
  
      if (idx == 0)
          return DTYPE_NONE;
  
      return rtree()
          ->get_aggtable()
          ->get_const_column((idx - 1) % naggs)
          ->get_dtype();
  }
  
  t_tscalvec
  t_ctx2::unity_get_row_data(t_uindex idx) const
  {
      auto rval = get_data(idx, idx + 1, 0, get_column_count());
      if (rval.empty())
          return t_tscalvec();
  
      return t_tscalvec(rval.begin() + 1, rval.end());
  }
  
  t_tscalvec
  t_ctx2::unity_get_column_data(t_uindex idx) const
  {
      PSP_COMPLAIN_AND_ABORT("Not implemented");
      return t_tscalvec();
  }
  
  t_tscalvec
  t_ctx2::unity_get_row_path(t_uindex idx) const
  {
      return get_row_path(idx);
  }
  
  t_tscalvec
  t_ctx2::unity_get_column_path(t_uindex idx) const
  {
      auto rv = get_column_path_userspace(idx);
      return rv;
  }
  
  t_uindex
  t_ctx2::unity_get_row_depth(t_uindex idx) const
  {
      return get_row_path(idx).size();
  }
  
  t_uindex
  t_ctx2::unity_get_column_depth(t_uindex idx) const
  {
      return get_column_path(idx).size();
  }
  
  t_str
  t_ctx2::unity_get_column_name(t_uindex idx) const
  {
      return m_config.unity_get_column_name(idx);
  }
  
  t_str
  t_ctx2::unity_get_column_display_name(t_uindex idx) const
  {
      return m_config.unity_get_column_display_name(idx);
  }
  
  t_svec
  t_ctx2::unity_get_column_names() const
  {
      t_svec rv;
  
      for (t_uindex idx = 0, loop_end = unity_get_column_count(); idx < loop_end;
           ++idx)
      {
          rv.push_back(unity_get_column_name(idx));
      }
      return rv;
  }
  
  t_svec
  t_ctx2::unity_get_column_display_names() const
  {
      t_svec rv;
  
      for (t_uindex idx = 0, loop_end = unity_get_column_count(); idx < loop_end;
           ++idx)
      {
          rv.push_back(unity_get_column_display_name(idx));
      }
      return rv;
  }
  
  t_uindex
  t_ctx2::unity_get_column_count() const
  {
      if (m_config.get_totals() != TOTALS_HIDDEN)
          return get_column_count() - 1;
      t_tvivec leaves;
      m_ctraversal->get_leaves(leaves);
      return leaves.size() * m_config.get_num_aggregates();
  }
  
  t_uindex
  t_ctx2::unity_get_row_count() const
  {
      return get_row_count();
  }
  
  t_bool
  t_ctx2::unity_get_row_expanded(t_uindex idx) const
  {
      return m_rtraversal->get_node_expanded(idx);
  }
  
  t_bool
  t_ctx2::unity_get_column_expanded(t_uindex idx) const
  {
  
      return m_ctraversal->get_node_expanded(
          calc_translated_colidx(idx, m_config.get_num_aggregates()));
  }
  
  void
  t_ctx2::unity_init_load_step_end()
  {
  }
  
  } // end namespace perspective
  