public BrainBurden(int index, Set2D[] brainSets, Item2D[] io)
                {
                    this.Index = index;
                    this.Brain = brainSets[index];
                    this.Size = this.Brain.Items.Sum(o => o.Size);

                    _io = io;
                }
            private static bool IOLinks_PostAdjust_MoveNext(BrainBurden[] ioLinks, Tuple<int, double>[][] brainLinks, Set2D[] brainSets, Item2D[] io)
            {
                List<Tuple<BrainBurden, int, double, double>> candidates = new List<Tuple<BrainBurden, int, double, double>>();

                // Find brains that have excess links to give away, and which ones to hand to
                foreach (BrainBurden ioLink in ioLinks.Where(o => o.LinksOrig.Count > 0))
                {
                    foreach (Tuple<int, double> brainLink in brainLinks[ioLink.Index])
                    {
                        double otherBurden = ioLinks.First(o => o.Index == brainLink.Item1).Burden;

                        if (ioLink.Burden > otherBurden + brainLink.Item2)
                        {
                            candidates.Add(Tuple.Create(ioLink, brainLink.Item1, ioLink.Burden - (otherBurden + brainLink.Item2), brainLink.Item2));
                        }
                    }
                }

                // Iterate over them with the largest distance first (can't just pull the top, because an individual io link may represent too big of a burden
                // to be able to shift)
                foreach (var candidate in candidates.OrderByDescending(o => o.Item3))
                {
                    // Try to move a link
                    if (IOLinks_PostAdjust_MoveNext_Brain(candidate.Item1, ioLinks.First(o => o.Index == candidate.Item2), candidate.Item4, brainSets, io))
                    {
                        return true;
                    }
                }

                return false;
            }
            private static bool IOLinks_PostAdjust_MoveNext_Brain(BrainBurden from, BrainBurden to, double linkBurden, Set2D[] brainSets, Item2D[] io)
            {
                // Cache the sizes of the current io links
                double fromIOSize = UtilityCore.Iterate(from.LinksOrig, from.LinksMoved).Sum(o => io[o].Size);
                double toIOSize = UtilityCore.Iterate(to.LinksOrig, to.LinksMoved).Sum(o => io[o].Size);

                // See what the new burdens would be for from and to for each io link
                var newBurdens = from.LinksOrig.Select(o => new
                    {
                        IOIndex = o,
                        FromBurden = BrainBurden.CalculateBurden(fromIOSize - io[o].Size, from.BrainSize),
                        ToBurden = BrainBurden.CalculateBurden(toIOSize + io[o].Size, to.BrainSize),
                    }).
                    Where(o => o.FromBurden >= linkBurden + o.ToBurden).        // throw out any moves that cause a reverse in burden
                    Select(o => new
                    {
                        IOIndex = o.IOIndex,
                        FromBurden = o.FromBurden,
                        ToBurden = o.ToBurden,
                        Distance = (brainSets[to.Index].Center - io[o.IOIndex].Position).Length,
                        //TODO: Other scoring criterea here
                    }).
                    ToArray();

                if (newBurdens.Length == 0)
                {
                    return false;
                }

                // Come up with the best io to move
                //TODO: Things to consider
                //      Distance between io and to brain
                //      Types of IO that from and to have (try to specialize)
                //
                // When combining scores, can't just score things linearly, then add up the score.  Average performers of multiple categories
                // would win
                var best = newBurdens.OrderBy(o => o.Distance).First();

                // Move the link
                to.LinksMoved.Add(best.IOIndex);
                from.LinksOrig.Remove(best.IOIndex);

                return true;
            }
            private static Tuple<int, int>[] IOLinks_PostAdjust(Tuple<int, int>[] initial, Set2D[] brainSets, Item2D[] io)
            {
                // Get the links and distances between brain sets
                Tuple<int, double>[][] brainLinks = IOLinks_PostAdjust_BrainBrainLinks(brainSets);

                // Turn the initial links into something more usable (list of links by brain)
                BrainBurden[] ioLinks = initial.
                    GroupBy(o => o.Item1).
                    Select(o => new BrainBurden(o.Key, o.Select(p => p.Item2))).
                    ToArray();

                // Make sure there is an element in ioLinks for each brainset (the groupby logic above only grabs brainsets that have links)
                int[] missing = Enumerable.Range(0, brainLinks.Length).Where(o => !ioLinks.Any(p => p.Index == o)).ToArray();

                ioLinks = UtilityCore.Iterate(ioLinks, missing.Select(o => new BrainBurden(o, Enumerable.Empty<int>()))).ToArray();

                // Move one link at a time
                while (true)
                {
                    // Figure out how burdened each brain set is
                    foreach (BrainBurden brain in ioLinks)
                    {
                        brain.Recalc(brainSets, io);
                    }

                    // Find the best link to move
                    if (!IOLinks_PostAdjust_MoveNext(ioLinks, brainLinks, brainSets, io))
                    {
                        break;
                    }
                }


                // Just build it manually
                //return ioLinks.Select(o => new { BrainIndex = o.Index, UtilityCore.Iterate(o.LinksOrig, o.LinksMoved)

                //var test = ioLinks.
                //    Select(o => new { BrainIndex = o.Index, Links = UtilityCore.Iterate(o.LinksOrig, o.LinksMoved).ToArray() }).
                //    ToArray();


                List<Tuple<int, int>> retVal = new List<Tuple<int, int>>();

                foreach (BrainBurden brain in ioLinks)
                {
                    foreach (int ioIndex in UtilityCore.Iterate(brain.LinksOrig, brain.LinksMoved))
                    {
                        retVal.Add(Tuple.Create(brain.Index, ioIndex));
                    }
                }

                return retVal.ToArray();


                //return initial;
            }
            private static Tuple<int, double>[][] IOLinks_PostAdjust_BrainBrainLinks(Set2D[] brainSets)
            {
                // Get the AABB of the brain sets, and use the diagonal as the size
                var aabb = Math2D.GetAABB(brainSets.Select(o => o.Center));
                double maxDistance = (aabb.Item2 - aabb.Item1).Length;

                // Get links between the brains and distances of each link
                var links = Math2D.GetDelaunayTriangulation(brainSets.Select(o => o.Center).ToArray(), true).
                    Select(o => new { Brain1 = o.Item1, Brain2 = o.Item2, Distance = (brainSets[o.Item1].Center - brainSets[o.Item2].Center).Length }).

                    // break here, get stats on lengths, omit links that are excessively long

                    Select(o => new { Brain1 = o.Brain1, Brain2 = o.Brain2, Resistane = (o.Distance / maxDistance) * IOPOSTMERGE_LINKRESISTANCEMULT }).      // calculate the wire's resistance (in terms of burden)
                    ToArray();


                //string dots = string.Join("\r\n", brainSets.Select(o => string.Format("_linksIO2D.Add(AddDot(new Point({0}, {1}), Brushes.Black, Brushes.Gray));", o.Center.X, o.Center.Y)));
                //string lines = string.Join("\r\n", links.Select(o => string.Format("_linksIO2D.Add(AddLine(new Point({0}, {1}), new Point({2}, {3}), Brushes.White));", brainSets[o.Brain1].Center.X, brainSets[o.Brain1].Center.Y, brainSets[o.Brain2].Center.X, brainSets[o.Brain2].Center.Y)));
                //string combined = dots + "\r\n\r\n" + lines;

                Tuple<int, double>[][] retVal = new Tuple<int, double>[brainSets.Length][];

                for (int cntr = 0; cntr < brainSets.Length; cntr++)
                {
                    // Store all links for this brain
                    retVal[cntr] = links.
                        Where(o => o.Brain1 == cntr || o.Brain2 == cntr).       // find links between this brain and another
                        Select(o => Tuple.Create(o.Brain1 == cntr ? o.Brain2 : o.Brain1, o.Resistane)).     // store the link to the other brain and the resistance
                        ToArray();
                }

                return retVal;
            }
            private static Tuple<int, int>[] IOLinks_Initial(Set2D[] brains, Item2D[] io, VoronoiResult2D voronoi)
            {
                List<Tuple<int, int>> retVal = new List<Tuple<int, int>>();
                List<int> remainingIO = Enumerable.Range(0, io.Length).ToList();        // store the remaining so that they can be removed as found (avoid unnecessary IsInside checks)

                for (int brainCntr = 0; brainCntr < brains.Length; brainCntr++)
                {
                    Edge2D[] edges = voronoi.EdgesByControlPoint[brainCntr].Select(o => voronoi.Edges[o]).ToArray();

                    if (edges.Length == 1)
                    {
                        throw new ApplicationException("finish this");
                    }

                    foreach (int ioIndex in remainingIO.ToArray())
                    {
                        if (Edge2D.IsInside(edges, io[ioIndex].Position))
                        {
                            retVal.Add(Tuple.Create(brainCntr, ioIndex));
                            remainingIO.Remove(ioIndex);
                        }
                    }
                }

                return retVal.ToArray();
            }
 public void Recalc(Set2D[] brainSets, Item2D[] io)
 {
     this.BrainSize = brainSets[this.Index].Items.Sum(o => o.Size);
     this.Burden = CalculateBurden(UtilityCore.Iterate(this.LinksOrig, this.LinksMoved).Sum(o => io[o].Size), this.BrainSize);        // size of the links / size of the brain
 }
            public LinkIO(Set2D brains, int io, Item2D[] allIO)
            {
                this.Brains = brains;

                this.IOIndex = io;
                this.IO = allIO[io];
            }
 public LinkBrain2D(Set2D brains1, Set2D brains2)
 {
     this.Brains1 = brains1;
     this.Brains2 = brains2;
 }
            private static Tuple<int, double>[][] BrainBrainDistances(Set2D[] brainSets, double brainLinkResistanceMult)
            {
                // Get the AABB of the brain sets, and use the diagonal as the size
                var aabb = Math2D.GetAABB(brainSets.Select(o => o.Center));
                double maxDistance = (aabb.Item2 - aabb.Item1).Length;

                // Get links between the brains and distances of each link
                List<Tuple<int, int, double>> links2 = new List<Tuple<int, int, double>>();
                for (int outer = 0; outer < brainSets.Length - 1; outer++)
                {
                    for (int inner = outer + 1; inner < brainSets.Length; inner++)
                    {
                        double distance = (brainSets[outer].Center - brainSets[inner].Center).Length;
                        double resistance = (distance / maxDistance) * brainLinkResistanceMult;

                        links2.Add(Tuple.Create(outer, inner, resistance));
                    }
                }

                Tuple<int, double>[][] retVal = new Tuple<int, double>[brainSets.Length][];

                for (int cntr = 0; cntr < brainSets.Length; cntr++)
                {
                    // Store all links for this brain
                    retVal[cntr] = links2.
                        Where(o => o.Item1 == cntr || o.Item2 == cntr).       // find links between this brain and another
                        Select(o => Tuple.Create(o.Item1 == cntr ? o.Item2 : o.Item1, o.Item3)).     // store the link to the other brain and the resistance
                        OrderBy(o => o.Item2).
                        ToArray();
                }

                return retVal;
            }