#ifndef __CIRNO_H__
#define __CIRNO_H__

#include <vector>
#include <map>
#include <iostream>
#include "evaluator.h"

using namespace std;

/**
 * おバカ  (とりあえず言い訳ができるように、笑)
 * 
 * 冗談はさておき、評価関数を生成するクラス。
 * 6x6の大きさでも状態量は馬鹿にならないので、
 * タイリングをすることによって状態圧縮をかけることにする。
 * 
 * タイリングの方法は
 * 縦、横、右斜め、左斜めの1列を一タイルとする。
 * このようにした理由はオセロの特徴
 * (石を置いた上下左右斜めの列しか石は変化しない)
 * がなんらかの形で評価関数の精度向上につながるかもしれないと考えたから。
 * 
 * ちなみに1タイルに入る評価関数の値は標準では
 * 終局状態における(黒石の数)-(白石の数)の推定値とする。
 * 
 * あとで分散値を使って解析したくなるかもしれないので、
 * いちおう評価関数を鍛える際には分散も同時に計算しておく。
 * 
 * それぞれのタイルで求めた評価関数値について、
 * 単純に線形和平均をとることによって評価関数とする。
 */ 
class Cirno : public Evaluator {
  public:
    class Tiler {
      public:
        struct Encoder {
          int encoded;
          Encoder(const Othello::Stone *stone) 
              : encoded(stone->to_i()){}
          bool operator()(
              const int i,
              const int j,
              const Othello::Stone *stone,
              const int move) {
            encoded *= 3;
            encoded += stone->to_i();
            return true;
          }
        };
        typedef vector<int> encoded_t;
        static int encoded_size(const Othello &othello) {
          return othello.current().board().size() * 6 - 10;
        }
        static encoded_t encode(const Othello::Board &board){
          encoded_t encoded;
          
          int board_size(board.size());
          
          // 縦
          for(int j = 0; j < board_size; j++){
            Encoder encoder(board.get(0, j));
            board.directional_map(0, j, 1, 0, encoder);
            encoded.push_back(encoder.encoded);
          }
          
          // 横
          for(int i = 0; i < board_size; i++){
            Encoder encoder(board.get(i, 0));
            board.directional_map(i, 0, 0, 1, encoder);
            encoded.push_back(encoder.encoded);
          }
          
          // 右下
          {
            int i(board_size - 2), j(0);
            while(i > 0){
              Encoder encoder(board.get(--i, j));
              board.directional_map(i, j, 1, 1, encoder);
              encoded.push_back(encoder.encoded);
            }
            while(j < board_size - 3){
              Encoder encoder(board.get(i, ++j));
              board.directional_map(i, j, 1, 1, encoder);
              encoded.push_back(encoder.encoded);
            }
          }
          
          // 右上
          {
            int i(1), j(0);
            while(i < board_size - 1){
              Encoder encoder(board.get(++i, j));
              board.directional_map(i, j, -1, 1, encoder);
              encoded.push_back(encoder.encoded);
            }
            while(j < board_size - 3){
              Encoder encoder(board.get(i, ++j));
              board.directional_map(i, j, -1, 1, encoder);
              encoded.push_back(encoder.encoded);
            }
          }
          
          return encoded;
        }
    };
  public:
    struct trained_item_t {
      double score;
      double score_sum;
      double score_pow2;
      unsigned int run_count;
      // 初期値は各タイルの初期値はここで設定のこと
      trained_item_t() 
          : score(0.), score_sum(0.), score_pow2(0.), run_count(0) {};
    };
    struct trained_1tile_t {
      typedef map<int, trained_item_t> items_t;
      items_t trained_items;
      double total_score_sum;
      double total_score_pow2;
      unsigned int total_run_count;
      trained_1tile_t() 
          : trained_items(), 
            total_score_sum(0), total_score_pow2(0), total_run_count(0) {} 
    };
  protected:
    typedef vector<trained_1tile_t *> trained_tiles_t;
    trained_tiles_t trained_data;
    const Othello::Stone *evaluate_mode;
    const double _min_value;
    const double _max_value;
  public:
    Cirno (const Othello &othello) 
        : trained_data(), 
          evaluate_mode(othello.current().next()),
#ifndef pow2
#define pow2(x) ((x)*(x))
#endif
          _min_value(-pow2(othello.current().board().size())),
          _max_value(pow2(othello.current().board().size())) {
      for(int i = 0; i < Tiler::encoded_size(othello); i++){
        trained_data.push_back(new trained_1tile_t());
      }
    }
    ~Cirno () {
      for(trained_tiles_t::iterator it(trained_data.begin());
          it != trained_data.end();
          ++it){
        delete *it;
      }
    }
    const double min_value() const {return _min_value;}
    const double max_value() const {return _max_value;}
    
    // シリアルダンプ
    friend ostream &operator<<(ostream &out, const Cirno &cirno){
      for(trained_tiles_t::const_iterator it(cirno.trained_data.begin());
          it != cirno.trained_data.end();
          ++it){
        out << (*it)->total_score_sum << " "
            << (*it)->total_score_pow2 << " "
            << (*it)->total_run_count << " "
            << (*it)->trained_items.size() << " ";
        for(trained_1tile_t::items_t::iterator it2((*it)->trained_items.begin());
            it2 != (*it)->trained_items.end();
            ++it2){
          out << it2->first << " " 
              << it2->second.score << " "
              << it2->second.score_sum << " "
              << it2->second.score_pow2 << " "
              << it2->second.run_count << " ";
        }
        out << endl;
      }
      return out;
    }
    
    // シリアルロード
    friend istream &operator>>(istream &in, Cirno &cirno){
      for(trained_tiles_t::iterator it(cirno.trained_data.begin());
          it != cirno.trained_data.end();
          ++it){
        in >> ((*it)->total_score_sum);
        in >> ((*it)->total_score_pow2);
        in >> ((*it)->total_run_count);
        (*it)->trained_items.clear();
        
        int items;
        in >> items;
        while(items-- > 0){
          int index;
          double score, score_sum, score_pow2;
          int run_count;
          in >> index >> score >> score_sum >> score_pow2 >> run_count;
          trained_item_t &item((*it)->trained_items[index]);
          item.score = score;
          item.score_sum = score_sum; 
          item.score_pow2 = score_pow2;
          item.run_count = run_count;
        }
      }
          
      return in;
    }
    
    /**
     * 評価器の動作モードを指定
     * 
     */
    void set_evaluate_mode(const Othello::State &current){
      evaluate_mode = current.next();
    }
    
    /**
     * 評価器の動作モードを取得
     * 
     */
    const Othello::Stone *get_evaluate_mode() const {
      return evaluate_mode;
    }
    
    /**
     * ある状態を評価する
     * 
     */
    virtual double evaluate(const Othello::State &state){
      Tiler::encoded_t encoded(Tiler::encode(state.board()));
      
      int index(0);
      double score_total(0);
      for(Tiler::encoded_t::iterator it(encoded.begin());
            it != encoded.end();
            ++it){
        trained_item_t &item((trained_data[index++])->trained_items[*it]);
        score_total += item.score;
      }
      score_total /= encoded.size();
      
      return score_total * evaluate_mode->to_i();
    }
};

#endif /* __CIRNO_H__ */

