/***************************************************************************
*   KBlocks, a falling blocks game for KDE                                *
*   Copyright (C) 2010 University Freiburg                                *
*                                                                         *
*   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.                                   *
***************************************************************************/
#include "KBlocksAIEvaluation.h"
#include "KBlocksAIFeature.h"
#include "KBlocksAILog.h"

/******************************************************************************
********   Aggregate  Function   **********************************************
*******************************************************************************/
struct SFeature {
    SpecialFeatureEnumeration id;
    double weight;
};
struct Feature {
    FeatureEnumeration id;
    double weight;
};

bool getDecisionFeature(const DecisionFeatureEnumeration id, KBlocksField *field)
{
    bool  result = false;

    switch (id) {
    case DF_HEIGHT_MAX:
        set_ground_line(0);
        update_board_signature(field);
        result = (getFeature(FEATURE_MAX_HEIGHT, field) > (field->getHeight() * 0.6));
        break;

    case DF_PEEKS_COUNT:
        set_ground_line(0);
        update_board_signature(field);
        result = (getFeature(FEATURE_NARROW_COUNT, field) >= 2);
        break;

    case DF_BLOCK_SCAN:
        set_ground_line(0);
        update_board_signature(field);
        break;

    case DF_DEEP_NARROW:
        set_ground_line(0);
        update_board_signature(field);
        result = ((getFeature(FEATURE_MAX_HEIGHT_DIFFERENT, field) >= 5) && (getFeature(FEATURE_NARROW_COUNT, field) == 1));
        break;

    case DF_LAYER_SCAN:
        set_ground_line(0);
        update_board_signature(field);
        result = ((getFeature(FEATURE_MAX_HEIGHT, field) >= 8) && (getFeature(FEATURE_MAX_HEIGHT_DIFFERENT, field) <= 2));
        break;

    case DF_CREATING_TETRIS:
        set_ground_line(0);
        update_board_signature(field);
        result = ((getFeature(FEATURE_HOLES_COUNT, field) <= 5) && (getFeature(FEATURE_MAX_HEIGHT, field) <= 6));
        break;

    case DF_REMOVE_TETRIS:
        set_ground_line(0);
        update_board_signature(field);
        result = (getFeature(FEATURE_NARROW_COUNT, field) == 1);
        break;

    default:
        break;
    }

    return result;
}

/*#########################################################
* SECTION ANALYSIS EVALUATION  ############################
*########################################################## */
HalfBaseEvaluation *HalfBaseEvaluation::_instance = 0;
double HalfBaseEvaluation::evaluate(KBlocksField *field)
{
    int line = getFeature(FEATURE_MAX_HEIGHT, field) - 6;
    if (line < 0) {
        line = 0;
    }
    set_ground_line(line);
    return BaseEvaluation::instance()->evaluate(field);
}
/*#########################################################
* BASIC  EVALUATION  ######################################
*########################################################## */
BaseEvaluation *BaseEvaluation::_instance = 0;
double BaseEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;
    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -0.05},
        {FEATURE_HOLES_COUNT, -2.25},
        {FEATURE_WEIGHTED_BLOCKS_COUNT, -0.005},
        {FEATURE_MAX_KONTUR_LENGTH, 0.5},
        {FEATURE_MAX_HEIGHT_DIFFERENT, -1},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1},
        {FEATURE_BLOCKS_OVER_HOLES_COUNT, -0.01},
        {FEATURE_WELLS_COUNT, -0.01},
        {FEATURE_PREDICTION_COUNT, 0.01},
        {FEATURE_NARROW_COUNT, -5},
    };
    /*
    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-1.8},
        {FEATURE_HOLES_COUNT,-2.25 },
        {FEATURE_BLOCKS_COUNT,-0.5},
        {FEATURE_AVERAGE_HEIGHT,-1},
    };
    */
    /*
    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-1.5},
        {FEATURE_HOLES_COUNT,-3.25 },
        {FEATURE_BLOCKS_COUNT,-0.005},
        {FEATURE_AVERAGE_HEIGHT,-0.7},
    };
    */
    /*
    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-2.5},
        {FEATURE_HOLES_COUNT,-1.25 },
        {FEATURE_BLOCKS_COUNT,-0.005},
        {FEATURE_AVERAGE_HEIGHT,-1},
    };
    */
    /*

    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-1.5},
        {FEATURE_HOLES_COUNT,-3.25 },
        {FEATURE_BLOCKS_COUNT,-0.005},
        {FEATURE_AVERAGE_HEIGHT,-0.7},
        {FEATURE_NARROW_COUNT,-20},
    };
    */
    /*
    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-1.5},
        {FEATURE_HOLES_COUNT,-3.25 },
        {FEATURE_BLOCKS_COUNT,-0.005},
        {FEATURE_AVERAGE_HEIGHT,-0.7},
        {FEATURE_NARROW_COUNT,-20},
        {FEATURE_MAX_HEIGHT_DIFFERENT,-3},
    };
    */
    /*
    Feature features [] =
    {
        {FEATURE_MAX_HEIGHT,-1.5},
        {FEATURE_HOLES_COUNT,-3.25 },
        {FEATURE_BLOCKS_COUNT,-0.005},
        {FEATURE_AVERAGE_HEIGHT,-0.7},
        {FEATURE_NARROW_COUNT,-20},
        {FEATURE_MAX_HEIGHT_DIFFERENT,-3},
        {FEATURE_PREDICTION_COUNT,0.005},
    };
    */
    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* WELLS  STOPPER ##########################################
