/*
 * Copyright (C) 2002-2004 Joern Thyssen <jthyssen@dk.ibm.com>
 * Copyright (C) 2003-2019 the AUTHORS
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 *
 * $Id: bearoff.c,v 1.109 2022/07/26 21:16:44 plm Exp $
 */

#include "config.h"
/*must be first here because of strange warning from mingw */
#include "multithread.h"

#include "backgammon.h"

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "bearoffgammon.h"
#include "positionid.h"

#include <glib/gstdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define HEURISTIC_C 15
#define HEURISTIC_P 6

static int
setGammonProb(const TanBoard anBoard, unsigned int bp0, unsigned int bp1, float *g0, float *g1)
{
    int i;
    unsigned short int prob[32];

    unsigned int tot0 = 0;
    unsigned int tot1 = 0;

    for (i = 5; i >= 0; --i) {
        tot0 += anBoard[0][i];
        tot1 += anBoard[1][i];
    }

    {
        g_assert(tot0 == 15 || tot1 == 15);
    }

    *g0 = 0.0;
    *g1 = 0.0;

    if (tot0 == 15) {
        struct GammonProbs *gp = getBearoffGammonProbs(anBoard[0]);
        double make[3];

        if (BearoffDist(pbc1, bp1, NULL, NULL, NULL, prob, NULL))
            return -1;

        make[0] = gp->p0 / 36.0;
        make[1] = (make[0] + gp->p1 / (36.0 * 36.0));
        make[2] = (make[1] + gp->p2 / (36.0 * 36.0 * 36.0));

        *g1 = (float) ((prob[1] / 65535.0) +
                       (1 - make[0]) * (prob[2] / 65535.0) +
                       (1 - make[1]) * (prob[3] / 65535.0) + (1 - make[2]) * (prob[4] / 65535.0));
    }

    if (tot1 == 15) {
        struct GammonProbs *gp = getBearoffGammonProbs(anBoard[1]);
        double make[3];

        if (BearoffDist(pbc1, bp0, NULL, NULL, NULL, prob, NULL))
            return -1;

        make[0] = gp->p0 / 36.0;
        make[1] = (make[0] + gp->p1 / (36.0 * 36.0));
        make[2] = (make[1] + gp->p2 / (36.0 * 36.0 * 36.0));

        *g0 = (float) ((prob[1] / 65535.0) * (1 - make[0]) + (prob[2] / 65535.0) * (1 - make[1])
                       + (prob[3] / 65535.0) * (1 - make[2]));
    }
    return 0;
}


static void
AverageRolls(const float arProb[32], float *ar)
{
    float sx;
    float sx2;
    int i;

    sx = sx2 = 0.0f;

    for (i = 1; i < 32; i++) {
        float p = (float) i * arProb[i];
        sx += p;
        sx2 += (float) i * p;
    }

    ar[0] = sx;
    ar[1] = sqrtf(sx2 - sx * sx);
}

/* Make a plausible bearoff move (used to create approximate bearoff database). */
static unsigned int
HeuristicBearoff(unsigned int anBoard[6], const unsigned int anRoll[2])
{
    unsigned int i,             /* current die being played */
     c,                         /* number of dice to play */
     nMax,                      /* highest occupied point */
     anDice[4], j, iSearch, nTotal;
    int n;                      /* point to play from */

    if (anRoll[0] == anRoll[1]) {
        /* doubles */
        anDice[0] = anDice[1] = anDice[2] = anDice[3] = anRoll[0];
        c = 4;
    } else {
        /* non-doubles */
        g_assert(anRoll[0] > anRoll[1]);

        anDice[0] = anRoll[0];
        anDice[1] = anRoll[1];
        c = 2;
    }

    for (i = 0; i < c; i++) {
        for (nMax = 5; nMax > 0; nMax--) {
            if (anBoard[nMax])
                break;
        }

        if (!anBoard[nMax])
            /* finished bearoff */
            break;

        do {
            if (anBoard[anDice[i] - 1]) {
                /* bear off exactly */
                n = (int) anDice[i] - 1;
                break;
            }

            if (anDice[i] - 1 > nMax) {
                /* bear off highest chequer */
                n = (int) nMax;
                break;
            }

            nTotal = anDice[i] - 1;
            for (n = -1, j = i + 1; j < c; j++) {
                nTotal += anDice[j];
                if (nTotal < 6 && anBoard[nTotal]) {
                    /* there's a chequer we can bear off with subsequent dice;
                     * do it */
                    n = (int) nTotal;
                    break;
                }
            }
            if (n >= 0)
                break;

            for (n = -1, iSearch = anDice[i]; iSearch <= nMax; iSearch++) {
                if (anBoard[iSearch] >= 2 &&    /* at least 2 on source point */
                    !anBoard[iSearch - anDice[i]] &&    /* dest empty */
                    (n == -1 || anBoard[iSearch] > anBoard[n]))
                    n = (int) iSearch;
            }
            if (n >= 0)
                break;

            /* find the point with the most on it (or least on dest) */
            for (iSearch = anDice[i]; iSearch <= nMax; iSearch++)
                if (n == -1 || anBoard[iSearch] > anBoard[n] ||
                    (anBoard[iSearch] == anBoard[n] &&
                     anBoard[iSearch - anDice[i]] < anBoard[(unsigned int) n - anDice[i]]))
                    n = (int) iSearch;

            g_assert(n >= 0);
        } while (n < 0);        /* Dummy loop to remove goto's */

        g_assert(anBoard[n]);
        anBoard[n]--;

        if (n >= (int) anDice[i])
            anBoard[n - (int) anDice[i]]++;
    }

    return PositionBearoff(anBoard, HEURISTIC_P, HEURISTIC_C);
}

