/*
 * evalgraph.c - Evaluation graph back-end part
 *
 * Author: Alessandro Scotti (Dec 2005)
 *
 * Copyright 2005 Alessandro Scotti
 *
 * Enhancments Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015,
 * 2016 Free Software Foundation, Inc.
 *
 * ------------------------------------------------------------------------
 *
 * GNU XBoard 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 3 of the License, or (at
 * your option) any later version.
 *
 * GNU XBoard 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, see http://www.gnu.org/licenses/.  *
 *
 *------------------------------------------------------------------------
 ** See the file ChangeLog for a revision history.  */

// code refactored by HGM to obtain front-end / back-end separation

#include "config.h"

#include <stdio.h>

#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else /* not STDC_HEADERS */
# if HAVE_STRING_H
#  include <string.h>
# else /* not HAVE_STRING_H */
#  include <strings.h>
# endif /* not HAVE_STRING_H */
#endif /* not STDC_HEADERS */

#include "common.h"
#include "frontend.h"
#include "backend.h"
#include "evalgraph.h"

/* Module globals */
ChessProgramStats_Move * currPvInfo;
int currFirst = 0;
int currLast = 0;
int currCurrent = -1;
int range = 1;
int differentialView;

int nWidthPB = 0;
int nHeightPB = 0;

int MarginX = 18;
int MarginW = 4;
int MarginH = 4;

// back-end
static void
DrawLine (int x1, int y1, int x2, int y2, int penType)
{
    DrawSegment( x1, y1, NULL, NULL, PEN_NONE );
    DrawSegment( x2, y2, NULL, NULL, penType );
}

// back-end
static void
DrawLineEx (int x1, int y1, int x2, int y2, int penType)
{
    int savX, savY;
    DrawSegment( x1, y1, &savX, &savY, PEN_NONE );
    DrawSegment( x2, y2, NULL, NULL, penType );
    DrawSegment( savX, savY, NULL, NULL, PEN_NONE );
}

// back-end
static int
GetPvScore (int index)
{
    int score = currPvInfo[ index ].score;

    if(differentialView) score = index < currLast-1 ? -currPvInfo[ index+1 ].score - score : 0;
    if( index & 1 ) score = -score; /* Flip score for black */

    return score;
}

char *
MakeEvalTitle (char *title)
{
    int score, depth;
    static char buf[MSG_SIZ];

    if( currCurrent <0 ) return title; // currCurrent = -1 crashed WB on start without ini file!
    score = currPvInfo[ currCurrent ].score;
    depth = currPvInfo[ currCurrent ].depth;

    if( depth <=0 ) return title;
    if( currCurrent & 1 ) score = -score; /* Flip score for black */
    snprintf(buf, MSG_SIZ, "%s {%d: %s%.2f/%-2d %d}", title, currCurrent/2+1,
				score>0 ? "+" : " ", score/100., depth, (currPvInfo[currCurrent].time+50)/100);

    return buf;
}

// back-end
/*
    For a centipawn value, this function returns the height of the corresponding
    histogram, centered on the reference axis.

    Note: height can be negative!
*/
static int
GetValueY (int value)
{
    if( value < -range*700 ) value = -range*700;
    if( value > +range*700 ) value = +range*700;
    if(value > 100*range)  value += (appData.zoom - 1)*100*range; else
    if(value < -100*range) value -= (appData.zoom - 1)*100*range; else
	value *= appData.zoom;
    return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / ((1200. + 200.*appData.zoom)*range));
}

// the brush selection is made part of the DrawLine, by passing a style argument
// the wrapper for doing the text output makes this back-end
static void
DrawAxisSegmentHoriz (int value, Boolean drawValue)
{
    int y = GetValueY( range*value*100 );

    if( drawValue ) {
        char buf[MSG_SIZ], *b = buf;

        if( value > 0 ) *b++ = '+';
	sprintf(b, "%d", range*value);

	DrawEvalText(buf, strlen(buf), y);
    }
    // [HGM] counts on DrawEvalText to have select transparent background for dotted line!
    DrawLine( MarginX, y, MarginX + MarginW, y, PEN_BLACK ); // Y-axis tick marks
    DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y, PEN_DOTTED ); // hor grid
}

// The DrawLines again must select their own brush.
// the initial brush selection is useless? BkMode needed for dotted line and text
static void
DrawAxis ()
{
    int cy = nHeightPB / 2, space = nHeightPB/(6 + appData.zoom);

    DrawAxisSegmentHoriz( +5, TRUE );
    DrawAxisSegmentHoriz( +3, space >= 20 );
    DrawAxisSegmentHoriz( +1, space >= 20 && space*appData.zoom >= 40 );
    DrawAxisSegmentHoriz(  0, TRUE );
    DrawAxisSegmentHoriz( -1, space >= 20 && space*appData.zoom >= 40 );
    DrawAxisSegmentHoriz( -3, space >= 20 );
    DrawAxisSegmentHoriz( -5, TRUE );

    DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy, PEN_BLACK ); // x-axis
    DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH, PEN_BLACK ); // y-axis
}

