/*
 * analyst.c -- Functions to keep a record of engine analysis
 * $Id$
 *
 * Copyright 1995 Free Software Foundation, Inc.
 *
 * ------------------------------------------------------------------------
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * ------------------------------------------------------------------------
 */
/*

-debug -cp -fcp 'crafty' -fd 'e:\program\winboard\crafty' -scp 'phalanx -e 100' -sd 'e:\program\winboard\phalanx'
-debug -cp -fcp 'crafty' -fd 'e:\program\winboard\crafty' -scp 'tcb dumber.cui' -sd 'e:\program\winboard\tcb'
-debug -cp -fcp 'crafty' -fd 'e:\program\winboard\crafty' -scp 'GNUChes5 xboard'
-debug -cp -scp 'crafty' -sd 'e:\program\winboard\crafty' -fcp 'phalanx' -fd 'e:\program\winboard\phalanx'
-debug -cp -scp 'crafty' -sd 'e:\program\winboard\crafty' -fcp 'tcb' -fd 'e:\program\winboard\tcb'
  
E:\program\winboard\winboard.exe -cp -fcp 'tcb dumb.cui' -fd 'e:\program\winboard\tcb' -scp 'GNUChes5 xboard' @ freechess.ini
-ics -icshost freechess.org -icshelper timeseal @ 'e:\program\winboard\freechess.ini'
*/
#include <windows.h>
#include <winuser.h>
#include <stdio.h>
#include <assert.h>

#include "common.h"
#include "winboard.h"
#include "backend.h"
#include "backendz.h"
#include "whistory.h"
#include "analyst.h"

extern HWND hwndMain; 
extern int analysisTimerEvent;  
HINSTANCE hInst;          /* current instance */
     
typedef enum {
    DefaultMode = 0, CoachMode, TimerMode
} AnalystMode;

// get from analysis engine every N millisec
#define DEFAULT_TIMER_DELAY 500

// multiply these two number for the minimum node searched when not in DepthMode
#define NODE_SEARCH_MULTIPLE 10000
#define NODE_SEARCH_DEFAULT 10


struct AnalystStruct analyst={
        TimerMode,
        1,  // timer_time seconds
        200,// score_threshold
        1,  // white move
        1,  // black move
        1,  // delete comment
        1,
        0,
        0};

void 
InitAnalyst()
{
    analyst.message_last_score = 0;
    analyst.game_last_score = 0;
    analyst.is_analyzing = 0;

    analyst.last_move = currentMove;
    analyst.tactic_last_move = currentMove;
    analyst.tactic_last_score = 0;
    analyst.tactic_latest_score = 0;
    analyst.tactic_was_black_side = analyst.tactic_black_side;

    if (analyst.tactic_black_side && !(currentMove & 1)) {
        analyst.last_move = currentMove + 1; // make sure it is odd number
        analyst.tactic_last_move = currentMove + 1; // make sure it is odd number
    } 
    analyst.has_tick = 0;
}
void 
AnalystTacticSetBlack(int b)
{
    analyst.tactic_black_side = b;
}
int 
AnalystStartClock()
{
    int time_delay = DEFAULT_TIMER_DELAY; // half second
    if (analyst.mode==TimerMode)
        time_delay = analyst.timer_time * 1000;
    
    analysisTimerEvent = SetTimer(hwndMain, (UINT) ANALYSIS_TIMER_ID,
                    (UINT) time_delay, NULL);
    return analysisTimerEvent; 
}
void 
RestartTimer()
{
  	KillTimer(hwndMain, analysisTimerEvent);
    AnalystStartClock();
    analyst.has_tick = 0;
}

void 
StartCoachMatch(int machine_white)
{
    InitAnalyst();
    analyst.mode = CoachMode;
    coachMode = 1; // machine black
    if (machine_white)
        coachMode = 2; // machine white
    TwoMachinesEvent();
}


void 
StartBlunderAnalyst()
{
    InitAnalyst();
    if (analyst.delete_old_comment)
        ResetComments();

    SendToProgram("sd 0", &first); // make sure it is good depth

    analyst.is_analyzing = 1;
    analyst.start_move = currentMove;
    ModeHighlight();
}

// call this to restore everything
void 
EndAnalyst()
{
    char buf[50];
    sprintf(buf, "sd %d\n", appData.searchDepth);
    SendToProgram(buf, &first);
    analyst.is_analyzing = 0;
    ReinitHistory();
}

void 
AbortAnalyst()
{
    EditGameEvent();
    analyst.is_analyzing = 0;
}
extern ChessProgramStats programStats;

void BlunderComment(char *comment)
{
    if ((currentMove & 1) && !analyst.black_moves) return; // don't record black move
    if (!(currentMove & 1) && !analyst.white_moves) return; // don't record black move
    ReplaceComment(currentMove+1, comment);
}

