/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package org.drools.examples.sudoku

global Boolean explain;

rule "halt after setup"
when
    $ctr: Counter( count == 0 )
then
    drools.halt();
end

rule "emergency halt"
salience -100
when
    $s: Stepping()
then
    modify( $s ){ setEmergency( true ) } 
    drools.halt();
end

// A Setting object is inserted to define the value of a Cell.
// Rule "set a value" updates the cell and all cell groups containing it.
rule "set a value"
when
    // a Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )
    
    // a matching Cell, with no value set
    $c: Cell( rowNo == $rn, colNo == $cn, value == null,
              $cr: cellRow, $cc: cellCol, $cs: cellSqr )
    
    // count down
    $ctr: Counter( $count: count )
then
    // modify the Cell by setting its value
    modify( $c ){ setValue( $v ) }
//  System.out.println( "set cell " + $c.toString() );
    modify( $cr ){ blockValue( $v ) }
    modify( $cc ){ blockValue( $v ) }
    modify( $cs ){ blockValue( $v ) }
    modify( $ctr ){ setCount( $count - 1 ) }
end

// Rule for removing a value from all cells that are siblings
// in one of the three cell groups.
rule "eliminate a value from Cell"
when
    // a Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // the matching Cell, with the value already set
    Cell( rowNo == $rn, colNo == $cn, value == $v, $exCells: exCells )
    
    // for all Cells that are in cahoots with the updated cell
    $c: Cell( free contains $v ) from $exCells
then
//  System.out.println( "clear " + $v + " from cell " + $c.posAsString()  );
    // modify a related Cell by blocking the assigned value
    modify( $c ){ blockValue( $v ) }
end

// Rule for elimination the Setting fact.
rule "retract setting"
when
    // a Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // the matching Cell, with the value already set
    $c: Cell( rowNo == $rn, colNo == $cn, value == $v )

    // This is the negation of the last pattern in the previous rule.
    // Now the Setting fact can be safely retracted.
    not( $x: Cell( free contains $v )
         and
         Cell( this == $c, exCells contains $x ) )
then
//  System.out.println( "done setting cell " + $c.toString() ); 
    // Discard the Setter fact.
    retract( $s );
//////    Sudoku.sudoku.consistencyCheck();
end

// Detect a set of candidate values with cardinality 1 for some Cell.
// This is the value to be set.
rule "single"
when
    // There is currently no setting under way.
    not Setting()
    
    // One element in the "free" set.
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount == 1 )
then
    Integer i = $c.getFreeValue();
    if (explain) System.out.println( "single " + i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, i ) );
end

// Detect a set of candidate values with a value that is the only one
// in one of its groups. This is the value to be set.
rule "hidden single"
when
    // There is currently no setting under way.
    not Setting()
    not Cell( freeCount == 1 )
    
    // Some integer.
    $i: Integer()
    
    // The "free" set contains this number. 
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount > 1, free contains $i )
         
    // We have a cell group containing this cell $c
    $cg: CellGroup( cells contains $c )
    // but no other cell from that group contains $i
    not ( Cell( this != $c, free contains $i ) from $cg.getCells() )
then
    if (explain) System.out.println( "hidden single " + $i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, $i ) );
end


// A "naked pair" is two cells in some cell group with their sets of
// permissible values being equal with cardinality 2. These two values
// can be removed from all other candidate lists in the group.
rule "naked pair"
when
    // There is currently no setting under way.
    not Setting()
    not Cell( freeCount == 1 )

    // One cell with two candidates.
    $c1: Cell( freeCount == 2, $f1: free, $r1: cellRow, $rn1: rowNo, $cn1: colNo, $b1: cellSqr )
    
    // The containing cell group.
    $cg: CellGroup( freeCount > 2, cells contains $c1 )
    
    // Another cell with two candidates, not the one we already have.
    $c2: Cell( this != $c1, free == $f1 /*** , rowNo >= $rn1, colNo >= $cn1 ***/ ) from $cg.cells
    
    // Get one of the "naked pair".
    Integer( $v: intValue ) from $c1.getFree()
    
    // Get some other cell with a candidate equal to one from the pair.
    $c3: Cell( this != $c1 && != $c2, freeCount > 1, free contains $v ) from $cg.cells
