//public static T GetTypedValue<T>(CAProperty property)
        //{
        //    dynamic value = property.value;
        //    return value;
        //}

        public static Bitmap MakeImage(CA ca)
        {
            CAGraph    graph    = ca.Graph;
            CASettings settings = ca.Settings;

            // needs to be SVG that returns various types
            if (graph.Shape == GridShape.Square)
            {
                int x = graph.Cells.GetLength(0);
                int y = graph.Cells.GetLength(1);
                int z = graph.Cells.GetLength(2);

                Bitmap bmp = new Bitmap(x, y);
                using (Graphics g = Graphics.FromImage(bmp))
                {
                    for (ushort i = 0; i < z; i++)
                    {
                        Bitmap layer = new Bitmap(x, y);
                        for (ushort j = 0; j < x; j++)
                        {
                            for (ushort k = 0; k < y; k++)
                            {
                                var   cell  = graph.GetCell(new ValueTuple <ushort, ushort, ushort>(j, k, i));
                                Color color = Color.Black;
                                if (cell != null)
                                {
                                    if (cell.ContainsAgent())
                                    {
                                        int state = cell.Agent.GetStateProperty();
                                        if (settings.StateColorMap.Count >= state)
                                        {
                                            color = settings.StateColorMap[state];
                                        }
                                    }
                                }
                                layer.SetPixel(j, k, color);
                            }
                        }
                        g.DrawImage(layer, new Point(0, 0));
                    }
                }
                return(bmp);
            }
            else
            {
                throw new Exception("Graph shape not expected.");
            }
        }
        public static void ChemicalEquilibrium(ValueTuple <ushort, ushort, ushort> dimensions, List <List <double> > probabilities, string savePath, ValueTuple <ulong, double, double> convergeanceConditions, CASettings settings = null)
        {
            List <(string, dynamic)> stateProperties = new List <(string, dynamic)>();

            for (int i = 0; i < probabilities.Count; i++)
            {
                if (probabilities[i].Count != (probabilities.Count - 1))
                {
                    throw new Exception("Each state must include a probability of conversion to every other state (NOT its own)");
                }
                stateProperties.Add(("state", (ushort)i));
            }
            var noneNeighborhood = new CANeighborhood(CANeighborhoodType.None);
            var localTarget      = new CATarget(CAScale.Local, CAEntityType.Agent, noneNeighborhood);
            CA  ca = new CA(dimensions, GridShape.Square);

            if (settings != null)
            {
                ca.Settings = settings;
            }
            else
            {
                ca.Settings = new CASettings {
                    CopyFormat = CACopyFormat.Reference, StateColorMap = StaticMethods.GetColors(probabilities.Count), /*StoreChangeCounts = true,*/ StoreCounts = true, Subprocessing = false, StoreTransitions = true
                };
            }
            ca.Settings.States = (ushort)probabilities.Count;
            for (int i = 0; i < probabilities.Count; i++)
            {
                ca.AddAgents((1.0 / probabilities.Count), (ushort)i, true);
                int  val     = 0;
                CAIf stateif = new CAIf("state", CATargetType.All, localTarget, CAEquality.Equal_to, stateProperties[i]);
                for (int j = 0; j < probabilities.Count; j++)
                {
                    if (i == j)
                    {
                        continue;
                    }
                    CAThenChange changeThen = new CAThenChange(localTarget, "state", CAOperator.Equal, (ushort)j, probabilities[i][val]);
                    CARule       changeRule = new CARule(new List <CAIf> {
                        stateif
                    }, new List <CAThen> {
                        changeThen
                    });
                    ca.AddRule(changeRule);
                    val++;
                }
            }
            CAExitCondition exit = new CAExitConditionConvergeance(convergeanceConditions.Item1, convergeanceConditions.Item2, convergeanceConditions.Item3);

            ca.CreateExitCondition(exit);
            DateTime start = DateTime.Now;

            //bool alive = true;
            Console.WriteLine("Every iteration, the CA will check that the population counts have changed by less than " + (convergeanceConditions.Item2 > 1? ((int)convergeanceConditions.Item2).ToString() + " cells":(convergeanceConditions.Item2 * 100).ToString() + "%") + " (for a single state).");
            Console.WriteLine("It will then check for >" + convergeanceConditions.Item1 + " iterations, then " + (convergeanceConditions.Item3 > 1 ? ((int)convergeanceConditions.Item3).ToString() + " iterations" : (convergeanceConditions.Item3 * 100).ToString() + "%") + " of the total iterations, where the previous predicate is true (consecutively).");
            Console.WriteLine("It will automatically end at that time, but you can exit early by pressing Escape. Your data up to that point will be saved.");
            Console.WriteLine("Press Enter to begin.");
            Console.ReadKey();
            DateTime now    = DateTime.Now;
            Task     output = Task.Factory.StartNew(() =>
            {
                while (!ca.Exit && !(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
                {
                    ca.Run();
                    Console.Write("\rIteration {0}, Counts: {1}", ca.Iteration, String.Join(", ", ca.CurrentCounts) + "                ");
                }
                Console.WriteLine("");
            });

            output.Wait();
            Console.WriteLine("Task complete after: " + (DateTime.Now - now).TotalSeconds + " seconds");
            var probString = probabilities.Select(x => x.Select(y => y.ToString()).ToList()).ToList();

            for (int i = 0; i < probString.Count; i++)
            {
                probString[i].Insert(0, i.ToString());
            }
            // save data
            List <List <string> > header = new List <List <string> >()
            {
                new List <string> {
                    now.ToString("o")
                },
                new List <string> {
                    "Chemical Equilibrium"
                },
                new List <string> {
                    "Probabilities"
                }
            };

            header.AddRange(probString);
            ca.Save(header, ca.ListSaveProperties(), savePath + System.IO.Path.DirectorySeparatorChar + start.ToString("o").Replace(':', '.') + " Iteration " + ca.Iteration + " Data.csv");
            Console.WriteLine("Data saved to: " + savePath + System.IO.Path.DirectorySeparatorChar + start.ToString("o").Replace(':', '.') + " Iteration " + ca.Iteration + " Data.csv");
            Console.WriteLine();
            Console.WriteLine("Enter Y to save image, and anything else to skip.");
            bool saveImage = false;
            var  key       = Console.ReadLine();

            if (key.ToLower().Equals("y"))
            {
                saveImage = true;
            }
            else
            {
                Console.WriteLine("Are you sure? Enter Y to save image, and anything else to skip.");
                key = Console.ReadLine();
                if (key.ToLower().Equals("y"))
                {
                    saveImage = true;
                }
            }
            if (saveImage)
            {
                var    image    = StaticMethods.MakeImage(ca);
                string filename = savePath + System.IO.Path.DirectorySeparatorChar + start.ToString("o").Replace(':', '.') + " Iteration " + ca.Iteration + " Image.bmp";
                image.Save(filename, System.Drawing.Imaging.ImageFormat.Bmp);
                Console.WriteLine("Saved to {0}.", filename);
            }
            Console.WriteLine("Complete. Press any key to exit.");
            Console.ReadKey();
        }