void 
AnalystStep()
{
   char move_text[MOVE_LEN];
   int score = programStats.score;

   if ((currentMove % 2) && !analyst.absolute_score)
       score = -score; // convert to absolute
    
   GetMoveAt(currentMove, move_text); // obsolete

   if (currentMove == analyst.start_move)   // first time here
        analyst.game_last_score = score;

    if (currentMove < forwardMostMove)
    {   
        if (programStats.line_is_book)
        {
            ReplaceComment(currentMove, "book end");
        }
        else
        {
            int diff = analyst.game_last_score - score;
            if (abs(diff) >=  analyst.score_threshold)
            {
                // record the analysis as comment
                char buf[1000];
                sprintf(buf,"[score=%d](%s score=%d)", analyst.game_last_score,
                            programStats.movelist, score);
                BlunderComment(buf); // put it after the next move
	            if (appData.debugMode)
                {
                     char buf[100];
		             sprintf(buf, "blunder analysis adding comment at move %d\n",currentMove);
                     fprintf(debugFP, buf);;
                }


            }
            analyst.game_last_score = score;
        }
    }
	if (appData.debugMode)
    {
         char buf[100];
		 sprintf(buf, "blunder analysis recording score %d move %s\n", 
                    analyst.game_last_score, move_text);
         fprintf(debugFP, buf);;
    }

    if (programStats.line_is_book)
        AbortAnalyst(); // hit an end
    else
    {
        BackwardEvent();
    }        
}