static void
GenerateBearoff(unsigned char *p, unsigned int nId)
{
    unsigned int anRoll[2], anBoard[6], aProb[32] = { 0 };
    unsigned int i, iBest;

    for (anRoll[0] = 1; anRoll[0] <= 6; anRoll[0]++)
        for (anRoll[1] = 1; anRoll[1] <= anRoll[0]; anRoll[1]++) {
            PositionFromBearoff(anBoard, nId, HEURISTIC_P, HEURISTIC_C);
            iBest = HeuristicBearoff(anBoard, anRoll);

            g_assert(iBest < nId);

            if (anRoll[0] == anRoll[1])
                for (i = 0; i < 31; i++)
                    aProb[i + 1] += p[(iBest << 6) | (i << 1)] + (p[(iBest << 6) | (i << 1) | 1] << 8);
            else
                for (i = 0; i < 31; i++)
                    aProb[i + 1] += (p[(iBest << 6) | (i << 1)] +
                                     ((unsigned int) p[(iBest << 6) | (i << 1) | 1] << 8)) << 1;
        }

    for (i = 0; i < 32; i++) {
        unsigned short us = (unsigned short) ((aProb[i] + 18) / 36);

        p[(nId << 6) | (i << 1)] = us & 0xFF;
        p[(nId << 6) | (i << 1) | 1] = us >> 8;
    }
}

static unsigned char *
HeuristicDatabase(void (*pfProgress) (unsigned int))
{
    unsigned char *pm = malloc(40 + 54264 * 64);
    unsigned char *p;
    unsigned int i;

    if (!pm)
        return NULL;

    p = pm + 40;
    p[0] = p[1] = 0xFF;
    for (i = 2; i < 64; i++)
        p[i] = 0;

    for (i = 1; i < 54264; i++) {
        GenerateBearoff(p, i);
        if (pfProgress && !(i % 1000))
            pfProgress(i);
    }

    return pm;
}


static void
ReadBearoffFile(const bearoffcontext * pbc, unsigned int offset, unsigned char *buf, unsigned int nBytes)
{
    MT_Exclusive();

    if ((fseek(pbc->pf, (long) offset, SEEK_SET) < 0) || (fread(buf, 1, nBytes, pbc->pf) < nBytes)) {
        if (errno)
            perror(_("bearoff database"));
        else
            fprintf(stderr, _("Error reading bearoff database"));

        memset(buf, 0, nBytes);
        return;
    }

    MT_Release();
}

/* BEAROFF_GNUBG: read two sided bearoff database */
static void
ReadTwoSidedBearoff(const bearoffcontext * pbc, const unsigned int iPos, float ar[4], unsigned short int aus[4])
{
    unsigned int i, k = (pbc->fCubeful) ? 4 : 1;
    unsigned char ac[8];
    unsigned char *pc = NULL;

    if (pbc->p)
        pc = pbc->p + 40 + 2 * iPos * k;
    else {
        ReadBearoffFile(pbc, 40 + 2 * iPos * k, ac, k * 2);
        pc = ac;
    }
    /* add to cache */

    for (i = 0; i < k; ++i) {
        unsigned short int us = pc[2 * i] | (unsigned short) (pc[2 * i + 1] << 8);

        if (aus)
            aus[i] = us;
        if (ar)
            ar[i] = us / 32767.5f - 1.0f;
    }
}

extern int
BearoffCubeful(const bearoffcontext * pbc, const unsigned int iPos, float ar[4], unsigned short int aus[4])
{
    g_return_val_if_fail(pbc, -1);
    g_return_val_if_fail(pbc->fCubeful, -1);

    ReadTwoSidedBearoff(pbc, iPos, ar, aus);
    return 0;
}


