#include "queens.h"

int CalculateSolutions(int rows, QUEENS_CALLBACK callback)
{
    __asm
    {
/*///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////Main Code///////////////////////////////////*/

        /*//////////////////////////////////////////////////////////////////////
        //Initialization:
        //      1)save any of the registers we are about to use on the stack
        //      2)save "this" position in the base pointer (just after the regs)
        //      3)save width ("C" param: rows) on the stack
        //      4)save callback function ("C" param: callback) on the stack
        //          NOTE: we have to save the width and callback BEFORE 
        //          manipulating the base pointer
        //      5)create memory on stack for the board (rows^2 bytes)
        //          NOTE: esp must point to EVEN memory address
        //      6)clear this memory (clear board)
        ///////////////////////////////////////////////////////////////////////*/
        pushfd
        pushad                              //save our regs
        mov     eax, DWORD PTR [rows]       //put width in eax
        push    eax                         //save width on stack
        push    DWORD PTR [callback]        //save callback on stack
        lea     ebp, [esp + 4]              //ebp is last reg. pushed
        
        mul     al                          //eax = width*width  (maximum width: 255)
        add     eax, 3
        shr     eax, 2                      //eax is # DWORDS needed to clear board
        mov     ecx, eax                    //get ready to rep stos
        xor     eax, eax                    //store 0's on stack
        lea     edi, [esp - 4]              //[edi] is the first DWORD below callback
        std                                 //rep stos downward
        rep stos DWORD PTR [edi]            //zero ECX DWORDS below ESP
        add     edi, 4                      //rep stos decrements one extra time
        mov     esp, edi                    //now stack points to top of board

        /*/////////////////////////////////////////////
        //Stack Check:
        //      |---------------|
        //      |   (pushad)    |
        //      | eax, edx, ecx |   (in this order)
        //      | ebx, esb, esp |   32 bytes
        //      |   esi, edi    |
        //      |---------------|
        //EBP-->|     width     |   4 bytes
        //      |---------------|
        //      |    callback   |   4 bytes
        //      |---------------|
        //      |   (padding)   |   (rows^2 % 4) bytes
        //      |---------------|
        //      |   board end   |
        //      |     ....      |
        //      ~~~~~~~~~~~~~~~~~   (rows^2) bytes
        //      |     ....      |
        //EDI-->|  board start  |<--ESP (currently)
        //      |---------------|
        //
        //Regs:
        //      EBP, EDI, ESP: as above
        //      EAX, ECX: 0
        //      (All are saved and can be manipulated
        //
        //Todo:
        //      Call StartOnRow and allow it  to
        //      recursively solve the queen's problem
        //      with brute force (return in EAX)
        /////////////////////////////////////////////*/

        mov     ebx, DWORD PTR [ebp]        //get the width from memory
        mov     edx, edi                    //pointer to (0, 0) on board
        call    StartOnRow                  //start brute force algorithm
        
        push    ebp                         //save our entry point
        push    eax                         //and return value

        //callback->finished(edi, MSG_FINISHED, 0, 0, 0, 0)
        sub     esp, 16                     //last 4 params unused
        mov     ebx, MSG_FINISHED           //make sure we push a DWORD
        push    ebx                         //msg
        push    edi                         //ptr to board
        call    DWORD PTR [ebp - 4]         //see stack diagram above
        add     esp, 24                     //restore stack

        pop     eax                         //restore return value
        pop     ebp                         //use this to find entry point

        jmp     FinishedASM                 //cleanup and return
/*///////////////////////////////////////////////////////////////////////////////
///////////////////////////////End of Main Code////////////////////////////////*/


        /*///////////////////////////////////////////////////////
        //StartOnRow()
        //
        //      Function uses brute force and recursion to solve
        //      the queen's problem.  Returns value is the
        //      number of solutions, and it is stored in EAX.
        //
        //      Assumptions:
        //          EDI: points to start of board (and increasing
        //              the pointer travels towards the end of 
        //              the board; only used with callback)
        //          AL:  row that this call starts on
        //          EDX: points to the starting position on the
        //              board; (0, AL)
        //          EBP: points to the 4 byte width of the board
        //              and directly below it in memory is the
        //              4 byte callback pointer
        //          EBX: width of the board (must be less than 255)
        //
        //      Registers Used:
        //          EAX (during call): AL is current row and
        //              AH is current column
        //          EAX: (upon returning): Holds return value;
        //              previous contents destroyed
        //          EBX, EDI, EBP: remain unchanged
        //          ECX, EDX: previous contents destroyed
        /////////////////////////////////////////////////////////*/
StartOnRow:
        mov     ah, 0                       //always start on first column
        xor     ecx, ecx                    //clear ecx
        push    ecx                         //this will be our solutions counter
        mov     ecx, ebx                    //loop for each square in the row

//RowLoop: for each square in the row; if queen can be placed, place and recurse
RowLoop:
        push    ecx                         //and ecx
        
        call    IsThreatened                //can we place a queen here?
        cmp     ch, 0                       //check the return value

        pop     ecx                         //restore ecx

        je      QueenAccepted               //ch == 0; place queen, recurse on next row

        //can't place a queen, clear the return value and move on
        xor     ch, ch
        jmp     RowLoopEnd

        //queen can be placed here, if this is the last row, increment counter and
        //move on.  If this is not the last row, recurse
QueenAccepted:
        inc     al                          //row WAS zero-indexed
        
        cmp     al, bl                      //our row compared to board size
        jb      RecurseNextRow              //our row less than total rows

        dec     al                          //return al to zero-indexed
        inc     DWORD PTR [esp]             //increment our solutions counter
        
        pushad
        //callback->solution(edi, MSG_SOLUTION, 0, 0, 0, 0)
        sub     esp, 16                     //last 4 params unused
        mov     ebx, MSG_SOLUTION           //make sure we push a DWORD
        push    ebx                         //msg
        push    edi                         //board pointer
        call    DWORD PTR [ebp - 4]         //see stack diagram above
        add     esp, 24                     //restore stack
        popad

        mov     BYTE PTR [edx], 0           //remove the queen

        jmp     RowLoopEnd                  //go to next square

RecurseNextRow:
        dec     al                          //return al to zero-indexed

        //IsThreatened places the queen on the board

        push    eax                         //save our current row/column
        push    edx                         //save our pointer to our row/column
        push    ecx                         //save our counter

        add     edx, ebx                    //go one row further
        inc     al                          //move coordinates one row further
        movzx   ecx, ah                     //move our width into 4 byte ecx
        sub     edx, ecx                    //move pointer to first of row
        call    StartOnRow                  //recurse

        add     DWORD PTR [esp + 12], eax   //add retval to our solutions counter

        pop     ecx                         //restore our counter
        pop     edx                         //restore ptr to our row/column
        pop     eax                         //restore our row/column

        mov     BYTE PTR [edx], 0           //remove the queen and go to next square        

        pushad
        //callback->backtracking(edi, MSG_BACKTRACK, al, 0, 0, 0)
        sub     esp, 12                     //last 3 params unused
        movzx   ebx, al                     //use ebx to extend al to a DWORD
        push    ebx                         //push row we are backtracking to
        mov     ebx, MSG_BACKTRACK          //make sure we push a DWORD
        push    ebx                         //msg
        push    edi                         //pointer to board
        call    DWORD PTR [ebp - 4]         //see stack diagram above
        add     esp, 24                     //restore stack
        popad

RowLoopEnd:
        inc     edx                         //move pointer over one
        inc     ah                          //move column over one
        loop RowLoop
//End of RowLoop

        pop     eax                         //put solutions counter in EAX
        ret
/*///////////////////////////////////////////////////////////////////////////////
//////////////////////////////End of StartOnRow////////////////////////////////*/

        /*/////////////////////////////////////////////////////////////
        //IsThreatened()
        //
        //      Function checks if it would be safe to place a
        //      queen at the given position of the board.
        //      If the queen would be threatened at the position
        //      CH is set to 1; otherwise, no changes are made to ECX
        //      If a position is found to be unthreatened, a queen is
        //      placed in that position to allow the callback to
        //      display the corrent board configuration.
        //
        //      Assumptions:
        //          EDI: points to start of board (and increasing
        //              the pointer travels towards the end of 
        //              the board; only used with callback)
        //          AL:  row of queen to be placed
        //          AH:  column of queen to be placed
        //          EDX: points to the position of the queen to
        //              be placed (AH, AL) on the board
        //          EBP: points to the 4 byte width of the board
        //              and directly below it in memory is the
        //              4 byte callback pointer
        //          EBX: width of the board
        //
        //      Registers Used:
        //          EAX (during call): (AH, AL) is the coordinates
        //              of the possible threatening queen
        //          EBX, EDI, EBP, EDX, EAX: remain unchanged
        //          ECX: previous contents destroyed
        //          CH:  if queen is threatened, set to 1 and previous
        //              contents destroyed.  Otherwise, it is unchanged
        ///////////////////////////////////////////////////////////////*/
IsThreatened:
        push    eax                         //save possible queen's board coordinates
        push    edx                         //save ptr to possible queen's position

        cmp     al, 0                       //trivial acceptance (top row)
        je      NotThreatened               //can't be threatened on top row
        
        //Loop going up from our position (# loops == our row # [if zero indexed])
        movzx   ecx, al                     //loop once for each row above us

//LoopCheckNorth: check all rows directly above our own
LoopCheckNorth:
        dec     al                          //keep track of where we are at for callback
        sub     edx, ebx                    //move ptr one row up
        
        cmp     BYTE PTR [edx], 0           //is a queen here?
        jne     Collision                   //our queen is threatened

        loop    LoopCheckNorth
//End of LoopCheckNorth
        
        mov     edx, DWORD PTR [esp]        //restore ptr to position (leave on stack)
        mov     eax, DWORD PTR [esp + 4]    //restore row/column (leave on stack)

        //Loop going up and to the left (# loops == minimum of row # and column #)
        inc     ebx                         //temporarily change the width; now, 
                                            //subtracting this will go up and to left
        cmp     ah, 0                       //make sure we aren't on left edge
        je      SkipLoopCheckNW             //on left edge, no need to check this

        cmp     ah, al                      //compare row # to column #
        ja      ColumnsGreater

        movzx   ecx, ah                     //rows greater; loops == column # (zero-indexed)

        jmp     LoopCheckNorthWest          //start looping

ColumnsGreater:
        movzx   ecx, al                     //columns greater; loops == row # (zero-indexed)
        
//LoopCheckNorthWest: check all threats diagonally up and left of our position
LoopCheckNorthWest:
        sub     edx, ebx                    //move pointer up one and left one
        sub     ax, 0x0101                  //rows--, columns-- (same time, save a tick)

        cmp     BYTE PTR [edx], 0           //is a queen here?
        jne     Collision                   //our queen is threatened

        loop LoopCheckNorthWest
//End of LoopCheckNorthWest
SkipLoopCheckNW:

        mov     edx, DWORD PTR [esp]        //restore ptr to position (leave on stack)
        movzx   eax, BYTE PTR [esp + 5]     //restore column; (row in a sec)
        
        sub     ebx, 2                      //now (edx -= ebx) will move up to right
        
        //now we need to check diagonally up to the right; the number of squares
        //(number of loops) will be the minimum of our distance from the right
        //side of the board, and our distance from the top.  min(width - column, row)
        mov     ecx, DWORD PTR [ebp]        //ecx = width
        dec     ecx                         //now width and column are zero-indexed
        sub     ecx, eax                    //ecx = width - column (distance from right)
        cmp     ecx, 0                      //check if square is on right side of board
        je      NotThreatened               //no need to check if on right side

        movzx   eax, BYTE PTR [esp + 4]     //eax = row
        cmp     ecx, eax                    //find min(width - column, row)
        jb      TravelToRightSide           //distance to right side is less

        mov     ecx, eax                    //distance to top is less
        mov     eax, DWORD PTR [esp + 4]    //restore board coordinates
        jmp     LoopCheckNorthEast          //start checking diagonally up-right

TravelToRightSide:                          //distance to right side is less
        //ecx already has the distance to the top of the board
        mov     eax, DWORD PTR [esp + 4]    //restore the board coordinates

//LoopCheckNorthEast: check for threats diagonally up and to the right of our square
LoopCheckNorthEast:
        sub     edx, ebx                    //move pointer up one and right
        inc     ah                          //columns++
        dec     al                          //rows--

        cmp     BYTE PTR [edx], 0           //is a queen here?
        jne     Collision                   //our queen is threatened

        loop LoopCheckNorthEast
//End of LoopCheckNorthEast

NotThreatened:
        pop     edx                         //restore pointer to position
        mov     BYTE PTR [edx], 1           //place the queen

        //the saved eax (board coordinates) are at the top of the stack now
        pushad                              //now, saved eax is at [esp + 32]
        //callback->accepted(edi, MSG_ACCEPTED, pushed ah, pushed al, 0, 0)
        sub     esp, 8                      //last 2 params unused (eax at [esp + 40])
        movzx   ebx, BYTE PTR [esp + 41]    //use ebx to extend saved row to DWORD
        push    ebx                         //push row  (eax at [esp + 44])
        movzx   ebx, BYTE PTR [esp + 44]    //use ebx to extend saved column to DWORD
        push    ebx                         //push column
        mov     ebx, MSG_ACCEPTED           //make sure we push a DWORD
        push    ebx                         //msg
        push    edi                         //pointer to the board
        call    [ebp - 4]                   //see stack diagram above
        add     esp, 24                     //restore stack
        popad

        jmp     IsThreatenedDone            //made it this far with no collisions

Collision:
        pop     edx                         //restore pointer to position
        mov     BYTE PTR [edx], 1           //place the queen, remove it before return

        //the saved eax (board coordinates) are at top of the stack
        pushad                              //saved eax is at [esp + 32]
        //callback->collision(edi, MSG_COLLISION, saved ah, saved al, ah, al)
        movzx   ebx, ah                     //use ebx to extend al to DWORD
        push    ebx                         //push collision row (eax at [esp + 36])
        movzx   ebx, al                     //use ebx to extend ah to DWORD
        push    ebx                         //push collision column (eax at [esp + 40])
        movzx   ebx, BYTE PTR [esp + 41]    //use ebx to extend pushed al to DWORD
        push    ebx                         //push row (eax at [esp + 44])
        movzx   ebx, BYTE PTR [esp + 44]    //use ebx to extend pushed ah to DWORD
        push    ebx                         //push column
        mov     ebx, MSG_COLLISION          //make sure we push a DWORD
        push    ebx                         //msg
        push    edi                         //pointer to the board
        call    [ebp - 4]                   //see stack diagram above
        add     esp, 24                     //restore stack
        popad

        mov     BYTE PTR [edx], 0           //remove it, it was threatened

        mov     ch, 1                       //set return value

IsThreatenedDone:
        pop     eax                         //restore square's coordinates
        mov     ebx, DWORD PTR [ebp]        //restore width


        //callback->abort_check(edi, MSG_ABORT_CHECK, 0, 0, 0, 0)
        pushad

        sub     esp, 16                     //4 unused params
        mov     ebx, MSG_ABORT_CHECK        //make sure it is 32-bit
        push    ebx                         //msg
        xor     ebx, ebx                    //clear ebx
        push    ebx                         //param unused
        call    [ebp - 4]                   //see stack diagram above
        add     esp, 24                     //restore stack
        
        cmp     eax, 0                      //return non-zero: return to caller
        
        popad

        jnz     ThrowAbort                  //like throwing an exception

        

        ret                                 //return back to caller

ThrowAbort:                                 //Callback proc told us to abort
        mov     eax, -1                     //return value
        jmp     FinishedASM                 //restore regs and exit
                                            //as long as EBP hasn't changed,
                                            //we can exit anytime we want

/*///////////////////////////////////////////////////////////////////////////////
//////////////////////////////End of IsThreatened//////////////////////////////*/
    


/*////////////////////////////Cleanup and Return/////////////////////////////////
///////////////////////////////////////////////////////////////////////////////*/
FinishedASM:
        lea     esp, [ebp + 4]              //esp is now top of pushed regs
        mov     [esp + 28], eax             //overwrite saved eax value
        popad                               //leaves eax unchanged
        popfd
    }

    //let "C" return from its own function with return value in EAX
}