/** Apply the operator to the given state, returning a new state.
     *
     * @param state The source state.
     *
     * @returns The newly created destination state.
     */
    public IDBSpaceState Apply(DBSearchModule module, IDBSpaceState stateDB)
    {
        GBSearchModule gmod  = (GBSearchModule)module;
        GBSpaceState   state = (GBSpaceState)stateDB;

        // TODO: remove later, we already checked this previously
        if (Valid(state) == false)
        {
            throw (new ArgumentException("Operator not applicable!"));
        }

        GoBangBoard newBoard = (GoBangBoard)state.GB.Clone();

        // Apply all the f_{add} stones
        for (int n = 0; n < fAdd.GetLength(0); ++n)
        {
            newBoard.board[fAdd[n, 1], fAdd[n, 0]] = fAdd[n, 2];
        }

        GBSpaceState newState = new GBSpaceState(newBoard, this, state,
                                                 gmod.maximumCategory);

        newState.UpdateIsGoal(gmod);

        return(newState);
    }
    // Global function
    public GBThreatSequence FindWinningThreatSeq()
    {
        // First find a number of possibly winning threat trees.
        GBSearchModule gbSearch  = new GBSearchModule(GoBangBoard.boardDim);
        GBSpaceState   rootState = new GBSpaceState((GoBangBoard)gb.Clone());

        rootState.UpdateIsGoal(gbSearch);

        // HEURISTIC: use category reduction (page 140-141)
        gbSearch.categoryReductionHeuristicOn = true;

        DBSearch db = new DBSearch(gbSearch, breadthFirst);

        db.Search(rootState);
        //db.DumpDOTGoalsOnly ();

        // Now, enumerate all the possibly winning threat trees found
        GBThreatSequence[] potentialWinningSeqs =
            GBThreatSequence.BuildAllGoalPathes(gbSearch, db.Root);
        Console.WriteLine("{0} potential winning threat sequences.",
                          potentialWinningSeqs.Length);

        // Check them one by one until a surely winning threat tree is found
        GoBangBoard gbFlipped = (GoBangBoard)gb.Clone();

        gbFlipped.Flip();

        int DEBUGwinningFound         = 0;
        GBThreatSequence DEBUGwinning = null;

        foreach (GBThreatSequence threatSeq in potentialWinningSeqs)
        {
            if (DefenseRefutes(threatSeq,
                               (GoBangBoard)gbFlipped.Clone()) < 0)
            {
                // Found a sure win, return early
                // FIXME: for debugging we count all winning sequences found,
                // but we should return as early as possible.
                DEBUGwinningFound += 1;
                DEBUGwinning       = threatSeq;
                //Console.WriteLine ("WINNING:\n{0}", threatSeq);
                // FIXME
                //return (threatSeq);
            }
        }

        Console.WriteLine("{0} winning of {1} potential winning threat sequences identified",
                          DEBUGwinningFound, potentialWinningSeqs.Length);

        // Found no unrefuted threat sequence
        return(DEBUGwinning);
    }
    /** Try to find a winning threat sequence by dependency based search and
     * on the fly refutation for goal nodes.
     *
     * Return early, as soon as a sure candidate has been found.
     *
     * @param timeoutMS If non-zero, a maximum time spend in db-search is
     * given in milliseconds.  At least 1000 (1s) is meaningful to do
     * something, though.
     *
     * @returns The first winning threat sequence on success, null otherwise.
     */
    public GBThreatSequence FindWinningThreatSeqOTF(int timeoutMS)
    {
        // First find a number of possibly winning threat trees.
        GBSearchModule gbSearch  = new GBSearchModule(GoBangBoard.boardDim);
        GBSpaceState   rootState = new GBSpaceState((GoBangBoard)gb.Clone());

        rootState.UpdateIsGoal(gbSearch);

        // HEURISTIC: use category reduction (page 140-141)
        // FIXME: re-enable as soon as three3 bug is fixed.
        // FIXME: test if this is good in the real ai, otherwise disable again.
        //gbSearch.categoryReductionHeuristicOn = true;

        // Do on-the-fly refutation checking.
        gbSearch.doDefenseRefutationCheck = true;

        if (timeoutMS != 0)
        {
            gbSearch.doExpirationCheck = true;
            gbSearch.expireTime        = DateTime.Now.AddMilliseconds(timeoutMS);
        }

        DBSearch db = new DBSearch(gbSearch, breadthFirst);

        try {
            Console.WriteLine("Board:\n{0}\n", gb);
            db.Search(rootState);
            //db.DumpDOT ();
        } catch (GBSearchModule.GBSearchTimeoutException) {
            // We timed out...
            Console.WriteLine("FindWinningThreatSeqOTF: timeouted...");
        } catch (GBWinningThreatSequenceFoundException gex) {
            //db.DumpDOT ();
            return(gex.seq);
        }

        return(null);
    }
    // TODO: this needs to be changed fundamentally, including changes in
    // DBSearch.cs.
    //
    // GoMoku will need up to four nodes combined in one combination step, for
    // a situation like this (artificial, but illustrates the point):
    //
    //    O  O  O
    //    O  O  O
    //    O3 O2 O1
    //
    // Lets assume the two rows at the top already existed before, and the
    // states O1, O2 and O3 have been independently explored (as they create a
    // new application of an operator, this will be the case).  However, then
    // the row O1, O2 and O3 create a new threat.  This can only be "seen" by
    // db-search if they are allowed to be combined in one step.  No single
    // combination will create a new dependent operator.
    //
    // We might do up-to-four combination efficiently by exploiting the board
    // geometry.  To do that, we create an array the same dimensions as the
    // board of linked list, storing states.  For each dependency node state,
    // store the state in a number of linked lists.  Store them in that linked
    // lists that are behind the coordinates in the f_{add} set of the last
    // operator of the state.
    //
    // Then, for each coordinate, we do the following: extract all linked
    // lists in a G_7 environment centered at the coordinate into one big
    // linked list.  Only check all the states refered in this big list for
    // 2-, 3- and 4-combinability.  For each coordinate we have to do this
    // four times for the different G_7 environment.
    //
    // The pseudo code for the whole combination stage would look like:
    //
    // foreach (Coordinate coord in boardCoordinates) {
    //   foreach (G_7 g7 centeredIn coord) {
    //     ArrayList states = new ArrayList ();
    //     foreach (Square sq in g7)
    //       states.AddRange (sq.States);
    //
    //     foreach (2-Combination (c1, c2) in states)
    //       CheckCombinability (c1, c2);
    //     foreach (3-Combination (c1, c2, c3) in states)
    //       CheckCombinability (c1, c2, c3);
    //     foreach (4-Combination (c1, c2, c3, c4) in states)
    //       CheckCombinability (c1, c2, c3, c4);
    //   }
    // }
    //
    // The numerical complexity is (n \over k) = \frac{n!}{k! (n - k)!}
    // where n = number of states collected in "states". k = number of
    // elements in the combination (2, 3, 4).
    //
    // So, for n states on the combined G_7 square list this would give
    // (n \over k) k-Combinations.
    //
    // States |  2-C |    3-C |     4-C
    // -------+------+--------+--------
    //     10 |   45 |    120 |     210
    //     20 |  190 |   1140 |    4845
    //    100 | 4950 | 161700 | 3921225
    //
    // So we should keep the number of states influencing a board square well
    // below 100, while <= 20 is certainly ok.
    //
    // XXX: for now, we only test with two-combinability
    public IDBSpaceState CombineIfResultIsNewOperators(DBNode node1,
                                                       Stack node1Path, DBNode node2, Stack node2Path)
    {
        GBSpaceState gstate1 = (GBSpaceState)node1.State;
        GBSpaceState gstate2 = (GBSpaceState)node2.State;
        GBOperator   last2   = gstate2.LastOperator;

        // First check the boards are not incompatible:
        // TODO: check if this is right (or necessary, does not seem to change
        // any results).
        if (gstate2.GB.CompatibleWith(((GBSpaceState)node1.State).GB) == false)
        {
            return(null);
        }

        // Build combined state by applying operator chain.
        ArrayList operatorChain = new ArrayList();

        foreach (DBNode pN in node2Path)
        {
            if (node1Path.Contains(pN))
            {
                break;
            }

            GBSpaceState nstate = (GBSpaceState)pN.State;
            operatorChain.Add(nstate.LastOperator);

            if (nstate.CombinedOperPath != null)
            {
                ArrayList combRev = (ArrayList)nstate.CombinedOperPath.Clone();
                combRev.Reverse();

                operatorChain.AddRange(combRev);
            }
        }
        operatorChain.Reverse();
        operatorChain.Add(last2);

        GBSpaceState gComb = (GBSpaceState)node1.State;

        foreach (GBOperator oper in operatorChain)
        {
            gComb = (GBSpaceState)oper.Apply(this, gComb);
        }

        //GBSpaceState gComb = (GBSpaceState) last2.Apply (this, node1.State);
        //gComb.GB.AddExtraStones (gstate2.GB);
        gComb.UpdateLegalOperators(maximumCategory, categoryReductionHeuristicOn);

        // Now check if the new state results in new operators
        GBOperator[] n1o = gstate1.LegalOperators;
        GBOperator[] n2o = gstate2.LegalOperators;

        GBOperator[] nCo = gComb.LegalOperators;
        if (nCo == null)
        {
            return(null);
        }

        // Check: nCo \setminus (n1o \cup n2o) \neq \emptyset
        foreach (GBOperator gbo in nCo)
        {
            if (n1o != null && Array.IndexOf(n1o, gbo) >= 0)
            {
                return(null);
            }
            if (n2o != null && Array.IndexOf(n2o, gbo) >= 0)
            {
                return(null);
            }
        }

        gComb.UpdateIsGoal(this);

        // Now that the combination succeeded, we still need to copy over all
        // the operators we applied 'at once' when copying the field content.
        // We need to do this for the threat sequence search later, which
        // needs operator-level granularity for the defense search.
        gComb.BuildCombinedOperatorPath(node1Path, node2Path);

        return(gComb);
    }
    /** Test the GBSearchModule
     */
    public static void Main(string[] args)
    {
        // Initialize a board randomly
        GoBangBoard gb  = new GoBangBoard();
        Random      rnd = new Random();

        /*
         * for (int n = 0 ; n < 23 ; ++n)
         *      gb.board[rnd.Next (0, gb.boardDim), rnd.Next (0, gb.boardDim)] =
         *              rnd.Next (0, 3) - 1;
         */

        /*
         * gb.board[5,5] = gb.board[5,8] = -1;
         * gb.board[7,4] = gb.board[7,9] = -1;
         *
         * gb.board[5,4] = gb.board[5,6] = gb.board[5,7] = gb.board[5,9] = 1;
         * gb.board[7,6] = gb.board[7,7] = 1;
         */

        gb.board[6, 6] = gb.board[6, 7] = gb.board[6, 8] = 1;
        gb.board[7, 7] = gb.board[8, 6] = gb.board[8, 7] = gb.board[8, 8] = 1;
        gb.board[9, 6] = gb.board[10, 5] = gb.board[10, 6] = gb.board[10, 7] = 1;

        gb.board[6, 5]  = gb.board[7, 5] = gb.board[7, 6] = gb.board[7, 8] = -1;
        gb.board[8, 5]  = gb.board[8, 9] = gb.board[9, 5] = gb.board[9, 7] = gb.board[9, 9] = -1;
        gb.board[10, 4] = gb.board[11, 6] = -1;

        gb.board[5, 5] = 1;
        gb.board[4, 4] = -1;

        gb.board[6, 9]  = 1;
        gb.board[6, 10] = -1;
        gb.board[4, 7]  = 1;
        gb.board[5, 7]  = -1;

        /*
         * gb.board[6,10] = 1;
         * gb.board[6,9] = -1;
         */

        /*
         * gb.board[6,6] = gb.board[6,7] = gb.board[6,8] = 1;
         * gb.board[7,7] = gb.board[8,6] = gb.board[8,7] = gb.board[8,8] = 1;
         * gb.board[9,6] = gb.board[10,5] = gb.board[10,6] = gb.board[10,7] = 1;
         *
         * gb.board[6,5] = gb.board[7,5] = gb.board[7,6] = gb.board[7,8] = -1;
         * gb.board[8,5] = gb.board[8,9] = gb.board[9,5] = gb.board[9,7] = gb.board[9,9] = -1;
         * gb.board[10,4] = gb.board[11,6] = -1;
         *
         * // Move 1/2
         * gb.board[5,5] = 1;
         * gb.board[4,4] = -1;
         *
         * // Move 3/4
         * gb.board[5,7] = 1;
         * gb.board[4,7] = -1;
         *
         * // Move 5/6
         * gb.board[5,9] = 1;
         * gb.board[4,10] = -1;
         *
         * // Move 7/8
         * gb.board[5,8] = 1;
         * gb.board[5,6] = -1;
         *
         * // Move 9/10
         * gb.board[5,11] = 1;
         * gb.board[5,10] = -1;
         *
         * // Move 11/12
         * gb.board[6,10] = 1;
         * gb.board[6,9] = -1;
         *
         * // Move 13/14
         * gb.board[7,9] = 1;
         * gb.board[4,12] = -1;
         *
         * // Move 15/16
         * gb.board[4,6] = 1;
         * gb.board[3,5] = -1;
         */

        /* TODO: check this, ask marco
         * gb.board[4,4] = gb.board[6,6] = gb.board[7,7] = 1;
         */

        GBSearchModule gbSearch  = new GBSearchModule(GoBangBoard.boardDim);
        GBSpaceState   rootState = new GBSpaceState(gb);

        rootState.UpdateIsGoal(gbSearch);

        DBSearch db = new DBSearch(gbSearch, false);

        db.Search(rootState);

        db.DumpDOT();
        //db.DumpDOTGoalsOnly ();
        gbSearch.DEBUGnodeArray();

        /*
         * foreach (DLPSpaceState state in db.GoalStates ()) {
         *      DumpOperatorChain (state);
         * }
         */
    }
    /** Try to find a winning threat sequence by dependency based search and
     * on the fly refutation for goal nodes.
     *
     * Return early, as soon as a sure candidate has been found.
     *
     * @param timeoutMS If non-zero, a maximum time spend in db-search is
     * given in milliseconds.  At least 1000 (1s) is meaningful to do
     * something, though.
     *
     * @returns The first winning threat sequence on success, null otherwise.
     */
    public GBThreatSequence FindWinningThreatSeqOTF(int timeoutMS)
    {
        // First find a number of possibly winning threat trees.
        GBSearchModule gbSearch = new GBSearchModule (GoBangBoard.boardDim);
        GBSpaceState rootState = new GBSpaceState ((GoBangBoard) gb.Clone ());
        rootState.UpdateIsGoal (gbSearch);

        // HEURISTIC: use category reduction (page 140-141)
        // FIXME: re-enable as soon as three3 bug is fixed.
        // FIXME: test if this is good in the real ai, otherwise disable again.
        //gbSearch.categoryReductionHeuristicOn = true;

        // Do on-the-fly refutation checking.
        gbSearch.doDefenseRefutationCheck = true;

        if (timeoutMS != 0) {
            gbSearch.doExpirationCheck = true;
            gbSearch.expireTime = DateTime.Now.AddMilliseconds (timeoutMS);
        }

        DBSearch db = new DBSearch (gbSearch, breadthFirst);

        try {
            Console.WriteLine ("Board:\n{0}\n", gb);
            db.Search (rootState);
            //db.DumpDOT ();
        } catch (GBSearchModule.GBSearchTimeoutException) {
            // We timed out...
            Console.WriteLine ("FindWinningThreatSeqOTF: timeouted...");
        } catch (GBWinningThreatSequenceFoundException gex) {
            //db.DumpDOT ();
            return (gex.seq);
        }

        return (null);
    }
    // Global function
    public GBThreatSequence FindWinningThreatSeq()
    {
        // First find a number of possibly winning threat trees.
        GBSearchModule gbSearch = new GBSearchModule (GoBangBoard.boardDim);
        GBSpaceState rootState = new GBSpaceState ((GoBangBoard) gb.Clone ());
        rootState.UpdateIsGoal (gbSearch);

        // HEURISTIC: use category reduction (page 140-141)
        gbSearch.categoryReductionHeuristicOn = true;

        DBSearch db = new DBSearch (gbSearch, breadthFirst);
        db.Search (rootState);
        //db.DumpDOTGoalsOnly ();

        // Now, enumerate all the possibly winning threat trees found
        GBThreatSequence[] potentialWinningSeqs =
            GBThreatSequence.BuildAllGoalPathes (gbSearch, db.Root);
        Console.WriteLine ("{0} potential winning threat sequences.",
            potentialWinningSeqs.Length);

        // Check them one by one until a surely winning threat tree is found
        GoBangBoard gbFlipped = (GoBangBoard) gb.Clone ();
        gbFlipped.Flip ();

        int DEBUGwinningFound = 0;
        GBThreatSequence DEBUGwinning = null;

        foreach (GBThreatSequence threatSeq in potentialWinningSeqs) {
            if (DefenseRefutes (threatSeq,
                (GoBangBoard) gbFlipped.Clone ()) < 0)
            {
                // Found a sure win, return early
                // FIXME: for debugging we count all winning sequences found,
                // but we should return as early as possible.
                DEBUGwinningFound += 1;
                DEBUGwinning = threatSeq;
                //Console.WriteLine ("WINNING:\n{0}", threatSeq);
                // FIXME
                //return (threatSeq);
            }
        }

        Console.WriteLine ("{0} winning of {1} potential winning threat sequences identified",
            DEBUGwinningFound, potentialWinningSeqs.Length);

        // Found no unrefuted threat sequence
        return (DEBUGwinning);
    }
    /** Test the GBSearchModule
     */
    public static void Main(string[] args)
    {
        // Initialize a board randomly
        GoBangBoard gb = new GoBangBoard ();
        Random rnd = new Random ();

        /*
        for (int n = 0 ; n < 23 ; ++n)
            gb.board[rnd.Next (0, gb.boardDim), rnd.Next (0, gb.boardDim)] =
                rnd.Next (0, 3) - 1;
        */

        /*
        gb.board[5,5] = gb.board[5,8] = -1;
        gb.board[7,4] = gb.board[7,9] = -1;

        gb.board[5,4] = gb.board[5,6] = gb.board[5,7] = gb.board[5,9] = 1;
        gb.board[7,6] = gb.board[7,7] = 1;
        */

        gb.board[6,6] = gb.board[6,7] = gb.board[6,8] = 1;
        gb.board[7,7] = gb.board[8,6] = gb.board[8,7] = gb.board[8,8] = 1;
        gb.board[9,6] = gb.board[10,5] = gb.board[10,6] = gb.board[10,7] = 1;

        gb.board[6,5] = gb.board[7,5] = gb.board[7,6] = gb.board[7,8] = -1;
        gb.board[8,5] = gb.board[8,9] = gb.board[9,5] = gb.board[9,7] = gb.board[9,9] = -1;
        gb.board[10,4] = gb.board[11,6] = -1;

        gb.board[5,5] = 1;
        gb.board[4,4] = -1;

        gb.board[6,9] = 1;
        gb.board[6,10] = -1;
        gb.board[4,7] = 1;
        gb.board[5,7] = -1;

        /*
        gb.board[6,10] = 1;
        gb.board[6,9] = -1;
        */

        /*
        gb.board[6,6] = gb.board[6,7] = gb.board[6,8] = 1;
        gb.board[7,7] = gb.board[8,6] = gb.board[8,7] = gb.board[8,8] = 1;
        gb.board[9,6] = gb.board[10,5] = gb.board[10,6] = gb.board[10,7] = 1;

        gb.board[6,5] = gb.board[7,5] = gb.board[7,6] = gb.board[7,8] = -1;
        gb.board[8,5] = gb.board[8,9] = gb.board[9,5] = gb.board[9,7] = gb.board[9,9] = -1;
        gb.board[10,4] = gb.board[11,6] = -1;

        // Move 1/2
        gb.board[5,5] = 1;
        gb.board[4,4] = -1;

        // Move 3/4
        gb.board[5,7] = 1;
        gb.board[4,7] = -1;

        // Move 5/6
        gb.board[5,9] = 1;
        gb.board[4,10] = -1;

        // Move 7/8
        gb.board[5,8] = 1;
        gb.board[5,6] = -1;

        // Move 9/10
        gb.board[5,11] = 1;
        gb.board[5,10] = -1;

        // Move 11/12
        gb.board[6,10] = 1;
        gb.board[6,9] = -1;

        // Move 13/14
        gb.board[7,9] = 1;
        gb.board[4,12] = -1;

        // Move 15/16
        gb.board[4,6] = 1;
        gb.board[3,5] = -1;
        */

        /* TODO: check this, ask marco
        gb.board[4,4] = gb.board[6,6] = gb.board[7,7] = 1;
        */

        GBSearchModule gbSearch = new GBSearchModule (GoBangBoard.boardDim);
        GBSpaceState rootState = new GBSpaceState (gb);
        rootState.UpdateIsGoal (gbSearch);

        DBSearch db = new DBSearch (gbSearch, false);
        db.Search (rootState);

        db.DumpDOT ();
        //db.DumpDOTGoalsOnly ();
        gbSearch.DEBUGnodeArray ();
        /*
        foreach (DLPSpaceState state in db.GoalStates ()) {
            DumpOperatorChain (state);
        }
        */
    }
    /** Apply the operator to the given state, returning a new state.
     *
     * @param state The source state.
     *
     * @returns The newly created destination state.
     */
    public IDBSpaceState Apply(DBSearchModule module, IDBSpaceState stateDB)
    {
        GBSearchModule gmod = (GBSearchModule) module;
        GBSpaceState state = (GBSpaceState) stateDB;

        // TODO: remove later, we already checked this previously
        if (Valid (state) == false)
            throw (new ArgumentException ("Operator not applicable!"));

        GoBangBoard newBoard = (GoBangBoard) state.GB.Clone ();

        // Apply all the f_{add} stones
        for (int n = 0 ; n < fAdd.GetLength (0) ; ++n)
            newBoard.board[fAdd[n,1], fAdd[n,0]] = fAdd[n,2];

        GBSpaceState newState = new GBSpaceState (newBoard, this, state,
            gmod.maximumCategory);
        newState.UpdateIsGoal (gmod);

        return (newState);
    }