static int
BearoffEvalTwoSided(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{

    unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
    unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
    unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
    unsigned int iPos = nUs * n + nThem;
    float ar[4];

    ReadTwoSidedBearoff(pbc, iPos, ar, NULL);

    memset(arOutput, 0, 5 * sizeof(float));
    arOutput[OUTPUT_WIN] = ar[0] / 2.0f + 0.5f;

    return 0;

}


static int
ReadHypergammon(const bearoffcontext * pbc, const unsigned int iPos, float arOutput[NUM_OUTPUTS], float arEquity[4])
{

    unsigned char ac[28];
    unsigned char *pc = NULL;
    unsigned int us;
    int i;
    const int x = 28;

    if (pbc->p)
        pc = pbc->p + 40 + x * iPos;
    else {
        ReadBearoffFile(pbc, 40 + x * iPos, ac, x);
        pc = ac;
    }

    if (arOutput)
        for (i = 0; i < NUM_OUTPUTS; ++i) {
            us = pc[3 * i] | (pc[3 * i + 1]) << 8 | (pc[3 * i + 2]) << 16;
            arOutput[i] = (float) us / 16777215.0f;
        }

    if (arEquity)
        for (i = 0; i < 4; ++i) {
            us = pc[15 + 3 * i] | (pc[15 + 3 * i + 1]) << 8 | (pc[15 + 3 * i + 2]) << 16;
            arEquity[i] = ((float) us / 16777215.0f - 0.5f) * 6.0f;
        }

    return 0;

}


static int
BearoffEvalOneSided(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
    int i, j;
    float aarProb[2][32];
    float aarGammonProb[2][32];
    float r;
    unsigned int anOn[2] = { 0 };
    unsigned int an[2];
    float ar[2][4];

    /* get bearoff probabilities */

    for (i = 0; i < 2; ++i) {

        an[i] = PositionBearoff(anBoard[i], pbc->nPoints, pbc->nChequers);
        if (BearoffDist(pbc, an[i], aarProb[i], aarGammonProb[i], ar[i], NULL, NULL))
            return -1;
    }

    /* calculate winning chance */

    r = 0.0;
    for (i = 0; i < 32; ++i)
        for (j = i; j < 32; ++j)
            r += aarProb[1][i] * aarProb[0][j];

    arOutput[OUTPUT_WIN] = r;

    /* calculate gammon chances */

    for (i = 0; i < 2; ++i)
        for (j = 0; j < 25; ++j)
            anOn[i] += anBoard[i][j];

    if (anOn[0] == 15 || anOn[1] == 15) {

        if (pbc->fGammon) {

            /* my gammon chance: I'm out in i rolls and my opponent isn't inside
             * home quadrant in less than i rolls */

            r = 0;
            for (i = 0; i < 32; i++)
                for (j = i; j < 32; j++)
                    r += aarProb[1][i] * aarGammonProb[0][j];

            arOutput[OUTPUT_WINGAMMON] = r;

            /* opp gammon chance */

            r = 0;
            for (i = 0; i < 32; i++)
                for (j = i + 1; j < 32; j++)
                    r += aarProb[0][i] * aarGammonProb[1][j];

            arOutput[OUTPUT_LOSEGAMMON] = r;

        } else {

            if (setGammonProb(anBoard, an[0], an[1], &arOutput[OUTPUT_LOSEGAMMON], &arOutput[OUTPUT_WINGAMMON]))
                return -1;

        }
    } else {
        /* no gammons possible */
        arOutput[OUTPUT_WINGAMMON] = 0.0f;
        arOutput[OUTPUT_LOSEGAMMON] = 0.0f;
    }


    /* no backgammons possible */

    arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f;
    arOutput[OUTPUT_WINBACKGAMMON] = 0.0f;

    return 0;
}


extern int
BearoffHyper(const bearoffcontext * pbc, const unsigned int iPos, float arOutput[], float arEquity[])
{

    return ReadHypergammon(pbc, iPos, arOutput, arEquity);

}


static int
BearoffEvalHypergammon(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{

    unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
    unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
    unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
    unsigned int iPos = nUs * n + nThem;

    return ReadHypergammon(pbc, iPos, arOutput, NULL);
}

extern int
BearoffEval(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
    g_return_val_if_fail(pbc, 0);

    switch (pbc->bt) {
    case BEAROFF_TWOSIDED:
        return BearoffEvalTwoSided(pbc, anBoard, arOutput);
    case BEAROFF_ONESIDED:
        return BearoffEvalOneSided(pbc, anBoard, arOutput);
    case BEAROFF_HYPERGAMMON:
        return BearoffEvalHypergammon(pbc, anBoard, arOutput);
    case BEAROFF_INVALID:
    default:
        g_warning(_("Invalid bearoff database type"));
        g_assert_not_reached();
        return 0;
    }
}

extern void
BearoffStatus(const bearoffcontext * pbc, char *sz)
{
    char buf[256];

    if (!pbc || (pbc->bt != BEAROFF_TWOSIDED && pbc->bt != BEAROFF_ONESIDED && pbc->bt != BEAROFF_HYPERGAMMON))
        return;

    /* Title */
    if (pbc->bt == BEAROFF_HYPERGAMMON) {
        if (pbc->p)
            sprintf(buf, _("In memory 2-sided exact %u-chequer Hypergammon database evaluator"), pbc->nChequers);
        else
            sprintf(buf, _("On disk 2-sided exact %u-chequer Hypergammon database evaluator"), pbc->nChequers);

    } else {
        if (pbc->p)
            sprintf(buf, _("In memory %u-sided bearoff database evaluator"), pbc->bt);
        else
            sprintf(buf, _("On disk %u-sided bearoff database evaluator"), pbc->bt);

    }
    sz += sprintf(sz, " * %s\n", buf);

    sz += sprintf(sz, "   - %s\n", _("generated by GNU Backgammon"));

    sprintf(buf, _("up to %u chequers on %u points (%u positions) per player"), pbc->nChequers, pbc->nPoints,
            Combination(pbc->nChequers + pbc->nPoints, pbc->nPoints));
    sz += sprintf(sz, "   - %s\n", buf);

    switch (pbc->bt) {
    case BEAROFF_TWOSIDED:
        sz += sprintf(sz, "   - %s\n", pbc->fCubeful ? _("database includes both cubeful and cubeless equities")
                      : _("cubeless database"));
        break;

    case BEAROFF_ONESIDED:
        if (pbc->fND)
            sz += sprintf(sz, "   - %s\n", _("distributions are approximated with a normal distribution"));
        if (pbc->fHeuristic)
            sz += sprintf(sz, "   - %s\n", _("with heuristic moves"));

        sz += sprintf(sz, "   - %s\n", pbc->fGammon ? _("database includes gammon distributions")
                      : _("database does not include gammon distributions"));
        break;
    case BEAROFF_HYPERGAMMON:
    case BEAROFF_INVALID:
    default:
        break;
    }
    sprintf(sz, "\n");
}

static int
BearoffDumpTwoSided(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
    unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
    unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
    unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
    unsigned int iPos = nUs * n + nThem;
    float ar[4];
    const char *aszEquity[] = {
        N_("Cubeless equity"),
        N_("Owned cube"),
        N_("Centered cube"),
        N_("Opponent owns cube")
    };

    sprintf(sz + strlen(sz), "%19s %14s\n%s %12u  %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);

    ReadTwoSidedBearoff(pbc, iPos, ar, NULL);

    if (pbc->fCubeful)
        for (int i = 0; i < 4; ++i)
            sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[i]), ar[i]);
    else
        sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[0]), 2.0f * ar[0] - 1.0f);

    strcat(sz, "\n");

    return 0;

}


