context: ///////////////////////////////////////////////////////////////////////////////////////
  // Copyright (C) 2006-2019 Esper Team. All rights reserved.                           /
  // http://esper.codehaus.org                                                          /
  // ---------------------------------------------------------------------------------- /
  // The software in this package is published under the terms of the GPL license       /
  // a copy of which has been included with this distribution in the license.txt file.  /
  ///////////////////////////////////////////////////////////////////////////////////////
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  
  using com.espertech.esper.common.@internal.bytecodemodel.@base;
  using com.espertech.esper.common.@internal.bytecodemodel.model.expression;
  using com.espertech.esper.common.@internal.context.aifactory.core;
  using com.espertech.esper.common.@internal.epl.expression.core;
  using com.espertech.esper.common.@internal.util;
  using com.espertech.esper.compat;
  using com.espertech.esper.compat.collections;
  
  using static com.espertech.esper.common.@internal.bytecodemodel.model.expression.CodegenExpressionBuilder;
  
  namespace com.espertech.esper.common.@internal.epl.join.querygraph
  {
      /// <summary>
      ///     Property lists stored as a value for each stream-to-stream relationship, for use by
      ///     <seealso cref="QueryGraphForge" />.
      /// </summary>
      public class QueryGraphValueForge
      {
          /// <summary>
          ///     Ctor.
          /// </summary>
          public QueryGraphValueForge()
          {
              Items = new List<QueryGraphValueDescForge>();
          }
  
          public IList<QueryGraphValueDescForge> Items { get; }
  
          public bool IsEmptyNotNavigable {
              get { return Items.IsEmpty(); }
          }
  
          /// <summary>
          ///     Add key and index property.
          /// </summary>
          /// <param name="keyProperty">key property</param>
          /// <param name="indexPropertyIdent">index property</param>
          /// <param name="keyPropNode">key node</param>
          /// <returns>true if added and either property did not exist, false if either already existed</returns>
          public bool AddStrictCompare(
              string keyProperty,
              ExprIdentNode keyPropNode,
              ExprIdentNode indexPropertyIdent)
          {
              var value = FindIdentEntry(indexPropertyIdent);
              if (value != null && value.Entry is QueryGraphValueEntryHashKeyedForgeExpr) {
                  // if this index property exists and is compared to a constant, ignore the index prop
                  var expr = (QueryGraphValueEntryHashKeyedForgeExpr) value.Entry;
                  if (expr.IsConstant) {
                      return false;
                  }
              }
  
              if (value != null && value.Entry is QueryGraphValueEntryHashKeyedForgeProp) {
                  return false; // second comparison, ignore
              }
  
              Items.Add(
                  new QueryGraphValueDescForge(
                      new ExprNode[] {indexPropertyIdent},
                      new QueryGraphValueEntryHashKeyedForgeProp(
                          keyPropNode,
                          keyProperty,
                          keyPropNode.ExprEvaluatorIdent.Getter)));
              return true;
          }
  
          public void AddRange(
              QueryGraphRangeEnum rangeType,
              ExprNode propertyStart,
              ExprNode propertyEnd,
              ExprIdentNode propertyValueIdent)
          {
              if (!rangeType.IsRange()) {
                  throw new ArgumentException("Expected range type, received " + rangeType);
              }
  
              // duplicate can be removed right away
              if (FindIdentEntry(propertyValueIdent) != null) {
                  return;
              }
  
              Items.Add(
                  new QueryGraphValueDescForge(
                      new ExprNode[] {propertyValueIdent},
                      new QueryGraphValueEntryRangeInForge(rangeType, propertyStart, propertyEnd, true)));
          }
  
          public void AddRelOp(
              ExprNode propertyKey,
              QueryGraphRangeEnum op,
              ExprIdentNode propertyValueIdent,
              bool isBetweenOrIn)
          {
              // Note: Read as follows:
              // System.out.println("If I have an index on '" + propertyValue + "' I'm evaluating " + propertyKey + " and finding all values of " + propertyValue + " " + op + " then " + propertyKey);
  
              // Check if there is an opportunity to convert this to a range or remove an earlier specification
              var existing = FindIdentEntry(propertyValueIdent);
              if (existing == null) {
                  Items.Add(
                      new QueryGraphValueDescForge(
                          new ExprNode[] {propertyValueIdent},
                          new QueryGraphValueEntryRangeRelOpForge(op, propertyKey, isBetweenOrIn)));
                  return;
              }
  
              if (!(existing.Entry is QueryGraphValueEntryRangeRelOpForge)) {
                  return; // another comparison exists already, don't add range
              }
  
              var relOp = (QueryGraphValueEntryRangeRelOpForge) existing.Entry;
              var opsDesc = QueryGraphRangeUtil.GetCanConsolidate(op, relOp.Type);
              if (opsDesc != null) {
                  var start = !opsDesc.IsReverse ? relOp.Expression : propertyKey;
                  var end = !opsDesc.IsReverse ? propertyKey : relOp.Expression;
                  Items.Remove(existing);
                  AddRange(opsDesc.Type, start, end, propertyValueIdent);
              }
          }
  
          public void AddUnkeyedExpr(
              ExprIdentNode indexedPropIdent,
              ExprNode exprNodeNoIdent)
          {
              Items.Add(
                  new QueryGraphValueDescForge(
                      new ExprNode[] {indexedPropIdent},
                      new QueryGraphValueEntryHashKeyedForgeExpr(exprNodeNoIdent, false)));
          }
  
          public void AddKeyedExpr(
              ExprIdentNode indexedPropIdent,
              ExprNode exprNodeNoIdent)
          {
              Items.Add(
                  new QueryGraphValueDescForge(
                      new ExprNode[] {indexedPropIdent},
                      new QueryGraphValueEntryHashKeyedForgeExpr(exprNodeNoIdent, true)));
          }
  
          public QueryGraphValuePairHashKeyIndexForge HashKeyProps {
              get {
                  IList<QueryGraphValueEntryHashKeyedForge> keys = new List<QueryGraphValueEntryHashKeyedForge>();
                  Deque<string> indexed = new ArrayDeque<string>();
                  foreach (var desc in Items) {
                      if (desc.Entry is QueryGraphValueEntryHashKeyedForge) {
                          var keyprop = (QueryGraphValueEntryHashKeyedForge) desc.Entry;
                          keys.Add(keyprop);
                          indexed.Add(GetSingleIdentNodeProp(desc.IndexExprs));
                      }
                  }
  
                  var strictKeys = new string[indexed.Count];
                  var count = 0;
                  foreach (var desc in Items) {
                      if (desc.Entry is QueryGraphValueEntryHashKeyedForge) {
                          if (desc.Entry is QueryGraphValueEntryHashKeyedForgeProp) {
                              var keyprop = (QueryGraphValueEntryHashKeyedForgeProp) desc.Entry;
                              strictKeys[count] = keyprop.KeyProperty;
                          }
  
                          count++;
                      }
                  }
  
                  return new QueryGraphValuePairHashKeyIndexForge(indexed.ToArray(), keys, strictKeys);
              }
          }
  
          public QueryGraphValuePairRangeIndexForge RangeProps {
              get {
                  Deque<string> indexed = new ArrayDeque<string>();
                  IList<QueryGraphValueEntryRangeForge> keys = new List<QueryGraphValueEntryRangeForge>();
                  foreach (var desc in Items) {
                      if (desc.Entry is QueryGraphValueEntryRangeForge) {
                          var keyprop = (QueryGraphValueEntryRangeForge) desc.Entry;
                          keys.Add(keyprop);
                          indexed.Add(GetSingleIdentNodeProp(desc.IndexExprs));
                      }
                  }
  
                  return new QueryGraphValuePairRangeIndexForge(indexed.ToArray(), keys);
              }
          }
  
          public override string ToString()
          {
              var writer = new StringWriter();
              writer.Write("QueryGraphValue ");
              var delimiter = "";
  
gt:             foreach (var desc in Items) {