/*
    Copyright 2023 Harm Jetten

    This file is part of Moby Dam.

    Moby Dam 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.

    Moby Dam 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 Moby Dam.  If not, see <http://www.gnu.org/licenses/>.
*/

/* procweights.c: post-process pattern weights files */

#include "train.h"

bool debug_info;                 /* print extra debug info */
char eval_dirs[PATH_MAX] = ".";  /* directory/ies of evaluation table files */
char out_dir[PATH_MAX] = ".";    /* directory for output files */

u16 idx_p[IDXSZ];                /* pattern indexing tables */
u16 inv_p[IDXSZ];
double tbl_pat[PHASES][9*PATSZ]; /* floating point pattern weights */
double tbl_feat[PHASES][FEATSZ]; /* floating point feature weights */

/* compare and average the weights from two reversed/mirrored patterns */
/* e.g. 0 vs 8, 1 vs 7, etc. */
/* to make the patterns fully white/black symmetric */
/*
   -------------
   | 0 | 1 | 2 |
   -------------
   | 3 | 4 | 5 |
   -------------
   | 6 | 7 | 8 |
   -------------
*/
/* phase = the game phase */
/* pat1 = the first pattern */
/* pat2 = the second pattern */
static void equalize_weights(int phase, int pat1, int pat2)
{
    u64 wm, bm;
    u16 x1, x2;
    double w1, w2, w3;
    double *tblp = &tbl_pat[phase][0];

    for (wm = 0; wm < IDXSZ; wm++)
    {
        for (bm = 0; bm < IDXSZ; bm++)
        {
            if ((wm & bm) != 0)
            {
                continue;
            }

            x1 = 2*idx_p[wm] + idx_p[bm];
            x2 = inv_p[wm] + 2*inv_p[bm];

            w1 = tblp[pat1*PATSZ + x1];
            w2 = -tblp[pat2*PATSZ + x2];

            if (w1 == w2)
            {
                continue;
            }
            if (w1 == 0.0)
            {
                w3 = w2;
            }
            else if (w2 == 0.0)
            {
                w3 = w1;
            }
            else
            {
                w3 = (w1 + w2)/2.0;
            }

            tblp[pat1*PATSZ + x1] = w3;
            tblp[pat2*PATSZ + x2] = -w3;

            debugf("%d %d %d wm=%" PRIu64 " bm=%" PRIu64 " x1=%d x2=%d w1=%lf w2=%lf w3=%lf\n", 
                   phase, pat1, pat2, wm, bm, x1, x2, w1, w2, w3);
        }
    }
}

/* load a weights table */
/* phase = game phase */
/* file = weights file path/name */
/* file contains one floating point weight value per line */
/* returns: TRUE if successful */
static bool load_table(int phase, char *file)
{
    FILE *fp;
    char line[80];
    char *str;
    int i, n;
    double w;

    if (file == NULL)
    {
        fprintf(stderr, "error locating phase %d evaluation table\n", phase);
        return FALSE;
    }
    fp = fopen(file, "r");
    if (fp == NULL)
    {
        fprintf(stderr, "error opening %s\n", file);
        return FALSE;
    }
    for (i = 0; i < elements(tbl_pat[0]) + FEATSZ; i++)
    {
        str = fgets(line, sizeof line, fp);
        if (str == NULL)
        {
            if (i >= elements(tbl_pat[0]))
            {
                break; /* leave remaining feature weights at 0 */
            }
            fprintf(stderr, "eof error reading %s at %d\n", file, i);
            fclose(fp);
            return FALSE;
        }
        while (*str != '\0')      /* get rid of any brackets present */
        {
            if (*str == '[' || *str == ']')
            {
                *str = ' ';
            }
            str++;
        }
        n = sscanf(line, "%lf", &w);
        if (n != 1)
        {
            fprintf(stderr, "scanf error reading %s at %d\n", file, i);
            fclose(fp);
            return FALSE;
        }
        if (i < elements(tbl_pat[0]))
        {
            tbl_pat[phase][i] = w;
        }
        else
        {
            tbl_feat[phase][i - elements(tbl_pat[0])] = w;
        }
    }
    fclose(fp);
    debugf("success reading %s\n", file);
    return TRUE;
}

