/// <summary>
        /// Performs a <em>shallow</em> copy of properties from
        /// <paramref name="fromNode"/> to <paramref name="toNode"/>.
        /// </summary>
        /// <param name="fromNode">The node to copy from.</param>
        /// <param name="toNode">The node to copy to.</param>
        /// <returns><paramref name="toNode"/>, with its properties updated.</returns>
        /// <remarks>Does <em>not</em> change the parent node of
        /// <paramref name="toNode"/>.</remarks>
        public static ProductPartitionNode CopyProperties(ProductPartitionNode fromNode,
                                                          ProductPartitionNode toNode)
        {
            switch (fromNode.nodeState.NodeType)
            {
            case NodeType.BIDDABLE_UNIT:
                toNode        = toNode.AsBiddableUnit();
                toNode.CpcBid = fromNode.CpcBid;
                break;

            case NodeType.EXCLUDED_UNIT:
                toNode = toNode.AsExcludedUnit();
                break;

            case NodeType.SUBDIVISION:
                toNode = toNode.AsSubdivision();
                break;

            default:
                throw new InvalidOperationException(
                          "Unrecognized node state: " + fromNode.nodeState.NodeType);
            }

            toNode.ProductPartitionId = fromNode.ProductPartitionId;
            return(toNode);
        }
        /// <summary>
        /// Using the criteria in <paramref name="parentIdMap"/>, recursively adds
        /// all children under the partition ID of <paramref name="parentNode"/> to
        /// <paramref name="parentNode"/>.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="parentIdMap">The multimap from parent partition ID to list
        /// of child criteria</param>
        private static void AddChildNodes(ProductPartitionNode parentNode,
                                          Dictionary <long, List <AdGroupCriterion> > parentIdMap)
        {
            List <AdGroupCriterion> childCriteria = null;

            if (parentIdMap.ContainsKey(parentNode.ProductPartitionId))
            {
                childCriteria = parentIdMap[parentNode.ProductPartitionId];
            }

            // no children, return.
            if (childCriteria == null || childCriteria.Count == 0)
            {
                return;
            }

            // Ensure that the parent is a subdivision.
            parentNode.AsSubdivision();

            foreach (AdGroupCriterion childCriterion in childCriteria)
            {
                ProductPartition     partition = (ProductPartition)childCriterion.criterion;
                ProductPartitionNode childNode = parentNode.AddChild(partition.caseValue);
                childNode.ProductPartitionId = partition.id;

                if (childCriterion is BiddableAdGroupCriterion)
                {
                    childNode = childNode.AsBiddableUnit();
                    Money cpcBidAmount = GetBid((BiddableAdGroupCriterion)childCriterion);
                    if (cpcBidAmount != null)
                    {
                        childNode.CpcBid = cpcBidAmount.microAmount;
                    }
                }
                else
                {
                    childNode = childNode.AsExcludedUnit();
                }

                AddChildNodes(childNode, parentIdMap);
            }
        }
        /// <summary>
        /// Returns a new tree based on a non-empty collection of ad group criteria.
        /// </summary>
        /// <param name="adGroupId">The ad group ID.</param>
        /// <param name="parentIdMap">The multimap from parent product partition ID
        /// to child criteria.</param>
        /// <returns>a new product partition tree.</returns>
        private static ProductPartitionTree CreateAdGroupTree(long adGroupId,
                                                              Dictionary <long, List <AdGroupCriterion> > parentIdMap)
        {
            ProductPartitionNode rootNode = null;

            if (parentIdMap.Count == 0)
            {
                rootNode = new ProductPartitionNode(null, null, NEW_ROOT_ID);
            }
            else
            {
                PreconditionUtilities.CheckState(parentIdMap.ContainsKey(ROOT_PARENT_ID),
                                                 string.Format(ShoppingMessages.RootCriteriaNotFoundInCriteriaList, adGroupId));

                PreconditionUtilities.CheckState(parentIdMap[ROOT_PARENT_ID].Count == 1,
                                                 string.Format(ShoppingMessages.MoreThanOneRootFound, adGroupId));

                AdGroupCriterion rootCriterion = parentIdMap[ROOT_PARENT_ID][0];

                PreconditionUtilities.CheckState(rootCriterion is BiddableAdGroupCriterion,
                                                 string.Format(ShoppingMessages.RootCriterionIsNotBiddable, adGroupId));

                BiddableAdGroupCriterion biddableRootCriterion = (BiddableAdGroupCriterion)rootCriterion;

                rootNode = new ProductPartitionNode(null, null, rootCriterion.criterion.id,
                                                    new ProductDimensionEqualityComparer());

                // Set the root's bid if a bid exists on the BiddableAdGroupCriterion.
                Money rootNodeBid = GetBid(biddableRootCriterion);

                if (rootNodeBid != null)
                {
                    rootNode.AsBiddableUnit().CpcBid = rootNodeBid.microAmount;
                }

                AddChildNodes(rootNode, parentIdMap);
            }
            return(new ProductPartitionTree(adGroupId, rootNode));
        }
        /// <summary>
        /// Returns a new tree based on a non-empty collection of ad group criteria.
        /// </summary>
        /// <param name="adGroupId">The ad group ID.</param>
        /// <param name="parentIdMap">The multimap from parent product partition ID
        /// to child criteria.</param>
        /// <returns>a new product partition tree.</returns>
        private static ProductPartitionTree CreateAdGroupTree(long adGroupId,
        Dictionary<long, List<AdGroupCriterion>> parentIdMap)
        {
            ProductPartitionNode rootNode = null;

              if (parentIdMap.Count == 0) {
            rootNode = new ProductPartitionNode(null, null, NEW_ROOT_ID);
              } else {
            PreconditionUtilities.CheckState(parentIdMap.ContainsKey(ROOT_PARENT_ID),
            string.Format(ShoppingMessages.RootCriteriaNotFoundInCriteriaList, adGroupId));

            PreconditionUtilities.CheckState(parentIdMap[ROOT_PARENT_ID].Count == 1,
            string.Format(ShoppingMessages.MoreThanOneRootFound, adGroupId));

            AdGroupCriterion rootCriterion = parentIdMap[ROOT_PARENT_ID][0];

            PreconditionUtilities.CheckState(rootCriterion is BiddableAdGroupCriterion,
            string.Format(ShoppingMessages.RootCriterionIsNotBiddable, adGroupId));

            BiddableAdGroupCriterion biddableRootCriterion = (BiddableAdGroupCriterion) rootCriterion;

            rootNode = new ProductPartitionNode(null, null, rootCriterion.criterion.id,
            new ProductDimensionEqualityComparer());

            // Set the root's bid if a bid exists on the BiddableAdGroupCriterion.
            Money rootNodeBid = GetBid(biddableRootCriterion);

            if (rootNodeBid != null) {
              rootNode.AsBiddableUnit().CpcBid = rootNodeBid.microAmount;
            }

            AddChildNodes(rootNode, parentIdMap);
              }
              return new ProductPartitionTree(adGroupId, rootNode);
        }
    /// <summary>
    /// Performs a <em>shallow</em> copy of properties from
    /// <paramref name="fromNode"/> to <paramref name="toNode"/>.
    /// </summary>
    /// <param name="fromNode">The node to copy from.</param>
    /// <param name="toNode">The node to copy to.</param>
    /// <returns><paramref name="toNode"/>, with its properties updated.</returns>
    /// <remarks>Does <em>not</em> change the parent node of
    /// <paramref name="toNode"/>.</remarks>
    public static ProductPartitionNode CopyProperties(ProductPartitionNode fromNode,
        ProductPartitionNode toNode) {
      switch (fromNode.nodeState.NodeType) {
        case NodeType.BIDDABLE_UNIT:
          toNode = toNode.AsBiddableUnit();
          toNode.CpcBid = fromNode.CpcBid;
          break;

        case NodeType.EXCLUDED_UNIT:
          toNode = toNode.AsExcludedUnit();
          break;

        case NodeType.SUBDIVISION:
          toNode = toNode.AsSubdivision();
          break;

        default:
          throw new InvalidOperationException(
              "Unrecognized node state: " + fromNode.nodeState.NodeType);
      }

      toNode.ProductPartitionId = fromNode.ProductPartitionId;
      return toNode;
    }