int 
IsAnalysisCurrent(char first_move[MOVE_LEN])
{
     // we want this data if the if statement is true
     // check the movelist if analysis engine is analyzing the right move (only crafty return move number)
    char move1[MOVE_LEN], move2[MOVE_LEN];
    int movenum, movenum2;
    int valid = sscanf(programStats.movelist,"%d. %s %s %d.",&movenum, move1, move2, &movenum2);
    if (!valid) 
    {
        valid = sscanf(programStats.movelist,"%s %s %d.",move1, move2);
        strcpy (first_move, move1);
        if (valid) {
            ChessMove moveType;
            int fromX, fromY, toX, toY;
            char promoChar;
                // check if this is a valid move
            valid = ParseOneMove(first_move, currentMove, 
                                &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
        }
        return valid;
    }
    if (valid)  // can we find the movenumber
    {
        int has_half_move = 0;
        int has_dots = !strcmp(move1,"...");
        
        int right_num = movenum==(currentMove/2)+1;
        
        if (has_dots && currentMove%2)
            has_half_move = 1; // valid
        if (!has_dots && !(currentMove%2))
            has_half_move = 1;
        if (has_dots && !currentMove%2)
            has_half_move = 0; // invalid, wait till next event

        valid = has_half_move & right_num; // the valid flag
        if (valid)
        {
            if (has_dots)
                strcpy(first_move, move2);
            else
                strcpy(first_move, move1);
        }
    }
    return valid;
}

void 
AnalystBlunderData(char *s)
{
    int valid = 0; // is data up to date
    
     
    if (programStats.line_is_book)
    {
        BlunderComment("book end");
        AbortAnalyst(); // hit an end
    }
    
   
    if (!analyst.has_tick) return;

	if (appData.debugMode)
    {
		 fprintf(debugFP, "analysing blunder");
         fprintf(debugFP, s);
         fprintf(debugFP, "\n");
    }

    // get info from programStats directly;
    if (analyst.is_analyzing  // the right mode
        && strlen(programStats.movelist)    // have movelist
        )
    {
       // we want this data if the if statement is true
       // check the movelist if analysis engine is analyzing the right move (only crafty return move number)
        char first_move[MOVE_LEN];
        valid = IsAnalysisCurrent(first_move);
        if (valid)
        {
            AnalystStep(first_move);
            RestartTimer();
        }
    }
	if (appData.debugMode && !valid)
    {
		 fprintf(debugFP, "invalid analysing blunder data");
    }
}
void 
InitDefaultDisplay()
{
    int i;
    for (i = 0 ;i < NUM_LINE_RECORDED; i++)
        analyst.analysis_moves[i][0] = 0;    // empty the string
}

char *
AnalystDefaultData(char *s)
{
    char first_move[MOVE_LEN];
    int i;
    analyst.analysis_buf[0] = 0;

    if (analyst.last_move != currentMove)
    {
        InitDefaultDisplay();   // move has changed
        // wait for the next tick to skip spurios old state analysis        
        analyst.last_move = currentMove;
        analyst.has_tick = 0;
    }

    if (analyst.has_tick && IsAnalysisCurrent(first_move))
    {
        int copy_moves = 0;


        for (i = 0; i < NUM_LINE_RECORDED; i++)
        {   
            if (!strncmp(analyst.analysis_moves[i],first_move, strlen(first_move)))
                copy_moves = 1;

            if (analyst.analysis_moves[i][0]==0) // empty string
            {
                strcpy (analyst.analysis_moves[i], first_move);
                copy_moves = 1;
            }

            if (copy_moves)
            {
                char move_buf[ANALYST_LINE_BUF];    // make sure we are not overrunning buffer
                char *move_list = programStats.movelist;
                if (strlen(programStats.movelist) > ANALYST_LINE_BUF-10)
                {
                    move_list = move_buf;
                    strncpy(move_buf, programStats.movelist, ANALYST_LINE_BUF-10);
                    move_buf[ANALYST_LINE_BUF-10]=0;
                } 
                sprintf(analyst.analysis_line[i], "%+.2f %s", ((float)programStats.score)/100.0, move_list);

                break;
            }
        }

        // collect recorded analysis into one line separated with CR
        for (i = NUM_LINE_RECORDED-1; i >=0 ; i--)
        {
            if (analyst.analysis_moves[i][0]!=0) // empty string
            {
                strcat(analyst.analysis_buf, analyst.analysis_line[i]);
                strcat(analyst.analysis_buf, "\r\n");
            }
        }
    }
    return analyst.analysis_buf;
}
char *
AnalystData(char *s)
{
    char *ret = "";
    switch (analyst.mode)
    {
        case CoachMode:
            ret = AnalystDefaultData(s);    // get list of possible moves
        break;
        case TimerMode:
            if (analyst.is_analyzing)
                AnalystBlunderData(s);
        break;
    }
    return ret;
}

void 
AnalystTimerEvent()
{
    AnalysisPeriodicEvent(0);
    analyst.has_tick = 1;

}

LRESULT CALLBACK
BlunderWindowProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  BOOL ok;

  switch (message) {
  case WM_INITDIALOG: /* message: initialize dialog box */
    /* Center the dialog over the application window */
    CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
    /* Initialize the dialog items */
    SetDlgItemInt(hDlg, OPT_BLUTime, analyst.timer_time, FALSE);
    SetDlgItemInt(hDlg, OPT_BLUThreshold, analyst.score_threshold, FALSE);

    CheckDlgButton(hDlg, OPT_BLUWhite, analyst.white_moves);
    CheckDlgButton(hDlg, OPT_BLUBlack, analyst.black_moves);
    CheckDlgButton(hDlg, OPT_BLUDeleteComment, analyst.delete_old_comment);
    CheckDlgButton(hDlg, OPT_BLUAbsoluteScore, analyst.absolute_score);

    
  return TRUE;

  case WM_COMMAND: /* message: received a command */
    switch (LOWORD(wParam)) {
    case IDOK:
    {
      int threshold, timer_time;

      // default values for unselected option
      timer_time = analyst.timer_time;

      threshold = GetDlgItemInt(hDlg, OPT_BLUThreshold, &ok, FALSE);
      if (!ok || threshold<0 || threshold > 500 ) {
          MessageBox(hDlg, "Invalid threshold (0-500)",
                    "Option Error", MB_OK|MB_ICONEXCLAMATION);
          return FALSE;
      }
      if (!IsDlgButtonChecked(hDlg, OPT_BLUWhite) && !IsDlgButtonChecked(hDlg, OPT_BLUBlack)) {
          MessageBox(hDlg, "Please check at least white or black moves",
                     "Option Error", MB_OK|MB_ICONEXCLAMATION);
          return FALSE;
      }

      timer_time = GetDlgItemInt(hDlg, OPT_BLUTime, &ok, FALSE);
      if (!ok || timer_time <= 0 || timer_time > 60) 
      {
          MessageBox(hDlg, "Invalid moves Timer time (0-60)",
                     "Option Error", MB_OK|MB_ICONEXCLAMATION);
          return FALSE;
      }

      analyst.mode = TimerMode;
      analyst.timer_time = timer_time;
      analyst.white_moves = IsDlgButtonChecked(hDlg, OPT_BLUWhite);
      analyst.black_moves = IsDlgButtonChecked(hDlg, OPT_BLUBlack);
      analyst.delete_old_comment = IsDlgButtonChecked(hDlg, OPT_BLUDeleteComment);
      analyst.score_threshold = threshold;
      analyst.score_threshold = threshold;
      analyst.absolute_score = IsDlgButtonChecked(hDlg, OPT_BLUAbsoluteScore);
      EndDialog(hDlg, TRUE);
      return TRUE;

    case IDCANCEL:
      EndDialog(hDlg, FALSE);
      return TRUE;
    }
    break;
  }
  break;
  }
  return FALSE;
}


int 
BlunderAnalystOptionsPopup(HWND hwnd)
{
    int ret = 0;
    FARPROC lpProc = MakeProcInstance((FARPROC)BlunderWindowProc, hInst);
    ret = DialogBox(hInst, MAKEINTRESOURCE(DLG_Analyst), hwnd, (DLGPROC) lpProc);
    FreeProcInstance(lpProc);
    return ret;
}
/*
-debug -cp -fcp 'crafty' -fd 'e:\program\winboard\crafty' -scp 'phalanx -e 100' -sd 'e:\program\winboard\phalanx'
-debug -cp -fcp 'crafty' -fd 'e:\program\winboard\crafty' -scp 'tcb dumb.cui' -sd 'e:\program\winboard\tcb'

  */