*########################################################## */
WellsFillerEvaluation *WellsFillerEvaluation::_instance = 0;
double WellsFillerEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    Feature features [] = {
        {FEATURE_MAX_KONTUR_LENGTH, 1.0},
        {FEATURE_NARROW_COUNT, -1.0},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1.1},
    };

    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* HEIGHT  ELIMINIATOR #####################################
*########################################################## */
HeightKillerEvaluation *HeightKillerEvaluation::_instance = 0;
double HeightKillerEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -2.05},
        {FEATURE_HOLES_COUNT, -1.95},
        {FEATURE_WEIGHTED_BLOCKS_COUNT, -0.05},
        {FEATURE_MAX_HEIGHT_DIFFERENT, -1.0},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1},
        {FEATURE_WELLS_COUNT, -0.01},
        {FEATURE_PREDICTION_COUNT, 0.001},
    };

    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* BLOCK  ELIMINATION  #####################################
*########################################################## */
BlockRemoverEvaluation *BlockRemoverEvaluation::_instance = 0;
double BlockRemoverEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    // ### reenable the two following #if 0 comments when the features array is not empty
#if 0
    Feature features [] = {};

    unsigned int n = sizeof(features) / sizeof(Feature);
#endif

    update_board_signature(field);

#if 0
    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }
#endif

    return res;
}

/*#########################################################
* DEEP NARROW ELIMINATION  ################################
*########################################################## */
DeepNarrowRemoverEvaluation *DeepNarrowRemoverEvaluation::_instance = 0;
double DeepNarrowRemoverEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -1.05},
        {FEATURE_WEIGHTED_BLOCKS_COUNT, -0.15},
        {FEATURE_MAX_KONTUR_LENGTH, 0.5},
        {FEATURE_MAX_HEIGHT_DIFFERENT, -1},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1},
        {FEATURE_PREDICTION_COUNT, 0.001},
    };

    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* TETRIS  CREATOR  ########################################
*########################################################## */
TetrisPreparingEvaluation *TetrisPreparingEvaluation::_instance = 0;
double TetrisPreparingEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -0.15},
        {FEATURE_HOLES_COUNT, -3.95},
        {FEATURE_MAX_KONTUR_LENGTH, 1.5},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1.75},
        {FEATURE_NARROW_COUNT, 0.2},
        {FEATURE_PREDICTION_COUNT, 0.002},
    };

    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* TETRIS  REMOVER  ########################################
*########################################################## */
TetrisEliminationEvaluation *TetrisEliminationEvaluation::_instance = 0;
double TetrisEliminationEvaluation::evaluate(KBlocksField *field)
{
    double res = 0;

    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -0.75},
        {FEATURE_BLOCKS_COUNT, -2.00},
        {FEATURE_HOLES_COUNT, -1.25},
        {FEATURE_MAX_HEIGHT_DIFFERENT, -1},
        {FEATURE_AVERAGE_HEIGHT_DIFFERENT, -1},
        {FEATURE_NARROW_COUNT, -0.5},
        {FEATURE_PREDICTION_COUNT, 0.002},
    };

    unsigned int n = sizeof(features) / sizeof(Feature);

    update_board_signature(field);

    for (unsigned int i = 0; i < n; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    return res;
}

/*#########################################################
* NBS EVALUATION  #########################################
*########################################################## */
NBSEvaluation *NBSEvaluation::_instance = 0;
double NBSEvaluation::evaluate(KBlocksField *field)
{
    Feature features [] = {
        {FEATURE_MAX_HEIGHT, -62709},           //1
        {FEATURE_HOLES_COUNT, -30271},           //2
        {FEATURE_CLOSED_HOLES_COUNT, 0},           //3
        {FEATURE_MAX_HEIGHT_DIFFERENT, 35395},   //5
        {FEATURE_MAX_WELL_DEPTH, -12},           //6
        {FEATURE_WELLS_COUNT, -43810},           //7
        {FEATURE_BLOCKS_COUNT, 0},               //9
        {FEATURE_WEIGHTED_BLOCKS_COUNT, -4041},   //10
        {FEATURE_ROW_TRANSITION_COUNT, -44262},   //11
        {FEATURE_COLUMN_TRANSITION_COUNT, -5832}, //12
    };

    SFeature sfeatures [] = {
        {FEATURE_REMOVE_LINES, 13344},   //4
        {FEATURE_LANDING_HEIGHT, 0},       //8
    };

    unsigned int n1 = sizeof(features) / sizeof(Feature);

    unsigned int n2 = sizeof(sfeatures) / sizeof(SFeature);

    update_board_signature(field);

    double res = 0;

    for (unsigned int i = 0; i < n1; i++) {
        res += getFeature(features[i].id, field) * features[i].weight;
    }

    for (unsigned int i = 0; i < n2; i++) {
        res += getSpecialFeature(sfeatures[i].id, field, mpField, mpPiece) * sfeatures[i].weight;
    }

    return res;
}