static int
BearoffDumpOneSided(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
    unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
    unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
    float ar[2][4];
    unsigned int i;
    float aarProb[2][32], aarGammonProb[2][32];
    int f0, f1, f2, f3;
    unsigned int anPips[2];
    const float x = (2 * 3 + 3 * 4 + 4 * 5 + 4 * 6 + 6 * 7 +
                     5 * 8 + 4 * 9 + 2 * 10 + 2 * 11 + 1 * 12 + 1 * 16 + 1 * 20 + 1 * 24) / 36.0f;


    if (BearoffDist(pbc, nUs, aarProb[0], aarGammonProb[0], ar[0], NULL, NULL))
        return -1;
    if (BearoffDist(pbc, nThem, aarProb[1], aarGammonProb[1], ar[1], NULL, NULL))
        return -1;

    sz += sprintf(sz, "%19s %14s\n%s %12u  %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);

    sz += sprintf(sz, "%s \t\t\t\t%s\n", _("Bearing off"), _("Bearing at least one chequer off"));
    sz += sprintf(sz, "%s\t%s\t%s\t%s\t%s\n", _("Rolls"), _("Player"), _("Opponent"), _("Player"), _("Opponent"));

    f0 = f1 = f2 = f3 = FALSE;

    for (i = 0; i < 32; i++) {

        if (aarProb[0][i] > 0.0f)
            f0 = TRUE;

        if (aarProb[1][i] > 0.0f)
            f1 = TRUE;

        if (aarGammonProb[0][i] > 0.0f)
            f2 = TRUE;

        if (aarGammonProb[1][i] > 0.0f)
            f3 = TRUE;

        if (f0 || f1 || f2 || f3) {
            if (f0 && f1 && ((f2 && f3) || !pbc->fGammon) &&
                aarProb[0][i] == 0.0f && aarProb[1][i] == 0.0f &&
                ((aarGammonProb[0][i] == 0.0f && aarGammonProb[1][i] == 0.0f) || !pbc->fGammon))
                break;

            sprintf(sz = sz + strlen(sz),
                    "%5u\t%7.3f\t%7.3f" "\t\t", i, aarProb[0][i] * 100.0f, aarProb[1][i] * 100.0f);

            if (pbc->fGammon)
                sprintf(sz = sz + strlen(sz),
                        "%7.3f\t%7.3f\n", aarGammonProb[0][i] * 100.0f, aarGammonProb[1][i] * 100.0f);
            else
                sprintf(sz = sz + strlen(sz), "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));


        }
    }

    sz += sprintf(sz, "\n%s\n", _("Average rolls"));
    sz += sprintf(sz, "%s\t\t\t\t%s\n", _("Bearing off"), _("Saving gammon"));
    sz += sprintf(sz, "\t%s\t%s\t%s\t%s\n", _("Player"), _("Opponent"), _("Player"), _("Opponent"));

    /* mean rolls */

    sz += sprintf(sz, "%s\t%7.3f\t%7.3f\t\t", _("Mean"), ar[0][0], ar[1][0]);

    if (pbc->fGammon)
        sz += sprintf(sz, "%7.3f\t%7.3f\n", ar[0][2], ar[1][2]);
    else
        sz += sprintf(sz, "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));

    /* std. dev */

    sz += sprintf(sz, "%s\t%7.3f\t%7.3f\t\t", _("Std dev"), ar[0][1], ar[1][1]);

    if (pbc->fGammon)
        sz += sprintf(sz, "%7.3f\t%7.3f\n", ar[0][3], ar[1][3]);
    else
        sz += sprintf(sz, "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));


    /* effective pip count */

    PipCount(anBoard, anPips);

    sz += sprintf(sz, "\n%s:\n", _("Effective pip count"));
    sz += sprintf(sz, "\t%s\t%s\n", _("Player"), _("Opponent"));
    sz += sprintf(sz, "%s\t%7.3f\t%7.3f\n%s\t%7.3f\t%7.3f\n\n", _("EPC"),
                  ar[0][0] * x, ar[1][0] * x, _("Wastage"), ar[0][0] * x - (float) anPips[1],
                  ar[1][0] * x - (float) anPips[0]);

    sprintf(sz, "%s = %5.3f * %s\n%s = %s - %s\n\n",
            _("EPC"), x, _("Average rolls"), _("Wastage"), _("EPC"), _("pips"));

    return 0;

}

static int
BearoffDumpHyper(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
    unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
    unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
    unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
    unsigned int iPos = nUs * n + nThem;
    float ar[4];
    unsigned int i;
    const char *aszEquity[] = {
        N_("Owned cube"),
        N_("Centered cube"),
        N_("Centered cube (Jacoby rule)"),
        N_("Opponent owns cube")
    };

    if (BearoffHyper(pbc, iPos, NULL, ar))
        return -1;

    sprintf(sz + strlen(sz), "%19s %14s\n%s %12u  %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);

    for (i = 0; i < 4; ++i)
        sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[i]), ar[i]);

    return 0;
}

extern int
BearoffDump(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
    g_return_val_if_fail(pbc, -1);

    switch (pbc->bt) {
    case BEAROFF_TWOSIDED:
        return BearoffDumpTwoSided(pbc, anBoard, sz);
    case BEAROFF_ONESIDED:
        return BearoffDumpOneSided(pbc, anBoard, sz);
    case BEAROFF_HYPERGAMMON:
        return BearoffDumpHyper(pbc, anBoard, sz);
    case BEAROFF_INVALID:
    default:
        g_warning(_("Invalid bearoff database type"));
        return -1;
    }
}

extern void
BearoffClose(bearoffcontext * pbc)
{
    if (!pbc)
        return;

    if (pbc->pf)
        fclose(pbc->pf);

    if (pbc->map) {
#if GLIB_CHECK_VERSION(2,22,0)
        g_mapped_file_unref(pbc->map);
#else
        g_mapped_file_free(pbc->map);
#endif
        pbc->p = NULL;
    }

    if (pbc->p)
        free(pbc->p);

    if (pbc->szFilename)
        g_free(pbc->szFilename);

    g_free(pbc);
}

static unsigned char *
ReadIntoMemory(bearoffcontext * pbc)
{
    GError *error = NULL;
    pbc->map = g_mapped_file_new(pbc->szFilename, FALSE, &error);
    if (!pbc->map) {
        g_printerr(_("%s: Failed to map bearoff database %s\n"), pbc->szFilename, error->message);
        g_error_free(error);
        return NULL;
    }
    pbc->p = (unsigned char *) g_mapped_file_get_contents(pbc->map);
    return pbc->p;
}

/*
 * Check whether this is a exact bearoff file 
 *
 * The first long must be 73457356
 * The second long must be 100
 *
 */

static inline unsigned int
MakeInt(unsigned char a, unsigned char b, unsigned char c, unsigned char d)
{
    return (a | b << 8 | c << 16 | d << 24);
}

static void
InvalidDb(bearoffcontext * pbc)
{
    if (errno) {
        const char *fn = pbc->szFilename ? pbc->szFilename : "";
        g_printerr("%s(%s): %s\n", _("Bearoff Database"), fn, g_strerror(errno));
    }
    BearoffClose(pbc);
}

/*
 * Initialise bearoff database
 *
 * Input:
 *   szFilename: the filename of the database to open
 *
 * Returns:
 *   pointer to bearoff context on succes; NULL on error
 *
 * Garbage collect:
 *   caller must free returned pointer if not NULL.
 *
 */
extern bearoffcontext *
BearoffInit(const char *szFilename, const unsigned int bo, void (*p) (unsigned int))
{
    bearoffcontext *pbc;
    char sz[41];

    pbc = g_new0(bearoffcontext, 1);

    if (bo & BO_HEURISTIC) {
        pbc->bt = BEAROFF_ONESIDED;
        pbc->nPoints = HEURISTIC_P;
        pbc->nChequers = HEURISTIC_C;
        pbc->fHeuristic = TRUE;
        pbc->p = HeuristicDatabase(p);
        return pbc;
    }

    errno = 0;

    if (!szFilename || !*szFilename) {
        g_printerr("%s\n", _("No database filename provided"));
        InvalidDb(pbc);
        return NULL;
    }
    pbc->szFilename = g_strdup(szFilename);

    if (!g_file_test(szFilename, G_FILE_TEST_IS_REGULAR)) {
        /* fail silently */
        errno = 0;
        InvalidDb(pbc);
        return NULL;
    }


    if ((pbc->pf = g_fopen(szFilename, "rb")) == 0) {
        g_printerr("%s\n", _("Invalid or nonexistent database"));
        InvalidDb(pbc);
        return NULL;
    }
    /* 
     * Read header bearoff file
     */

    /* read header */

    if (fread(sz, 1, 40, pbc->pf) < 40) {
        g_printerr("%s\n", _("Database read failed"));
        InvalidDb(pbc);
        return NULL;
    }

    /* detect bearoff program */

    if (strncmp(sz, "gnubg", 5) != 0) {
        g_printerr("%s\n", _("Unknown bearoff database"));
        InvalidDb(pbc);
        return NULL;
    }

    /* one sided or two sided? */

    if (!strncmp(sz + 6, "TS", 2))
        pbc->bt = BEAROFF_TWOSIDED;
    else if (!strncmp(sz + 6, "OS", 2))
        pbc->bt = BEAROFF_ONESIDED;
    else if (*(sz + 6) == 'H')
        pbc->bt = BEAROFF_HYPERGAMMON;
    else {
        g_printerr("%s: %s\n (%s: '%2s')\n", szFilename, _("incomplete bearoff database"), _("illegal bearoff type"),
                   sz + 6);
        InvalidDb(pbc);
        return NULL;
    }

    if (((bo & BO_MUST_BE_ONE_SIDED) && (pbc->bt != BEAROFF_ONESIDED))
        || ((bo & BO_MUST_BE_TWO_SIDED) && (pbc->bt != BEAROFF_TWOSIDED))) {
        g_printerr("%s: %s\n (%s: '%2s')\n", szFilename, _("incorrect bearoff database"), _("wrong bearoff type"),
                   sz + 6);
        InvalidDb(pbc);
        return NULL;
    }

    if (pbc->bt == BEAROFF_TWOSIDED || pbc->bt == BEAROFF_ONESIDED) {

        /* normal onesided or twosided bearoff database */

        /* number of points */

        pbc->nPoints = (unsigned) atoi(sz + 9);
        if (pbc->nPoints < 1 || pbc->nPoints >= 24) {
            g_printerr("%s: %s\n (%s: %u)\n", szFilename, _("incomplete bearoff database"),
                       _("illegal number of points"), pbc->nPoints);
            InvalidDb(pbc);
            return NULL;
        }

        /* number of chequers */

        pbc->nChequers = (unsigned) atoi(sz + 12);
        if (pbc->nChequers < 1 || pbc->nChequers > 15) {
            g_printerr("%s: %s\n (%s: %u)", szFilename, _("incomplete bearoff database"),
                       _("illegal number of chequers"), pbc->nChequers);
            InvalidDb(pbc);
            return NULL;
        }

    } else {

        /* hypergammon database */

        pbc->nPoints = 25;
        pbc->nChequers = (unsigned) atoi(sz + 7);

    }
    switch (pbc->bt) {
    case BEAROFF_TWOSIDED:
        /* options for two-sided dbs */
        pbc->fCubeful = atoi(sz + 15);
        break;
    case BEAROFF_ONESIDED:
        /* options for one-sided dbs */
        pbc->fGammon = atoi(sz + 15);
        pbc->fCompressed = atoi(sz + 17);
        pbc->fND = atoi(sz + 19);
        break;
    case BEAROFF_HYPERGAMMON:
    case BEAROFF_INVALID:
    default:
        break;
    }

    /* 
     * read database into memory if requested 
     */

    if (bo & BO_IN_MEMORY) {
        fclose(pbc->pf);
        pbc->pf = NULL;
        if ((ReadIntoMemory(pbc) == NULL))
            if ((pbc->pf = g_fopen(szFilename, "rb")) == 0) {
                g_printerr("%s\n", _("Invalid or nonexistent database"));
                InvalidDb(pbc);
                return NULL;
            }
    }

    return pbc;
}

extern float
fnd(const float x, const float mu, const float sigma)
{

    const float epsilon = 1.0e-7f;

    if (sigma <= epsilon)
        /* dirac delta function */
        return (fabsf(mu - x) < epsilon) ? 1.0f : 0.0f;
    else {

        float xm = (x - mu) / sigma;

        return 1.0f / (sigma * sqrtf(2.0f * F_PI)) * expf(-xm * xm / 2.0f);

    }

}



static int
ReadBearoffOneSidedND(const bearoffcontext * pbc,
                      const unsigned int nPosID,
                      float arProb[32], float arGammonProb[32],
                      float *ar, unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{

    unsigned char ac[16];
    float arx[4];
    int i;
    float r;

    ReadBearoffFile(pbc, 40 + nPosID * 16, ac, 16);

    memcpy(arx, ac, 16);

    if (arProb || ausProb)
        for (i = 0; i < 32; ++i) {
            r = fnd((float) i, arx[0], arx[1]);
            if (arProb)
                arProb[i] = r;
            if (ausProb)
                ausProb[i] = (unsigned short) (r * 65535.0f);
        }



    if (arGammonProb || ausGammonProb)
        for (i = 0; i < 32; ++i) {
            r = fnd((float) i, arx[2], arx[3]);
            if (arGammonProb)
                arGammonProb[i] = r;
            if (ausGammonProb)
                ausGammonProb[i] = (unsigned short) (r * 65535.0f);
        }

    if (ar)
        memcpy(ar, arx, 16);

    return 0;

}


static void
AssignOneSided(float arProb[32], float arGammonProb[32],
               float ar[4],
               unsigned short int ausProb[32],
               unsigned short int ausGammonProb[32],
               const unsigned short int ausProbx[32], const unsigned short int ausGammonProbx[32])
{
    if (ausProb)
        memcpy(ausProb, ausProbx, 32 * sizeof(ausProb[0]));

    if (ausGammonProb)
        memcpy(ausGammonProb, ausGammonProbx, 32 * sizeof(ausGammonProbx[0]));

    if (ar || arProb || arGammonProb) {
        float arx[64];
        int i;

        for (i = 0; i < 32; ++i)
            arx[i] = ausProbx[i] / 65535.0f;

        for (i = 0; i < 32; ++i)
            arx[32 + i] = ausGammonProbx[i] / 65535.0f;

        if (arProb)
            memcpy(arProb, arx, 32 * sizeof(float));
        if (arGammonProb)
            memcpy(arGammonProb, arx + 32, 32 * sizeof(float));
        if (ar) {
            AverageRolls(arx, ar);
            AverageRolls(arx + 32, ar + 2);
        }
    }
}


static void
CopyBytes(unsigned short int aus[64],
          const unsigned char ac[128],
          const unsigned int nz, const unsigned int ioff, const unsigned int nzg, const unsigned int ioffg)
{

    unsigned int i, j;

    i = 0;
    memset(aus, 0, 64 * sizeof(unsigned short int));
    for (j = 0; j < nz; ++j, i += 2)
        aus[ioff + j] = ac[i] | (unsigned short int) (ac[i + 1] << 8);

    for (j = 0; j < nzg; ++j, i += 2)
        aus[32 + ioffg + j] = (unsigned short int) (ac[i] | ac[i + 1] << 8);
}

static unsigned short int *
GetDistCompressed(unsigned short int aus[64], const bearoffcontext * pbc, const unsigned int nPosID)
{
    unsigned char *puch;
    unsigned char ac[128];
    unsigned int iOffset;
    unsigned int nBytes;
    unsigned int ioff, nz, ioffg = 0, nzg = 0;
    unsigned int nPos = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
    unsigned int index_entry_size = pbc->fGammon ? 8 : 6;

    /* find offsets and no. of non-zero elements */

    if (pbc->p)
        /* database is in memory */
        puch = pbc->p + 40 + nPosID * index_entry_size;
    else {
        ReadBearoffFile(pbc, 40 + nPosID * index_entry_size, ac, index_entry_size);
        puch = ac;
    }

    /* find offset */

    iOffset = MakeInt(puch[0], puch[1], puch[2], puch[3]);

    nz = puch[4];
    ioff = puch[5];
    if (pbc->fGammon) {
        nzg = puch[6];
        ioffg = puch[7];
    }
    /* Sanity checks */

    if ((iOffset > 64 * nPos && 64 * nPos > 0) || nz > 32 || ioff > 32 || nzg > 32 || ioffg > 32) {
        fprintf(stderr, _("The bearoff file '%s' is likely to be corrupted.\n"), pbc->szFilename);
#if !defined(G_DISABLE_ASSERT)
        fprintf(stderr,
                "Offset %lu, dist size %u (offset %u), "
                "gammon dist size %u (offset %u)\n", (unsigned long) iOffset, nz, ioff, nzg, ioffg);
#endif
        g_assert_not_reached();
    }

    /* read prob + gammon probs */

    iOffset = 40                /* the header */
        + nPos * index_entry_size       /* the offset data */
        + 2 * iOffset;          /* offset to current position */

    /* read values */

    nBytes = 2 * (nz + nzg);

    /* get distribution */

    if (pbc->p)
        /* from memory */
        puch = pbc->p + iOffset;
    else {
        /* from disk */
        ReadBearoffFile(pbc, iOffset, ac, nBytes);
        puch = ac;

    }

    CopyBytes(aus, puch, nz, ioff, nzg, ioffg);

    return aus;
}

static unsigned short int *
GetDistUncompressed(unsigned short int aus[64], const bearoffcontext * pbc, const unsigned int nPosID)
{
    unsigned char ac[128];
    unsigned char *puch;
    unsigned int iOffset;

    /* read from file */

    iOffset = 40 + 64 * nPosID * (pbc->fGammon ? 2 : 1);

    if (pbc->p)
        /* from memory */
        puch = pbc->p + iOffset;
    else {
        /* from disk */

        ReadBearoffFile(pbc, iOffset, ac, pbc->fGammon ? 128 : 64);
        puch = ac;
    }

    CopyBytes(aus, puch, 32, 0, 32, 0);

    return aus;

}


static int
ReadBearoffOneSidedExact(const bearoffcontext * pbc, const unsigned int nPosID,
                         float arProb[32], float arGammonProb[32],
                         float ar[4], unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{
    unsigned short int aus[64];
    unsigned short int *pus = NULL;

    /* get distribution */
    if (pbc->fCompressed)
        pus = GetDistCompressed(aus, pbc, nPosID);
    else
        pus = GetDistUncompressed(aus, pbc, nPosID);

    if (!pus) {
        printf(_("Error decoding one-sided bearoff database entry; position %u\n"), nPosID);
        return -1;
    }

    AssignOneSided(arProb, arGammonProb, ar, ausProb, ausGammonProb, pus, pus + 32);

    return 0;
}

extern int
BearoffDist(const bearoffcontext * pbc, const unsigned int nPosID,
            float arProb[32], float arGammonProb[32],
            float ar[4], unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{
    g_return_val_if_fail(pbc, -1);
    g_return_val_if_fail(pbc->bt == BEAROFF_ONESIDED, -1);
    if (pbc->fND)
        return ReadBearoffOneSidedND(pbc, nPosID, arProb, arGammonProb, ar, ausProb, ausGammonProb);
    else
        return ReadBearoffOneSidedExact(pbc, nPosID, arProb, arGammonProb, ar, ausProb, ausGammonProb);
}

extern int
isBearoff(const bearoffcontext * pbc, const TanBoard anBoard)
{
    unsigned int i, nOppBack, nBack;
    unsigned int n = 0, nOpp = 0;

    if (!pbc)
        return FALSE;

    for (nOppBack = 24; nOppBack > 0; nOppBack--) {
        if (anBoard[0][nOppBack])
            break;
    }
    for (nBack = 24; nBack > 0; nBack--) {
        if (anBoard[1][nBack])
            break;
    }
    if (!anBoard[0][nOppBack] || !anBoard[1][nBack])
        /* the game is over */
        return FALSE;

    if ((nBack + nOppBack > 22) && !(pbc->bt == BEAROFF_HYPERGAMMON))
        /* contact position */
        return FALSE;

    for (i = 0; i <= nOppBack; ++i)
        nOpp += anBoard[0][i];

    for (i = 0; i <= nBack; ++i)
        n += anBoard[1][i];

    if (n <= pbc->nChequers && nOpp <= pbc->nChequers && nBack < pbc->nPoints && nOppBack < pbc->nPoints)
        return TRUE;
    else
        return FALSE;
}