/* write a weights table */
/* phase = game phase */
/* file = weights file path/name */
/* returns: TRUE if successful */
static bool write_table(int phase, char *file)
{
    FILE *fp;
    int i;

    fp = fopen(file, "w");
    if (fp == NULL)
    {
        fprintf(stderr, "error opening %s for writing\n", file);
        return FALSE;
    }
    for (i = 0; i < elements(tbl_pat[0]); i++)
    {
        fprintf(fp, "%g\n", tbl_pat[phase][i]);
    }
    for (i = 0; i < FEATSZ; i++)
    {
        fprintf(fp, "%g\n", tbl_feat[phase][i]);
    }
    fclose(fp);
    debugf("success writing %s\n", file);
    return TRUE;
}

/* set up indexing tables for patterns */
static void setup_indexes(void)
{
    u64 men, pcbit, mask;
    u16 x;

    for (men = 0; men < IDXSZ; men++)
    {
        /* regular indexes */
        x = 0;
        mask = IDXSZ - 1;
        while (mask != 0)
        {
            pcbit = mask & -mask;
            mask -= pcbit;
            x = 3*x + ((men & pcbit) != 0);
        }
        idx_p[men] = x;

        /* inverse indexes */
        x = 0;
        mask = IDXSZ - 1;
        while (mask != 0)
        {
            pcbit = 1ULL << (63 ^ __builtin_clzll(mask));
            mask -= pcbit;
            x = 3*x + ((men & pcbit) != 0);
        }
        inv_p[men] = x;
    }
}

/* count the non-zero weights */
/* phase = which table to count */
void count_weights(int phase)
{       
    int i, n;

    n = 0;
    for (i = 0; i < elements(tbl_pat[0]); i++)
    {
        if (tbl_pat[phase][i] != 0.0)
        {
            n++;
        }
    }
    printf("ph%d has %d weights\n", phase, n);
}

/* read in and modify the weights files */
/* eq = do equalization */
static void process_files(bool eq)
{
    char *dbpath;
    char weightfile[PATH_MAX];
    int phase;

    for (phase = 0; phase < PHASES; phase++)
    {
        sprintf(weightfile, "eval-ph%d", phase);
        dbpath = locate_dbfile(eval_dirs, weightfile);
        if (!load_table(phase, dbpath))           /* from evaldir */
        {
            continue;
        }
        count_weights(phase);
        if (eq)
        {
            equalize_weights(phase, 0, 8);
            equalize_weights(phase, 1, 7);
            equalize_weights(phase, 2, 6);
            equalize_weights(phase, 3, 5);
            equalize_weights(phase, 4, 4);
        }
    }
    if (eq)
    {
        printf("equalization:\n");
        for (phase = 0; phase < PHASES; phase++)
        {
            count_weights(phase);
        }
    }
    for (phase = 0; phase < PHASES; phase++)
    {
        sprintf(weightfile, "%s/eval-ph%d", out_dir, phase);
        write_table(phase, weightfile);
    }
}

/* post-process pattern weights files */
int main(int argc, char *argv[])
{
    int opt;
    bool eq = FALSE;

    while (TRUE)
    {
        opt = getopt(argc, argv, "dev:o:");
        if (opt == -1) 
        {
            break;
        }
        switch (opt) 
        {
        case 'd':
            debug_info = TRUE;
            break;
        case 'e':
            eq = TRUE;
            break;
        case 'v':
            strncpy(eval_dirs, optarg, sizeof eval_dirs - 1);
            break;
        case 'o':
            strncpy(out_dir, optarg, sizeof out_dir - 1);
            break;
        default:
            printf("Usage: %s [-d] [-e] [-v evaldir] [-o outdir]\n", argv[0]);
            printf("  -d = print lots of extra debug info\n"
                   "  -e = do weight equalization from inverse/mirrored patterns\n"
                   "  -v evaldir = directory holding the input eval-ph# files\n"
                   "       (default: current directory)\n"
                   "  -o outdir = directory to hold the output eval-ph# files\n"
                   "       (default: current directory)\n"
                   "       (directory must already exist; files will be overwritten)\n");
            exit(EXIT_FAILURE);
        }
    }

    setup_indexes();

    process_files(eq);

    return EXIT_SUCCESS;
}
