// take a look at what edges we have and show some information - trying to get some ideas on how we can get faster good guesses
        private void AnalyseAllEdges()
        {
            //Console.WriteLine("Number of tiles off the edge " + tilesOffEdge);
            //Console.WriteLine("Number of mines to find " + minesLeft);

            this.edgeMinesMin = 0;
            this.edgeMinesMax = 0;

            foreach (EdgeStore edge in edgeStore)
            {
                int edgeMinMines = edge.data[0].GetMineCount();
                int edgeMaxMines = edge.data[edge.data.Count - 1].GetMineCount();

                edgeMinesMin = edgeMinesMin + edgeMinMines;
                edgeMinesMax = edgeMinesMax + edgeMaxMines;
            }

            information.Write("Min mines on all edges " + edgeMinesMin + ", max " + edgeMinesMax);
            this.edgeMinesMaxLeft = this.edgeMinesMax;  // these values are used  in the merge logic to reduce the number of lines need to keep
            this.edgeMinesMinLeft = this.edgeMinesMin;

            this.mineCountLowerCutoff = this.edgeMinesMin;
            this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft);  // can't have more mines than are left

            // comment this out when doing large board analysis
            return;

            // the code below reduces the range of mine count values to just be the 'significant' range

            List <ProbabilityLine> store  = new List <ProbabilityLine>();
            List <ProbabilityLine> store1 = new List <ProbabilityLine>();

            ProbabilityLine init = new ProbabilityLine(0);

            init.SetSolutionCount(1);

            store.Add(init);

            // combine all the edges to determine the relative weights of the mine count
            foreach (EdgeStore edgeDetails in edgeStore)
            {
                foreach (ProbabilityLine pl in edgeDetails.data)
                {
                    BigInteger plSolCount = pl.GetSolutionCount();

                    foreach (ProbabilityLine epl in store)
                    {
                        if (pl.GetMineCount() + epl.GetMineCount() <= this.maxTotalMines)
                        {
                            ProbabilityLine newpl = new ProbabilityLine(0);

                            newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount());

                            BigInteger eplSolCount = epl.GetSolutionCount();

                            newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount);

                            store1.Add(newpl);
                        }
                    }
                }

                store.Clear();

                // sort into mine order
                store1.Sort();

                int             mc  = store1[0].GetMineCount();
                ProbabilityLine npl = new ProbabilityLine(0);
                npl.SetMineCount(mc);

                foreach (ProbabilityLine pl in store1)
                {
                    if (pl.GetMineCount() != mc)
                    {
                        store.Add(npl);
                        mc  = pl.GetMineCount();
                        npl = new ProbabilityLine(0);
                        npl.SetMineCount(mc);
                    }
                    npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount());
                }

                store.Add(npl);
                store1.Clear();
            }

            BigInteger total      = 0;
            int        mineValues = 0;

            foreach (ProbabilityLine pl in store)
            {
                if (pl.GetMineCount() >= this.minTotalMines)                                                       // if the mine count for this solution is less than the minimum it can't be valid
                {
                    BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
                    total = total + mult * pl.GetSolutionCount();
                    mineValues++;
                }
            }

            //this.mineCountLowerCutoff = this.edgeMinesMin;
            //this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft);  // can't have more mines than are left
            BigInteger soFar = 0;

            foreach (ProbabilityLine pl in store)
            {
                if (pl.GetMineCount() >= this.minTotalMines)                                                       // if the mine count for this solution is less than the minimum it can't be valid
                {
                    BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
                    soFar = soFar + mult * pl.GetSolutionCount();
                    double perc = Combination.DivideBigIntegerToDouble(soFar, total, 6) * 100;
                    //Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " multiplier " + mult + " running % " + perc);
                    //Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " has running % " + perc);

                    if (mineValues > 30 && perc < 2.5)
                    {
                        this.mineCountLowerCutoff = pl.GetMineCount();
                    }

                    if (mineValues > 30 && perc > 97.5)
                    {
                        this.mineCountUpperCutoff = pl.GetMineCount();
                        break;
                    }
                }
            }

            information.Write("Significant range " + this.mineCountLowerCutoff + " - " + this.mineCountUpperCutoff);
            //this.edgeMinesMaxLeft = this.edgeMinesMax;
            //this.edgeMinesMinLeft = this.edgeMinesMin;


            return;

            // below here are experimental ideas on getting a good guess on very large boards

            int midRangeAllMines = (this.mineCountLowerCutoff + this.mineCountUpperCutoff) / 2;

            BigInteger[] tally       = new BigInteger[boxes.Count];
            double[]     probability = new double[boxes.Count];


            foreach (EdgeStore edgeDetails in edgeStore)
            {
                int sizeRangeEdgeMines = (edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount() - edgeDetails.data[0].GetMineCount()) / 2;

                int start = (this.mineCountLowerCutoff - edgeDetails.data[0].GetMineCount() + this.mineCountUpperCutoff - edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount()) / 2;
                //int start = midRangeAllMines - sizeRangeEdgeMines;

                //BigInteger mult = Combination.Calculate(this.minesLeft - start, this.tilesOffEdge);
                BigInteger totalTally = 0;

                foreach (ProbabilityLine pl in edgeDetails.data)
                {
                    BigInteger mult = Combination.Calculate(this.minesLeft - start - pl.GetMineCount(), this.tilesOffEdge);
                    totalTally += mult * pl.GetSolutionCount();
                    for (int i = 0; i < boxes.Count; i++)
                    {
                        if (edgeDetails.mask[i])
                        {
                            BigInteger work = pl.GetMineBoxCount(i) * mult;
                            tally[i] += work;
                        }
                    }

                    //mult = mult * (this.tilesOffEdge - start) / (start + 1);
                    //start++;
                }

                for (int i = 0; i < boxes.Count; i++)
                {
                    if (edgeDetails.mask[i])
                    {
                        probability[i] = Combination.DivideBigIntegerToDouble(tally[i], totalTally, 6) / boxes[i].GetTiles().Count;
                    }
                }

                int minIndex = -1;
                for (int i = 0; i < boxes.Count; i++)
                {
                    if (edgeDetails.mask[i])
                    {
                        if (minIndex == -1 || probability[i] < probability[minIndex])
                        {
                            minIndex = i;
                        }
                    }
                }

                if (minIndex != -1)
                {
                    information.Write("Best guess is " + boxes[minIndex].GetTiles()[0].AsText() + " with " + (1 - probability[minIndex]));
                }
                else
                {
                    information.Write("No Guess found");
                }
            }
        }
        // here we expand the localised solution to one across the whole board and
        // sum them together to create a definitive probability for each box
        private void CalculateBoxProbabilities()
        {
            //long start = DateTime.Now.Ticks;

            if (truncatedProbs)
            {
                Console.WriteLine("probability line combining was truncated");
            }

            information.Write("Solution count multiplier is " + this.solutionCountMultiplier);

            BigInteger[] tally = new BigInteger[this.boxes.Count];

            // total game tally
            BigInteger totalTally = 0;

            // outside a box tally
            BigInteger outsideTally = 0;

            //console.log("There are " + this.heldProbs.length + " different mine counts on the edge");

            bool[] emptyBox = new bool[boxes.Count];
            for (int i = 0; i < emptyBox.Length; i++)
            {
                emptyBox[i] = true;
            }

            int linesProcessed = 0;

            // calculate how many mines
            foreach (ProbabilityLine pl in this.heldProbs)
            {
                //console.log("Mine count is " + pl.mineCount + " with solution count " + pl.solutionCount + " mineBoxCount = " + pl.mineBoxCount);

                if (pl.GetMineCount() >= this.minTotalMines)      // if the mine count for this solution is less than the minimum it can't be valid

                {
                    linesProcessed++;

                    //console.log("Mines left " + this.minesLeft + " mines on PL " + pl.mineCount + " squares left = " + this.squaresLeft);
                    BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge);  //# of ways the rest of the board can be formed

                    information.Write("Mines in solution " + pl.GetMineCount() + " solution count " + pl.GetSolutionCount() + " multiplier " + mult);

                    outsideTally = outsideTally + mult * new BigInteger(this.minesLeft - pl.GetMineCount()) * (pl.GetSolutionCount());

                    // this is all the possible ways the mines can be placed across the whole game
                    totalTally = totalTally + mult * (pl.GetSolutionCount());

                    for (int i = 0; i < emptyBox.Length; i++)
                    {
                        if (pl.GetMineBoxCount(i) != 0)
                        {
                            emptyBox[i] = false;
                        }
                    }
                }
            }

            // determine how many clear squares there are
            if (totalTally > 0)
            {
                for (int i = 0; i < emptyBox.Length; i++)
                {
                    if (emptyBox[i])
                    {
                        clearCount = clearCount + boxes[i].GetTiles().Count;
                    }
                }
            }

            this.finalSolutionCount = totalTally * solutionCountMultiplier;

            information.Write("Game has  " + this.finalSolutionCount + " candidate solutions");
        }
        public static SolverActionHeader FindActions(SolverInfo information)
        {
            long time1 = DateTime.Now.Ticks;

            List <SolverAction> actions;

            if (information.GetGameStatus() == GameStatus.Lost)
            {
                information.Write("Game has been lost already - no valid moves");
                return(new SolverActionHeader());
            }
            else if (information.GetGameStatus() == GameStatus.Won)
            {
                information.Write("Game has been won already - no valid moves");
                return(new SolverActionHeader());
            }
            else if (information.GetGameStatus() == GameStatus.NotStarted)
            {
                information.Write("Game has not yet started - currently unable to provide help");
                return(new SolverActionHeader());
            }

            // are we walking down a brute force deep analysis tree?
            BruteForceAnalysis lastBfa = information.GetBruteForceAnalysis();

            if (lastBfa != null)     // yes
            {
                SolverTile expectedMove = lastBfa.GetExpectedMove();
                if (expectedMove != null && expectedMove.IsHidden())      // the expected move wasn't played !
                {
                    information.Write("The expected Brute Force Analysis move " + expectedMove.AsText() + " wasn't played");
                    information.SetBruteForceAnalysis(null);
                }
                else
                {
                    SolverAction move = lastBfa.GetNextMove();
                    if (move != null)
                    {
                        information.Write("Next Brute Force Deep Analysis move is " + move.AsText());
                        actions = new List <SolverAction>(1);
                        actions.Add(move);
                        return(BuildActionHeader(information, actions));
                    }
                }
            }


            actions = FindTrivialActions(information);

            long time2 = DateTime.Now.Ticks;

            information.Write("Finding Trivial Actions took " + (time2 - time1) + " ticks");

            // if we have some actions from the trivial search use them
            if (actions.Count > 0)
            {
                return(BuildActionHeader(information, actions));
            }


            if (information.GetTilesLeft() == information.GetDeadTiles().Count)
            {
                information.Write("All tiles remaining are dead");

                // when all the tiles are dead return the first one (they are all equally good or bad)
                foreach (SolverTile guess in information.GetDeadTiles())
                {
                    actions.Add(new SolverAction(guess, ActionType.Clear, 0.5));  // not all 0.5 safe though !!
                    return(BuildActionHeader(information, actions));
                }
            }

            // get all the witnessed tiles

            List <SolverTile>    witnesses    = new List <SolverTile>(information.GetWitnesses());
            HashSet <SolverTile> witnessedSet = new HashSet <SolverTile>();

            foreach (SolverTile witness in witnesses)
            {
                foreach (SolverTile adjTile in information.GetAdjacentTiles(witness))
                {
                    if (adjTile.IsHidden())
                    {
                        witnessedSet.Add(adjTile);
                    }
                }
            }

            List <SolverTile> witnessed = new List <SolverTile>(witnessedSet);
            int livingTilesLeft         = information.GetTilesLeft();
            int livingMinesLeft         = information.GetMinesLeft();
            int offEdgeTilesLeft        = information.GetTilesLeft() - witnessed.Count;

            //information.Write("Excluded tiles " + information.GetExcludedTiles().Count + " out of " + information.GetTilesLeft());
            //information.Write("Excluded witnesses " + information.GetExcludedWitnesses().Count);
            //information.Write("Excluded mines " + information.GetExcludedMineCount() + " out of " + information.GetMinesLeft());

            // if there are no living mines but some living tiles then the living tiles can be cleared
            if (livingMinesLeft == 0 && livingTilesLeft > 0)
            {
                information.Write("There are no living mines left - all living tiles must be clearable");

                for (int x = 0; x < information.description.width; x++)
                {
                    for (int y = 0; y < information.description.height; y++)
                    {
                        SolverTile tile = information.GetTile(x, y);
                        if (tile.IsHidden() && !tile.IsMine())
                        {
                            actions.Add(new SolverAction(tile, ActionType.Clear, 1));
                        }
                    }
                }
                return(BuildActionHeader(information, actions));
            }


            SolutionCounter solutionCounter = new SolutionCounter(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);

            solutionCounter.Process();
            information.Write("Solution counter says " + solutionCounter.GetSolutionCount() + " solutions and " + solutionCounter.getClearCount() + " clears");


            //ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, information.GetTilesLeft(), information.GetMinesLeft());
            ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);

            pe.Process();

            long time3 = DateTime.Now.Ticks;

            information.Write("Probability Engine took " + (time3 - time2) + " ticks");

            // have we found any local clears which we can use
            List <SolverTile> localClears = pe.GetLocalClears();

            if (localClears.Count > 0)
            {
                foreach (SolverTile tile in localClears)     // place each local clear into an action
                {
                    actions.Add(new SolverAction(tile, ActionType.Clear, 1));
                }
                information.Write("The probability engine has found " + localClears.Count + " safe Local Clears");

                // add any mines to it
                List <SolverTile> minesFound = pe.GetMinesFound();
                foreach (SolverTile tile in minesFound)     // place each mine found into an action
                {
                    information.MineFound(tile);
                    actions.Add(new SolverAction(tile, ActionType.Flag, 0));
                }
                information.Write("The probability engine has found " + minesFound.Count + " mines");


                return(BuildActionHeader(information, actions));
            }

            if (pe.GetBestEdgeProbability() == 1)
            {
                actions = pe.GetBestCandidates(1);
                information.Write("The probability engine has found " + actions.Count + " safe Clears");

                // add any mines to it
                List <SolverTile> minesFound = pe.GetMinesFound();
                foreach (SolverTile tile in minesFound)     // place each mine found into an action
                {
                    information.MineFound(tile);
                    actions.Add(new SolverAction(tile, ActionType.Flag, 0));
                }
                information.Write("The probability engine has found " + minesFound.Count + " mines");

                return(BuildActionHeader(information, actions));
            }

            // dead edge (all tiles on the edge are dead and there is only one mine count value)
            if (pe.GetDeadEdge().Count != 0)
            {
                SolverTile tile = pe.GetDeadEdge()[0];
                information.Write("Probability engine has found a dead area, guessing at " + tile.AsText());
                double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, pe.GetDeadEdgeSolutionCount(), 6);
                actions.Add(new SolverAction(tile, ActionType.Clear, probability));
                return(BuildActionHeader(information, actions));
            }

            // Isolated edges found (all adjacent tiles are also on the edge and there is only one mine count value)
            if (pe.GetOutcome() == ProbabilityEngine.Outcome.ISOLATED_EDGE)
            {
                information.Write("Probability engine has found an isolated area");

                Cruncher cruncher = pe.getIsolatedEdgeCruncher();

                // determine all possible solutions
                cruncher.Crunch();

                // determine best way to solver them
                BruteForceAnalysis bfa = cruncher.GetBruteForceAnalysis();
                bfa.process();

                // if after trying to process the data we can't complete then abandon it
                if (!bfa.IsComplete())
                {
                    information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
                    bfa = null;
                }
                else     // otherwise try and get the best long term move
                {
                    information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
                    SolverAction move = bfa.GetNextMove();
                    if (move != null)
                    {
                        information.SetBruteForceAnalysis(bfa);  // save the details so we can walk the tree
                        information.Write("Brute Force Analysis: " + move.AsText());
                        actions.Add(move);
                        return(BuildActionHeader(information, actions));
                    }
                    else if (bfa.GetAllDead())
                    {
                        SolverTile tile = cruncher.getTiles()[0];
                        information.Write("Brute Force Analysis has decided all tiles are dead on the Isolated Edge, guessing at " + tile.AsText());
                        double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, bfa.GetSolutionCount(), 6);
                        actions.Add(new SolverAction(tile, ActionType.Clear, probability));
                        return(BuildActionHeader(information, actions));
                    }
                    else
                    {
                        information.Write("Brute Force Analysis: no move found!");
                    }
                }
            }


            // after this point we know the probability engine didn't return any certain clears. But there are still some special cases when everything off edge is either clear or a mine

            // If there are tiles off the edge and they are definitely safe then clear them all, or mines then flag them
            if (offEdgeTilesLeft > 0 && (pe.GetOffEdgeProbability() == 1 || pe.GetOffEdgeProbability() == 0))
            {
                information.Write("Looking for the certain moves off the edge found by the probability engine");
                bool clear;
                if (pe.GetOffEdgeProbability() == 1)
                {
                    information.Write("All off edge tiles are clear");
                    clear = true;
                }
                else
                {
                    information.Write("All off edge tiles are mines");
                    clear = false;
                }

                for (int x = 0; x < information.description.width; x++)
                {
                    for (int y = 0; y < information.description.height; y++)
                    {
                        SolverTile tile = information.GetTile(x, y);

                        if (tile.IsHidden() && !witnessedSet.Contains(tile))
                        {
                            if (clear)
                            {
                                information.Write(tile.AsText() + " is clear");
                                actions.Add(new SolverAction(tile, ActionType.Clear, 1));
                            }
                            else
                            {
                                information.Write(tile.AsText() + " is mine");
                                information.MineFound(tile);
                                actions.Add(new SolverAction(tile, ActionType.Flag, 0));
                            }
                        }
                    }
                }

                if (actions.Count > 0)
                {
                    // add any mines to it
                    List <SolverTile> minesFound = pe.GetMinesFound();
                    foreach (SolverTile tile in minesFound)     // place each mine found into an action
                    {
                        information.MineFound(tile);
                        actions.Add(new SolverAction(tile, ActionType.Flag, 0));
                    }
                    information.Write("The probability engine has found " + minesFound.Count + " mines");

                    return(BuildActionHeader(information, actions));
                }
                else
                {
                    Console.WriteLine("No Actions found!");
                }
            }


            // these are guesses
            List <SolverAction> guesses = pe.GetBestCandidates(1);

            // we know the Probability Engine completed so hold onto the information for the gui
            information.SetProbabilityEngine(pe);

            // if there aren't many possible solutions then do a brute force search
            if (pe.GetSolutionCount() <= MAX_BFDA_SOLUTIONS)
            {
                //if (minesFound.Count > 0) {
                //    information.Write("Not doing a brute force analysis because we found some mines using the probability engine");
                //    return BuildActionHeader(information, actions);
                //}

                // find a set of independent witnesses we can use as the base of the iteration
                pe.GenerateIndependentWitnesses();

                BigInteger expectedIterations = pe.GetIndependentIterations() * SolverMain.Calculate(livingMinesLeft - pe.GetIndependentMines(), livingTilesLeft - pe.GetIndependentTiles());

                information.Write("Expected Brute Force iterations " + expectedIterations);

                // do the brute force if there are not too many iterations
                if (expectedIterations < MAX_BRUTE_FORCE_ITERATIONS)
                {
                    List <SolverTile> allCoveredTiles = new List <SolverTile>();

                    for (int x = 0; x < information.description.width; x++)
                    {
                        for (int y = 0; y < information.description.height; y++)
                        {
                            SolverTile tile = information.GetTile(x, y);
                            if (tile.IsHidden() && !tile.IsMine())
                            {
                                allCoveredTiles.Add(tile);
                            }
                        }
                    }

                    WitnessWebIterator[] iterators = BuildParallelIterators(information, pe, allCoveredTiles, expectedIterations);
                    BruteForceAnalysis   bfa       = Cruncher.PerformBruteForce(information, iterators, pe.GetDependentWitnesses());

                    bfa.process();

                    // if after trying to process the data we can't complete then abandon it
                    if (!bfa.IsComplete())
                    {
                        information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
                        bfa = null;
                    }
                    else     // otherwise try and get the best long term move
                    {
                        information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
                        SolverAction move = bfa.GetNextMove();
                        if (move != null)
                        {
                            information.SetBruteForceAnalysis(bfa);  // save the details so we can walk the tree
                            information.Write("Brute Force Analysis: " + move.AsText());
                            actions.Add(move);
                            return(BuildActionHeader(information, actions));
                        }
                        else
                        {
                            information.Write("Brute Force Analysis: no move found!");
                        }
                    }
                }
                else
                {
                    information.Write("Too many iterations, Brute Force not atempted");
                }
            }


            if (guesses.Count == 0)    // find an off edge guess
            {
                if (offEdgeTilesLeft > 0)
                {
                    information.Write("getting an off edge guess");
                    SolverTile   tile   = OffEdgeGuess(information, witnessedSet);
                    SolverAction action = new SolverAction(tile, ActionType.Clear, pe.GetOffEdgeProbability());
                    information.Write(action.AsText());
                    actions.Add(action);
                }
                else
                {
                    if (information.GetDeadTiles().Count > 0)
                    {
                        information.Write("Finding a dead tile to guess");
                        SolverTile tile = null;
                        foreach (SolverTile deadTile in information.GetDeadTiles())     // get the first dead tile
                        {
                            tile = deadTile;
                            break;
                        }
                        SolverAction action = new SolverAction(tile, ActionType.Clear, 0.5);  // probability may not be 0.5
                        actions.Add(action);
                    }
                }
            }
            else if (guesses.Count > 1)      // if we have more than 1 guess then do some tie break logic
            {
                information.Write("Doing a tie break for " + guesses.Count + " actions");
                actions = DoTieBreak(guesses);
            }
            else
            {
                actions = guesses;
            }

            return(BuildActionHeader(information, actions));
        }