// back-end
static void
DrawHistogram (int x, int y, int width, int value, int side)
{
    int left, top, right, bottom;

    if( value > -appData.evalThreshold*range && value < +appData.evalThreshold*range ) return;

    left = x;
    right = left + width + 1;

    if( value > 0 ) {
        top = GetValueY( value );
        bottom = y+1;
    }
    else {
        top = y;
        bottom = GetValueY( value ) + 1;
    }


    if( width == MIN_HIST_WIDTH ) {
        right--;
        DrawRectangle( left, top, right, bottom, side, FILLED );
    }
    else {
        DrawRectangle( left, top, right, bottom, side, OPEN );
    }
}

// back-end
static void
DrawSeparator (int index, int x)
{
    if( index > 0 ) {
        if( index == currCurrent ) {
            DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_BLUEDOTTED );
        }
        else if( (index % 20) == 0 ) {
            DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_DOTTED );
        }
    }
}

// made back-end by replacing MoveToEx and LineTo by DrawSegment
/* Actually draw histogram as a diagram, cause there's too much data */
static void
DrawHistogramAsDiagram (int cy, int paint_width, int hist_count)
{
    double step;
    int i;

    /* Rescale the graph every few moves (as opposed to every move) */
    hist_count -= hist_count % 8;
    hist_count += 8;
    hist_count /= 2;

    step = (double) paint_width / (hist_count + 1);

    for( i=0; i<2; i++ ) {
        int index = currFirst;
        int side = (currCurrent + i + 1) & 1; /* Draw current side last */
        double x = MarginX + MarginW;

        if( (index & 1) != side ) {
            x += step / 2;
            index++;
        }

        DrawSegment( (int) x, cy, NULL, NULL, PEN_NONE );

        index += 2;

        while( index < currLast ) {
            x += step;

            DrawSeparator( index, (int) x );

            /* Extend line up to current point */
            if( currPvInfo[index].depth > 0 ) {
	      DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, (side==0 ? PEN_BOLDWHITE: PEN_BOLDBLACK) );
            }

            index += 2;
        }
    }
}

// back-end, delete pen selection
static void
DrawHistogramFull (int cy, int hist_width, int hist_count)
{
    int i;

//    SelectObject( hdcPB, GetStockObject(BLACK_PEN) );

    for( i=0; i<hist_count; i++ ) {
        int index = currFirst + i;
        int x = MarginX + MarginW + index * hist_width;

        /* Draw a separator every 10 moves */
        DrawSeparator( index, x );

        /* Draw histogram */
        if( currPvInfo[i].depth > 0 ) {
            DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );
        }
    }
}

typedef struct {
    int cy;
    int hist_width;
    int hist_count;
    int paint_width;
} VisualizationData;

// back-end
static Boolean
InitVisualization (VisualizationData *vd)
{
    Boolean result = FALSE;

    vd->cy = nHeightPB / 2;
    vd->hist_width = MIN_HIST_WIDTH;
    vd->hist_count = currLast - currFirst;
    vd->paint_width = nWidthPB - MarginX - 2*MarginW;

    if( vd->hist_count > 0 ) {
        result = TRUE;

        /* Compute width */
        vd->hist_width = vd->paint_width / vd->hist_count;

        if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;

        vd->hist_width -= vd->hist_width % 2;
    }

    return result;
}

// back-end
static void
DrawHistograms ()
{
    VisualizationData vd;
    int i; double step = 1;

    if( InitVisualization( &vd ) ) {
        if( vd.hist_width < MIN_HIST_WIDTH ) {
            DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );
            step = 0.5*vd.paint_width / (((vd.hist_count | 7) + 1)/2 + 1.);
        }
        else {
            DrawHistogramFull( vd.cy, step = vd.hist_width, vd.hist_count );
        }
    }
    if(!differentialView) return;
    differentialView = 0;
    DrawSegment( MarginX + MarginW, vd.cy, NULL, NULL, PEN_NONE );
    for( i=0; i<vd.hist_count; i++ ) {
        int index = currFirst + i;
        int x = MarginX + MarginW + index * step + step/2;
        DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, PEN_ANY );
    }
    differentialView = 1;
}

// back-end
int
GetMoveIndexFromPoint (int x, int y)
{
    int result = -1;
    int start_x = MarginX + MarginW;
    VisualizationData vd;

    if( x >= start_x && InitVisualization( &vd ) ) {
        /* Almost an hack here... we duplicate some of the paint logic */
        if( vd.hist_width < MIN_HIST_WIDTH ) {
            double step;

            vd.hist_count -= vd.hist_count % 8;
            vd.hist_count += 8;
            vd.hist_count /= 2;

            step = (double) vd.paint_width / (vd.hist_count + 1);
            step /= 2;

            result = (int) (0.5 + (double) (x - start_x) / step);
        }
        else {
            result = (x - start_x) / vd.hist_width;
        }
    }

    if( result >= currLast ) {
        result = -1;
    }

    return result;
}

// init and display part split of so they can be moved to front end
void
PaintEvalGraph (void)
{
    VariantClass v = gameInfo.variant;
    range = (gameInfo.holdingsWidth && v != VariantSuper && v != VariantGreat && v != VariantSChess) ? 2 : 1; // [HGM] double range in drop games
    /* Draw */
    DrawRectangle(0, 0, nWidthPB, nHeightPB, 2, FILLED);
    DrawAxis();
    DrawHistograms();
}