then
    if (explain) System.out.println( "remove " + $v + " from " + $c3.posAsString() + " due to naked pair at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Remove the value.
    modify( $c3 ){ blockValue( $v ) }
end

// If two cells within the same cell group contain candidate sets with more than
// two values, with two values being in both of them but in none of the other
// cells, then we have a "hidden pair". We can remove all other candidates from
// these two cells.
//
rule "hidden pair in row"
when
    // There is currently no setting under way.
    not Setting()
    not Cell( freeCount == 1 )
    
    // Establish a pair of Integer facts
    $i1: Integer()
    $i2: Integer( this > $i1 )
    
    // Look for a Cell with these two among its candidates. (The upper bound on
    // the number of candidates avoids a lot of useless work during startup.)
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellRow: cellRow )

    // Get another one from the same row, with the same pair among its candidates,
    $c2: Cell( this != $c1, cellRow == $cellRow, freeCount > 2, free contains $i1 && contains $i2 )
    
    // Assertain that no other cell in the group has one of these two values.
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellRow.getCells() ) 
then
    if( explain) System.out.println( "hidden pair in row at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Set the candidate lists of these two Cells to the "hidden pair".
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in column"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellCol: cellCol )
    $c2: Cell( this != $c1, cellCol == $cellCol, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellCol.getCells() ) 
then
    if (explain) System.out.println( "hidden pair in column at " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in square"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2,
               $cellSqr: cellSqr )
    $c2: Cell( this != $c1, cellSqr == $cellSqr, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellSqr.getCells() ) 
then
    if (explain) System.out.println( "hidden pair in square " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end


rule "X-wings in rows"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,
                $ra: cellRow, $rano: rowNo,         $c1: cellCol,        $c1no: colNo )
    $cb1: Cell( freeCount > 1, free contains $i,
                $rb: cellRow, $rbno: rowNo > $rano,      cellCol == $c1 )
    not( Cell( this != $ca1 && != $cb1, free contains $i ) from $c1.getCells() )

    $ca2: Cell( freeCount > 1, free contains $i,
                cellRow == $ra, $c2: cellCol,       $c2no: colNo > $c1no )
    $cb2: Cell( freeCount > 1, free contains $i,
                cellRow == $rb,      cellCol == $c2 )
    not( Cell( this != $ca2 && != $cb2, free contains $i ) from $c2.getCells() )
    
    $cx: Cell( rowNo == $rano || == $rbno, colNo != $c1no && != $c2no,
               freeCount > 1, free contains $i )
then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in rows " +
            $ca1.posAsString() + " - " + $cb1.posAsString() +
            $ca2.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "X-wings in columns"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,                
                $c1: cellCol, $c1no: colNo,         $ra: cellRow,        $rano: rowNo )
    $ca2: Cell( freeCount > 1, free contains $i,                
                $c2: cellCol, $c2no: colNo > $c1no,      cellRow == $ra )
    not( Cell( this != $ca1 && != $ca2, free contains $i ) from $ra.getCells() )

    $cb1: Cell( freeCount > 1, free contains $i,
                cellCol == $c1, $rb: cellRow,  $rbno: rowNo > $rano )                
    $cb2: Cell( freeCount > 1, free contains $i,
                cellCol == $c2,      cellRow == $rb ) 
    not( Cell( this != $cb1 && != $cb2, free contains $i ) from $rb.getCells() )

    $cx: Cell( colNo == $c1no || == $c2no, rowNo != $rano && != $rbno,
               freeCount > 1, free contains $i )
then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in columns " +
            $ca1.posAsString() + " - " + $ca2.posAsString() +
            $cb1.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString()  );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "intersection removal column"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cc: cellCol )
    // but not in another cell of the same square and a different column
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellCol != $cc )

    // but there is a cell in the same column and another square containing this value
    $cx: Cell( freeCount > 1, free contains $i, cellCol == $cc, cellSqr != $cs )
then
    // remove the value from that other cell
    if (explain) {
        System.out.println( "column elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }        
    modify( $cx ){ blockValue( $i ) }
end

rule "intersection removal row"
when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cr: cellRow )
    // but not in another cell of the same square and a different row
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellRow != $cr )

    // but there is a cell in the same row and another square containing this value
    $cx: Cell( freeCount > 1, free contains $i, cellRow == $cr, cellSqr != $cs )
then
    // remove the value from that other cell
    if (explain) {
        System.out.println( "row elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }        
    modify( $cx ){ blockValue( $i ) }
end

// Y-wing
// $c1: Cell( freeCount == 2 ) contains a, b
// $c2: Cell( freeCount == 2 ) contains a, c
// $c3: Cell( freeCount == 2 ) contains b, c
// #g12: CellGroup() contains $c1, $c2
// #g1e: CellGroup() contains $c1, $c3

// $cx: Cell( freeCount > 1 ) contains c
//  # #g12: CellGroup() contains $c1, $c2


