// -*-c++-*-

/*!
  \file strict_check_pass_generator.cpp
  \brief strict checked pass course generator Source File
*/

/*
 *Copyright:

 Copyright (C) Hidehisa AKIYAMA

 This code 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, or (at your option)
 any later version.

 This code 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 code; see the file COPYING.  If not, write to
 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

 *EndCopyright:
 */

/////////////////////////////////////////////////////////////////////

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "strict_check_pass_generator.h"

#include "pass.h"
#include "field_analyzer.h"

#include <rcsc/player/world_model.h>
#include <rcsc/player/intercept_table.h>
#include <rcsc/common/audio_memory.h>
#include <rcsc/common/logger.h>
#include <rcsc/common/server_param.h>
#include <rcsc/common/player_type.h>
#include <rcsc/math_util.h>
#include <rcsc/timer.h>

#include <algorithm>
#include <limits>
#include <sstream>
#include <cmath>

#define DEBUG_PROFILE

// #define DEBUG_PRINT_COMMON

// #define DEBUG_UPDATE_PASSER
// #define DEBUG_UPDATE_RECEIVERS
// #define DEBUG_UPDATE_OPPONENT
// #define DEBUG_DIRECT_PASS
// #define DEBUG_LEADING_PASS
// #define DEBUG_THROUGH_PASS

// #define DEBUG_PREDICT_RECEIVER
// #define DEBUG_PREDICT_OPPONENT_REACH_STEP

// #define DEBUG_PRINT_SUCCESS_PASS
// #define DEBUG_PRINT_FAILED_PASS


// #define CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT

using namespace rcsc;

namespace {

inline
void
debug_paint_failed_pass( const int count,
                         const Vector2D & receive_point )
{
    dlog.addRect( Logger::PASS,
                  receive_point.x - 0.1, receive_point.y - 0.1,
                  0.2, 0.2,
                  "#ff0000" );
    if ( count >= 0 )
    {
        char num[8];
        snprintf( num, 8, "%d", count );
        dlog.addMessage( Logger::PASS,
                         receive_point, num );
    }
}

}

/*-------------------------------------------------------------------*/
/*!

 */
StrictCheckPassGenerator::Receiver::Receiver( const AbstractPlayerObject * p,
                                              const Vector2D & first_ball_pos )
    : player_( p ),
      pos_( p->seenPosCount() <= p->posCount() ? p->seenPos() : p->pos() ),
      vel_( p->seenVelCount() <= p->velCount() ? p->seenVel() : p->vel() ),
      inertia_pos_( p->playerTypePtr()->inertiaFinalPoint( pos_, vel_ ) ),
      speed_( vel_.r() ),
      penalty_distance_( FieldAnalyzer::estimate_virtual_dash_distance( p ) ),
      penalty_step_( p->playerTypePtr()->cyclesToReachDistance( penalty_distance_ ) ),
      angle_from_ball_( ( p->pos() - first_ball_pos ).th() )
{

}

/*-------------------------------------------------------------------*/
/*!

 */
StrictCheckPassGenerator::Opponent::Opponent( const AbstractPlayerObject * p )
    : player_( p ),
      pos_( p->seenPosCount() <= p->posCount() ? p->seenPos() : p->pos() ),
      vel_( p->seenVelCount() <= p->velCount() ? p->seenVel() : p->vel() ),
      speed_( vel_.r() ),
      bonus_distance_( FieldAnalyzer::estimate_virtual_dash_distance( p ) )
{

}

/*-------------------------------------------------------------------*/
/*!

 */
StrictCheckPassGenerator::StrictCheckPassGenerator()
    : M_update_time( -1, 0 ),
      M_total_count( 0 ),
      M_pass_type( '-' ),
      M_passer( static_cast< AbstractPlayerObject * >( 0 ) ),
      M_start_time( -1, 0 )
{
    M_receiver_candidates.reserve( 11 );
    M_opponents.reserve( 16 );
    M_courses.reserve( 1024 );

    clear();
}

/*-------------------------------------------------------------------*/
/*!

 */
StrictCheckPassGenerator &
StrictCheckPassGenerator::instance()
{
    static StrictCheckPassGenerator s_instance;
    return s_instance;
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::clear()
{
    M_total_count = 0;
    M_pass_type = '-';
    M_passer = static_cast< AbstractPlayerObject * >( 0 );
    M_start_time.assign( -1, 0 );
    M_first_point.invalidate();
    M_receiver_candidates.clear();
    M_opponents.clear();
    M_direct_size = M_leading_size = M_through_size = 0;
    M_courses.clear();
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::generate( const WorldModel & wm )
{
    if ( M_update_time == wm.time() )
    {
        return;
    }
    M_update_time = wm.time();

    clear();

    if ( wm.time().stopped() > 0
         || wm.gameMode().isPenaltyKickMode() )
    {
        return;
    }

#ifdef DEBUG_PROFILE
    Timer timer;
#endif

    updatePasser( wm );

    if ( ! M_passer
         || ! M_first_point.isValid() )
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (generate) passer not found." );
        return;
    }

    updateReceivers( wm );

    if ( M_receiver_candidates.empty() )
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (generate) no receiver." );
        return;
    }

    updateOpponents( wm );

    createCourses( wm );

    std::sort( M_courses.begin(), M_courses.end(),
               CooperativeAction::DistCompare( ServerParam::i().theirTeamGoalPos() ) );

#ifdef DEBUG_PROFILE
    if ( M_passer->unum() == wm.self().unum() )
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (generate) PROFILE passer=self size=%d/%d D=%d L=%d T=%d elapsed %f [ms]",
                      (int)M_courses.size(),
                      M_total_count,
                      M_direct_size, M_leading_size, M_through_size,
                      timer.elapsedReal() );
    }
    else
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (update) PROFILE passer=%d size=%d/%d D=%d L=%d T=%d elapsed %f [ms]",
                      M_passer->unum(),
                      (int)M_courses.size(),
                      M_total_count,
                      M_direct_size, M_leading_size, M_through_size,
                      timer.elapsedReal() );
    }
#endif
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::updatePasser( const WorldModel & wm )
{
    if ( wm.self().isKickable()
         && ! wm.self().isFrozen() )
    {
        M_passer = &wm.self();
        M_start_time = wm.time();
        M_first_point = wm.ball().pos();
#ifdef DEBUG_UPDATE_PASSER
        dlog.addText( Logger::PASS,
                      __FILE__" (updatePasser) self kickable." );
#endif
        return;
    }

    int s_min = wm.interceptTable()->selfReachCycle();
    int t_min = wm.interceptTable()->teammateReachCycle();
    int o_min = wm.interceptTable()->opponentReachCycle();

    int our_min = std::min( s_min, t_min );
    if ( o_min < std::min( our_min - 4, (int)rint( our_min * 0.9 ) ) )
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (updatePasser) opponent ball." );
        return;
    }

    if ( s_min <= t_min )
    {
        if ( s_min <= 2 )
        {
            M_passer = &wm.self();
            M_first_point = wm.ball().inertiaPoint( s_min );
        }
    }
    else
    {
        if ( t_min <= 2 )
        {
            M_passer = wm.interceptTable()->fastestTeammate();
            M_first_point = wm.ball().inertiaPoint( t_min );
        }
    }

    if ( ! M_passer )
    {
        dlog.addText( Logger::PASS,
                      __FILE__" (updatePasser) no passer." );
        return;
    }

    M_start_time = wm.time();
    if ( ! wm.gameMode().isServerCycleStoppedMode() )
    {
        M_start_time.addCycle( t_min );
    }

    if ( M_passer->unum() != wm.self().unum() )
    {
        if ( M_first_point.dist2( wm.self().pos() ) > std::pow( 30.0, 2 ) )
        {
            M_passer = static_cast< const AbstractPlayerObject * >( 0 );
            dlog.addText( Logger::PASS,
                          __FILE__" (updatePasser) passer is too far." );
            return;
        }
    }

#ifdef DEBUG_UPDATE_PASSER
    dlog.addText( Logger::PASS,
                  __FILE__" (updatePasser) passer=%d(%.1f %.1f) reachStep=%d startPos=(%.1f %.1f)",
                  M_passer->unum(),
                  M_passer->pos().x, M_passer->pos().y,
                  t_min,
                  M_first_point.x, M_first_point.y );
#endif
}

/*-------------------------------------------------------------------*/
/*!

 */
struct ReceiverAngleCompare {

    bool operator()( const StrictCheckPassGenerator::Receiver & lhs,
                     const StrictCheckPassGenerator::Receiver & rhs ) const
      {
          return lhs.angle_from_ball_.degree() < rhs.angle_from_ball_.degree();
      }
};

/*-------------------------------------------------------------------*/
/*!

 */
namespace {
struct ReceiverDistCompare {

    const Vector2D pos_;

    ReceiverDistCompare( const Vector2D & pos )
        : pos_( pos )
      { }

    bool operator()( const StrictCheckPassGenerator::Receiver & lhs,
                     const StrictCheckPassGenerator::Receiver & rhs ) const
      {
          return lhs.pos_.dist2( pos_ ) < rhs.pos_.dist2( pos_ );
      }
};
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::updateReceivers( const WorldModel & wm )
{
    const ServerParam & SP = ServerParam::i();
    const double max_dist2 = std::pow( 40.0, 2 ); // Magic Number

    const bool is_self_passer = ( M_passer->unum() == wm.self().unum() );

    for ( AbstractPlayerCont::const_iterator
              p = wm.ourPlayers().begin(),
              end = wm.ourPlayers().end();
          p != end;
          ++p )
    {
        if ( *p == M_passer ) continue;

        if ( is_self_passer )
        {
            // if ( (*p)->isGhost() ) continue;
            if ( (*p)->unum() == Unum_Unknown ) continue;
            // if ( (*p)->unumCount() > 10 ) continue;
            if ( (*p)->posCount() > 10 ) continue;
            if ( (*p)->isTackling() ) continue;
            if ( (*p)->pos().x > wm.offsideLineX() )
            {
                dlog.addText( Logger::PASS,
                              "(updateReceiver) unum=%d (%.2f %.2f) > offside=%.2f",
                              (*p)->unum(),
                              (*p)->pos().x, (*p)->pos().y,
                              wm.offsideLineX() );
                continue;
            }
            //if ( (*p)->isTackling() ) continue;
            if ( (*p)->goalie()
                 && (*p)->pos().x < SP.ourPenaltyAreaLineX() + 15.0 )
            {
                continue;
            }
        }
        else
        {
            // ignore other players
            if ( (*p)->unum() != wm.self().unum() )
            {
                continue;
            }
        }

        if ( (*p)->pos().dist2( M_first_point ) > max_dist2 ) continue;

        M_receiver_candidates.push_back( Receiver( *p, M_first_point ) );
    }

    std::sort( M_receiver_candidates.begin(),
               M_receiver_candidates.end(),
               ReceiverDistCompare( SP.theirTeamGoalPos() ) );

    // std::sort( M_receiver_candidates.begin(),
    //            M_receiver_candidates.end(),
    //            ReceiverAngleCompare() );

#ifdef DEBUG_UPDATE_RECEIVERS
    for ( ReceiverCont::const_iterator p = M_receiver_candidates.begin();
          p != M_receiver_candidates.end();
          ++p )
    {
        dlog.addText( Logger::PASS,
                      "StrictPass receiver %d pos(%.1f %.1f) inertia=(%.1f %.1f) vel(%.2f %.2f)"
                      " penalty_dist=%.3f penalty_step=%d"
                      " angle=%.1f",
                      p->player_->unum(),
                      p->pos_.x, p->pos_.y,
                      p->inertia_pos_.x, p->inertia_pos_.y,
                      p->vel_.x, p->vel_.y,
                      p->penalty_distance_,
                      p->penalty_step_,
                      p->angle_from_ball_.degree() );
    }
#endif
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::updateOpponents( const WorldModel & wm )
{
    for ( AbstractPlayerCont::const_iterator
              p = wm.theirPlayers().begin(),
              end = wm.theirPlayers().end();
          p != end;
          ++p )
    {
        M_opponents.push_back( Opponent( *p ) );
#ifdef DEBUG_UPDATE_OPPONENT
        const Opponent & o = M_opponents.back();
        dlog.addText( Logger::PASS,
                      "StrictPass opp %d pos(%.1f %.1f) vel(%.2f %.2f) bonus_dist=%.3f",
                      o.player_->unum(),
                      o.pos_.x, o.pos_.y,
                      o.vel_.x, o.vel_.y,
                      o.bonus_distance_ );
#endif
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::createCourses( const WorldModel & wm )
{
    const ReceiverCont::iterator end = M_receiver_candidates.end();

    M_pass_type = 'D';
    for ( ReceiverCont::iterator p = M_receiver_candidates.begin();
          p != end;
          ++p )
    {
        createDirectPass( wm, *p );
    }

    M_pass_type = 'L';
    for ( ReceiverCont::iterator p = M_receiver_candidates.begin();
          p != end;
          ++p )
    {
        createLeadingPass( wm, *p );
    }

    M_pass_type = 'T';
    for ( ReceiverCont::iterator p = M_receiver_candidates.begin();
          p != end;
          ++p )
    {
        createThroughPass( wm, *p );
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::createDirectPass( const WorldModel & wm,
                                            const Receiver & receiver )
{
    static const int MIN_RECEIVE_STEP = 3;
#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
    static const int MAX_RECEIVE_STEP = 15; // Magic Number
#endif

    static const double MIN_DIRECT_PASS_DIST
        = ServerParam::i().defaultKickableArea() * 2.2;
    static const double MAX_DIRECT_PASS_DIST
        = 0.8 * inertia_final_distance( ServerParam::i().ballSpeedMax(),
                                        ServerParam::i().ballDecay() );
    static const double MAX_RECEIVE_BALL_SPEED
        = ServerParam::i().ballSpeedMax()
        * std::pow( ServerParam::i().ballDecay(), MIN_RECEIVE_STEP );

    const ServerParam & SP = ServerParam::i();

    //
    // check receivable area
    //
    if ( receiver.pos_.x > SP.pitchHalfLength() - 1.5
         || receiver.pos_.x < - SP.pitchHalfLength() + 5.0
         || receiver.pos_.absY() > SP.pitchHalfWidth() - 1.5 )
    {
#ifdef DEBUG_DIRECT_PASS
        dlog.addText( Logger::PASS,
                      "%d: xxx (direct) unum=%d outOfBounds pos=(%.2f %.2f)",
                      M_total_count, receiver.player_->unum(),
                      receiver.pos_.x, receiver.pos_.y );
#endif
        return;
    }

    //
    // avoid dangerous area
    //
    if ( receiver.pos_.x < M_first_point.x + 1.0
         && receiver.pos_.dist2( SP.ourTeamGoalPos() ) < std::pow( 18.0, 2 ) )
    {
#ifdef DEBUG_DIRECT_PASS
        dlog.addText( Logger::PASS,
                      "%d: xxx (direct) unum=%d dangerous pos=(%.2f %.2f)",
                      M_total_count, receiver.player_->unum(),
                      receiver.pos_.x, receiver.pos_.y );
#endif
        return;
    }

    const PlayerType * ptype = receiver.player_->playerTypePtr();

    const double max_ball_speed = ( wm.gameMode().type() == GameMode::PlayOn
                                    ? SP.ballSpeedMax()
                                    : wm.self().isKickable()
                                    ? wm.self().kickRate() * SP.maxPower()
                                    : SP.kickPowerRate() * SP.maxPower() );
    const double min_ball_speed = SP.defaultRealSpeedMax();

    const Vector2D receive_point = receiver.inertia_pos_;

    const double ball_move_dist = M_first_point.dist( receive_point );

    if ( ball_move_dist < MIN_DIRECT_PASS_DIST
         || MAX_DIRECT_PASS_DIST < ball_move_dist )
    {
#ifdef DEBUG_DIRECT_PASS
        dlog.addText( Logger::PASS,
                      "%d: xxx (direct) unum=%d overBallMoveDist=%.3f minDist=%.3f maxDist=%.3f",
                      M_total_count, receiver.player_->unum(),
                      ball_move_dist,
                      MIN_DIRECT_PASS_DIST, MAX_DIRECT_PASS_DIST );
#endif
        return;
    }

    if ( wm.gameMode().type() == GameMode::GoalKick_
         && receive_point.x < SP.ourPenaltyAreaLineX() + 1.0
         && receive_point.absY() < SP.penaltyAreaHalfWidth() + 1.0 )
    {
#ifdef DEBUG_DIRECT_PASS
        dlog.addText( Logger::PASS,
                      "%d: xxx (direct) unum=%d, goal_kick",
                      M_total_count, receiver.player_->unum() );
#endif
        return;
    }

    //
    // decide ball speed range
    //

    const double max_receive_ball_speed
        = std::min( MAX_RECEIVE_BALL_SPEED,
                    ptype->kickableArea() + ( SP.maxDashPower()
                                              * ptype->dashPowerRate()
                                              * ptype->effortMax() ) * 1.8 );
    const double min_receive_ball_speed = ptype->realSpeedMax();

    const AngleDeg ball_move_angle = ( receive_point - M_first_point ).th();

    const int min_ball_step = SP.ballMoveStep( SP.ballSpeedMax(), ball_move_dist );


#ifdef DEBUG_PRINT_SUCCESS_PASS
    std::vector< int > success_counts;
#endif

    const int start_step = std::max( std::max( MIN_RECEIVE_STEP,
                                               min_ball_step ),
                                     receiver.penalty_step_ );
#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
    const int max_step = std::max( MAX_RECEIVE_STEP, start_step + 2 );
#else
    const int max_step = start_step + 2;
#endif

#ifdef DEBUG_DIRECT_PASS
    dlog.addText( Logger::PASS,
                  "=== (direct) unum=%d stepRange=[%d, %d]",
                  receiver.player_->unum(),
                  start_step, max_step );
#endif

    createPassCommon( wm,
                      receiver, receive_point,
                      start_step, max_step,
                      min_ball_speed, max_ball_speed,
                      min_receive_ball_speed, max_receive_ball_speed,
                      ball_move_dist, ball_move_angle,
                      "strictDirect" );
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::createLeadingPass( const WorldModel & wm,
                                             const Receiver & receiver )
{
    static const double OUR_GOAL_DIST_THR2 = std::pow( 16.0, 2 );

    static const int MIN_RECEIVE_STEP = 4;
#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
    static const int MAX_RECEIVE_STEP = 20;
#endif

    static const double MIN_LEADING_PASS_DIST = 3.0;
    static const double MAX_LEADING_PASS_DIST
        = 0.8 * inertia_final_distance( ServerParam::i().ballSpeedMax(),
                                        ServerParam::i().ballDecay() );
    static const double MAX_RECEIVE_BALL_SPEED
        = ServerParam::i().ballSpeedMax()
        * std::pow( ServerParam::i().ballDecay(), MIN_RECEIVE_STEP );

    static const int ANGLE_DIVS = 24;
    static const double ANGLE_STEP = 360.0 / ANGLE_DIVS;
    static const double DIST_DIVS = 4;
    static const double DIST_STEP = 1.1;

    const ServerParam & SP = ServerParam::i();
    const PlayerType * ptype = receiver.player_->playerTypePtr();

    const double max_ball_speed = ( wm.gameMode().type() == GameMode::PlayOn
                                    ? SP.ballSpeedMax()
                                    : wm.self().isKickable()
                                    ? wm.self().kickRate() * SP.maxPower()
                                    : SP.kickPowerRate() * SP.maxPower() );
    const double min_ball_speed = SP.defaultRealSpeedMax();

    const double max_receive_ball_speed
        = std::min( MAX_RECEIVE_BALL_SPEED,
                    ptype->kickableArea() + ( SP.maxDashPower()
                                              * ptype->dashPowerRate()
                                              * ptype->effortMax() ) * 1.5 );
    const double min_receive_ball_speed = 0.001;

    const Vector2D our_goal = SP.ourTeamGoalPos();

#ifdef DEBUG_PRINT_SUCCESS_PASS
    std::vector< int > success_counts;
    success_counts.reserve( 16 );
#endif

    //
    // distance loop
    //
    for ( int d = 1; d <= DIST_DIVS; ++d )
    {
        const double player_move_dist = DIST_STEP * d;
        const int a_step = ( player_move_dist * 2.0 * M_PI / ANGLE_DIVS < 0.6
                             ? 2
                             : 1 );
        // const int move_dist_penalty_step
        //     = static_cast< int >( std::floor( player_move_dist * 0.3 ) );

        //
        // angle loop
        //
        for ( int a = 0; a < ANGLE_DIVS; a += a_step )
        {
            ++M_total_count;

            const AngleDeg angle = receiver.angle_from_ball_ + ANGLE_STEP*a;
            const Vector2D receive_point
                = receiver.inertia_pos_
                + Vector2D::from_polar( player_move_dist, angle );

            int move_dist_penalty_step = 0;
            {
                Line2D ball_move_line( M_first_point, receive_point );
                double player_line_dist = ball_move_line.dist( receiver.pos_ );

                move_dist_penalty_step = static_cast< int >( std::floor( player_line_dist * 0.3 ) );
            }

#ifdef DEBUG_LEADING_PASS
            dlog.addText( Logger::PASS,
                          ">>>> (lead) unum=%d receivePoint=(%.1f %.1f)",
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y );
#endif

            if ( receive_point.x > SP.pitchHalfLength() - 3.0
                 || receive_point.x < -SP.pitchHalfLength() + 5.0
                 || receive_point.absY() > SP.pitchHalfWidth() - 3.0 )
            {
#ifdef DEBUG_LEADING_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (lead) unum=%d outOfBounds pos=(%.2f %.2f)",
                              M_total_count, receiver.player_->unum(),
                              receive_point.x, receive_point.y );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                continue;
            }

            if ( receive_point.x < M_first_point.x
                 && receive_point.dist2( our_goal ) < OUR_GOAL_DIST_THR2 )
            {
#ifdef DEBUG_LEADING_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (lead) unum=%d our goal is near pos=(%.2f %.2f)",
                              M_total_count, receiver.player_->unum(),
                              receive_point.x, receive_point.y );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                continue;
            }

            if ( wm.gameMode().type() == GameMode::GoalKick_
                 && receive_point.x < SP.ourPenaltyAreaLineX() + 1.0
                 && receive_point.absY() < SP.penaltyAreaHalfWidth() + 1.0 )
            {
#ifdef DEBUG_LEADING_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (lead) unum=%d, goal_kick",
                              M_total_count, receiver.player_->unum() );
#endif
                return;
            }

            const double ball_move_dist = M_first_point.dist( receive_point );

            if ( ball_move_dist < MIN_LEADING_PASS_DIST
                 || MAX_LEADING_PASS_DIST < ball_move_dist )
            {
#ifdef DEBUG_LEADING_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (lead) unum=%d overBallMoveDist=%.3f minDist=%.3f maxDist=%.3f",
                              M_total_count, receiver.player_->unum(),
                              ball_move_dist,
                              MIN_LEADING_PASS_DIST, MAX_LEADING_PASS_DIST );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                continue;
            }

            {
                int nearest_receiver_unum = getNearestReceiverUnum( receive_point );
                if ( nearest_receiver_unum != receiver.player_->unum() )
                {
#ifdef DEBUG_LEADING_PASS
                    dlog.addText( Logger::PASS,
                                  "%d: xxx (lead) unum=%d otherReceiver=%d pos=(%.2f %.2f)",
                                  M_total_count, receiver.player_->unum(),
                                  nearest_receiver_unum,
                                  receive_point.x, receive_point.y );
                    debug_paint_failed_pass( M_total_count, receive_point );
#endif
                    break;
                }
            }

            const int receiver_step = predictReceiverReachStep( receiver,
                                                                receive_point,
                                                                true )
                + move_dist_penalty_step;
            const AngleDeg ball_move_angle = ( receive_point - M_first_point ).th();

            const int min_ball_step = SP.ballMoveStep( SP.ballSpeedMax(), ball_move_dist );

#ifdef DEBUG_PRINT_SUCCESS_PASS
            success_counts.clear();
#endif

            const int start_step = std::max( std::max( MIN_RECEIVE_STEP,
                                                       min_ball_step ),
                                             receiver_step );

// #ifdef DEBUG_LEADING_PASS
//             dlog.addText( Logger::PASS,
//                           "=== (lead) unum=%d MIN_RECEIVE_STEP=%d"
//                           " min_ball_step=%d"
//                           " receiver_step=%d",
//                           receiver.player_->unum(),
//                           MIN_RECEIVE_STEP, min_ball_step, receiver_step );
// #endif

#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
            const int max_step = std::max( MAX_RECEIVE_STEP, start_step + 3 );
#else
            const int max_step = start_step + 3;
#endif

#ifdef DEBUG_LEADING_PASS
            dlog.addText( Logger::PASS,
                          "=== (lead) receiver=%d"
                          " receivePos=(%.1f %.1f)",
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y );
            dlog.addText( Logger::PASS,
                          "__ ballMove=%.3f moveAngle=%.1f",
                          ball_move_dist, ball_move_angle.degree() );
            dlog.addText( Logger::PASS,
                          "__ stepRange=[%d, %d] receiverStep=%d(penalty=%d)",
                          start_step, max_step, receiver_step, move_dist_penalty_step );
#endif

            createPassCommon( wm,
                              receiver, receive_point,
                              start_step, max_step,
                              min_ball_speed, max_ball_speed,
                              min_receive_ball_speed, max_receive_ball_speed,
                              ball_move_dist, ball_move_angle,
                              "strictLead" );
        }
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::createThroughPass( const WorldModel & wm,
                                             const Receiver & receiver )
{
    static const int MIN_RECEIVE_STEP = 6;
#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
    static const int MAX_RECEIVE_STEP = 35;
#endif

    static const double MIN_THROUGH_PASS_DIST = 5.0;
    static const double MAX_THROUGH_PASS_DIST
        = 0.9 * inertia_final_distance( ServerParam::i().ballSpeedMax(),
                                        ServerParam::i().ballDecay() );
    static const double MAX_RECEIVE_BALL_SPEED
        = ServerParam::i().ballSpeedMax()
        * std::pow( ServerParam::i().ballDecay(), MIN_RECEIVE_STEP );

    static const int ANGLE_DIVS = 14;
    static const double MIN_ANGLE = -40.0;
    static const double MAX_ANGLE = +40.0;
    static const double ANGLE_STEP = ( MAX_ANGLE - MIN_ANGLE ) / ANGLE_DIVS;

    static const double MIN_MOVE_DIST = 6.0;
    static const double MAX_MOVE_DIST = 30.0 + 0.001;
    static const double MOVE_DIST_STEP = 2.0;

    const ServerParam & SP = ServerParam::i();
    const PlayerType * ptype = receiver.player_->playerTypePtr();
    const AngleDeg receiver_vel_angle = receiver.vel_.th();

    const double min_receive_x = std::min( std::min( std::max( 10.0, M_first_point.x + 10.0 ),
                                                     wm.offsideLineX() - 10.0 ),
                                           SP.theirPenaltyAreaLineX() - 5.0 );

    if ( receiver.pos_.x < min_receive_x - MAX_MOVE_DIST
         || receiver.pos_.x < 1.0 )
    {
#ifdef DEBUG_THROUGH_PASS
        dlog.addText( Logger::PASS,
                      "%d: xxx (through) unum=%d too back.",
                      M_total_count, receiver.player_->unum() );
#endif
        return;
    }

    //
    // initialize ball speed range
    //

    const double max_ball_speed = ( wm.gameMode().type() == GameMode::PlayOn
                                    ? SP.ballSpeedMax()
                                    : wm.self().isKickable()
                                    ? wm.self().kickRate() * SP.maxPower()
                                    : SP.kickPowerRate() * SP.maxPower() );
    const double min_ball_speed = 1.4; //SP.defaultPlayerSpeedMax();

    const double max_receive_ball_speed
        = std::min( MAX_RECEIVE_BALL_SPEED,
                    ptype->kickableArea() + ( SP.maxDashPower()
                                              * ptype->dashPowerRate()
                                              * ptype->effortMax() ) * 1.5 );
    const double min_receive_ball_speed = 0.001;

    //
    // check communication
    //

    bool pass_requested = false;
    AngleDeg requested_move_angle = 0.0;
    if ( wm.audioMemory().passRequestTime().cycle() > wm.time().cycle() - 10 ) // Magic Number
    {
        for ( std::vector< AudioMemory::PassRequest >::const_iterator it = wm.audioMemory().passRequest().begin();
              it != wm.audioMemory().passRequest().end();
              ++it )
        {
            if ( it->sender_ == receiver.player_->unum() )
            {
                pass_requested = true;
                requested_move_angle = ( it->pos_ - receiver.inertia_pos_ ).th();
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: (through) receiver=%d pass requested",
                              M_total_count, receiver.player_->unum() );
#endif
                break;
            }
        }
    }

    //
    //
    //
#ifdef DEBUG_PRINT_SUCCESS_PASS
    std::vector< int > success_counts;
    success_counts.reserve( 16 );
#endif

    //
    // angle loop
    //
    for ( int a = 0; a <= ANGLE_DIVS; ++a )
    {
        const AngleDeg angle = MIN_ANGLE + ( ANGLE_STEP * a );
        const Vector2D unit_rvec = Vector2D::from_polar( 1.0, angle );

        //
        // distance loop
        //
        for ( double move_dist = MIN_MOVE_DIST;
              move_dist < MAX_MOVE_DIST;
              move_dist += MOVE_DIST_STEP )
        {
            ++M_total_count;

            const Vector2D receive_point
                = receiver.inertia_pos_
                + unit_rvec * move_dist;

#ifdef DEBUG_THROUGH_PASS
            dlog.addText( Logger::PASS,
                          ">>>> (through) receiver=%d receivePoint=(%.1f %.1f)",
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y );
#endif

            if ( receive_point.x < min_receive_x )
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (through) unum=%d tooSmallX pos=(%.2f %.2f)",
                              M_total_count, receiver.player_->unum(),
                              receive_point.x, receive_point.y );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                continue;
            }

            if ( receive_point.x > SP.pitchHalfLength() - 1.5
                 || receive_point.absY() > SP.pitchHalfWidth() - 1.5 )
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (through) unum=%d outOfBounds pos=(%.2f %.2f)",
                              M_total_count, receiver.player_->unum(),
                              receive_point.x, receive_point.y );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                break;
            }

            const double ball_move_dist = M_first_point.dist( receive_point );

            if ( ball_move_dist < MIN_THROUGH_PASS_DIST
                 || MAX_THROUGH_PASS_DIST < ball_move_dist )
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: xxx (through) unum=%d overBallMoveDist=%.3f minDist=%.3f maxDist=%.3f",
                              M_total_count, receiver.player_->unum(),
                              ball_move_dist,
                              MIN_THROUGH_PASS_DIST, MAX_THROUGH_PASS_DIST );
                debug_paint_failed_pass( M_total_count, receive_point );
#endif
                continue;
            }

            {
                int nearest_receiver_unum = getNearestReceiverUnum( receive_point );
                if ( nearest_receiver_unum != receiver.player_->unum() )
                {
#ifdef DEBUG_THROUGH_PASS
                    dlog.addText( Logger::PASS,
                                  "%d: xxx (through) unum=%d otherReceiver=%d pos=(%.2f %.2f)",
                                  M_total_count, receiver.player_->unum(),
                                  nearest_receiver_unum,
                                  receive_point.x, receive_point.y );
                    debug_paint_failed_pass( M_total_count, receive_point );
#endif
                    break;
                }            }


            const int receiver_step = predictReceiverReachStep( receiver,
                                                                receive_point,
                                                                false );
            const AngleDeg ball_move_angle = ( receive_point - M_first_point ).th();

#ifdef DEBUG_PRINT_SUCCESS_PASS
            success_counts.clear();
#endif

            int start_step = receiver_step;
            if ( pass_requested
                 && ( requested_move_angle - angle ).abs() < 20.0 )
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: matched with requested pass. angle=%.1f",
                              M_total_count, angle.degree() );
#endif
            }
            // if ( receive_point.x > wm.offsideLineX() + 5.0
            //      || ball_move_angle.abs() < 15.0 )
            else if ( receiver.speed_ > 0.2
                      && ( receiver_vel_angle - angle ).abs() < 15.0 )
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: matched with receiver velocity. angle=%.1f",
                              M_total_count, angle.degree() );
#endif
            }
            else
            {
#ifdef DEBUG_THROUGH_PASS
                dlog.addText( Logger::PASS,
                              "%d: receiver step. one step penalty",
                              M_total_count );
#endif
                start_step += 1;
                if ( ( receive_point.x > SP.pitchHalfLength() - 5.0
                       || receive_point.absY() > SP.pitchHalfWidth() - 5.0 )
                     && ball_move_angle.abs() > 30.0
                     && start_step >= 10 )
                {
                    start_step += 1;
                }
            }

            const int min_ball_step = SP.ballMoveStep( SP.ballSpeedMax(), ball_move_dist );

            start_step = std::max( std::max( MIN_RECEIVE_STEP,
                                             min_ball_step ),
                                   start_step );

#ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
            const int max_step = std::max( MAX_RECEIVE_STEP, start_step + 3 );
#else
            const int max_step = start_step + 3;
#endif

#ifdef DEBUG_THROUGH_PASS
            dlog.addText( Logger::PASS,
                          "== (through) receiver=%d"
                          " ballPos=(%.1f %.1f) receivePos=(%.1f %.1f)",
                          receiver.player_->unum(),
                          M_first_point.x, M_first_point.y,
                          receive_point.x, receive_point.y );
            dlog.addText( Logger::PASS,
                          "== ballMove=%.3f moveAngle=%.1f",
                          ball_move_dist, ball_move_angle.degree() );
            dlog.addText( Logger::PASS,
                          "== stepRange=[%d, %d] receiverMove=%.3f receiverStep=%d",
                          start_step, max_step,
                          receiver.inertia_pos_.dist( receive_point ), receiver_step );
#endif

            createPassCommon( wm,
                              receiver, receive_point,
                              start_step, max_step,
                              min_ball_speed, max_ball_speed,
                              min_receive_ball_speed, max_receive_ball_speed,
                              ball_move_dist, ball_move_angle,
                              "strictThrough" );
        }

    }

}

/*-------------------------------------------------------------------*/
/*!

 */
void
StrictCheckPassGenerator::createPassCommon( const WorldModel & wm,
                                            const Receiver & receiver,
                                            const Vector2D & receive_point,
                                            const int min_step,
                                            const int max_step,
                                            const double & min_first_ball_speed,
                                            const double & max_first_ball_speed,
                                            const double & min_receive_ball_speed,
                                            const double & max_receive_ball_speed,
                                            const double & ball_move_dist,
                                            const AngleDeg & ball_move_angle,
                                            const char * description )
{
    const ServerParam & SP = ServerParam::i();

    int success_count = 0;
#ifdef DEBUG_PRINT_SUCCESS_PASS
    std::vector< int > success_counts;
    success_counts.reserve( max_step - min_step + 1 );
#endif

    for ( int step = min_step; step <= max_step; ++step )
    {
        ++M_total_count;

        double first_ball_speed = calc_first_term_geom_series( ball_move_dist,
                                                               SP.ballDecay(),
                                                               step );

#if (defined DEBUG_PRINT_DIRECT_PASS) || (defined DEBUG_PRINT_LEADING_PASS) || (defined DEBUG_PRINT_THROUGH_PASS) || (defined DEBUG_PRINT_FAILED_PASS)
        dlog.addText( Logger::PASS,
                      "%d: type=%c unum=%d recvPos=(%.2f %.2f) step=%d ballMoveDist=%.2f speed=%.3f",
                      M_total_count, M_pass_type,
                      receiver.player_->unum(),
                      receive_point.x, receive_point.y,
                      step,
                      ball_move_dist,
                      first_ball_speed );
#endif


        if ( first_ball_speed < min_first_ball_speed )
        {
#ifdef DEBUG_PRINT_FAILED_PASS
            dlog.addText( Logger::PASS,
                          "%d: xxx type=%c unum=%d (%.1f %.1f) step=%d firstSpeed=%.3f < min=%.3f",
                          M_total_count, M_pass_type,
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y,
                          step,
                          first_ball_speed, min_first_ball_speed );
#endif
            break;
        }

        if ( max_first_ball_speed < first_ball_speed )
        {
#ifdef DEBUG_PRINT_FAILED_PASS
            dlog.addText( Logger::PASS,
                          "%d: xxx type=%c unum=%d (%.1f %.1f) step=%d firstSpeed=%.3f > max=%.3f",
                          M_total_count, M_pass_type,
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y,
                          step,
                          first_ball_speed, max_first_ball_speed );
#endif

            continue;
        }

        double receive_ball_speed = first_ball_speed * std::pow( SP.ballDecay(), step );
        if ( receive_ball_speed < min_receive_ball_speed )
        {
#ifdef DEBUG_PRINT_FAILED_PASS
            dlog.addText( Logger::PASS,
                          "%d: xxx type=%c unum=%d (%.1f %.1f) step=%d recvSpeed=%.3f < min=%.3f",
                          M_total_count, M_pass_type,
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y,
                          step,
                          receive_ball_speed, min_receive_ball_speed );
#endif
            break;
        }

        if ( max_receive_ball_speed < receive_ball_speed )
        {
#ifdef DEBUG_PRINT_FAILED_PASS
            dlog.addText( Logger::PASS,
                          "%d: xxx type=%c unum=%d (%.1f %.1f) step=%d recvSpeed=%.3f > max=%.3f",
                          M_total_count, M_pass_type,
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y,
                          step,
                          receive_ball_speed, max_receive_ball_speed );
#endif
            continue;
        }

        int kick_count = FieldAnalyzer::predict_kick_count( wm,
                                                            M_passer,
                                                            first_ball_speed,
                                                            ball_move_angle );

        const AbstractPlayerObject * opponent = static_cast< const AbstractPlayerObject * >( 0 );
        int o_step = predictOpponentsReachStep( wm,
                                                M_first_point,
                                                first_ball_speed,
                                                ball_move_angle,
                                                receive_point,
                                                step + ( kick_count - 1 ) + 5,
                                                &opponent );

        bool failed = false;
        if ( M_pass_type == 'T' )
        {
            if ( o_step <= step )
            {
#ifdef DEBUG_THROUGH_PASS
                 dlog.addText( Logger::PASS,
                               "%d: ThroughPass failed???",
                               M_total_count );
#endif
                 failed = true;
            }

            if ( receive_point.x > 30.0
                 && step >= 15
                 && ( ! opponent
                      || ! opponent->goalie() )
                 && o_step >= step ) // Magic Number
            {
                AngleDeg receive_move_angle = ( receive_point - receiver.pos_ ).th();
                if ( ( receiver.player_->body() - receive_move_angle ).abs() < 15.0 )
                {
#ifdef DEBUG_THROUGH_PASS
                    dlog.addText( Logger::PASS,
                                  "%d: ********** ThroughPass reset failed flag",
                                  M_total_count );
#endif
                    failed = false;
                }
            }
        }
        else
        {
            if ( o_step <= step + ( kick_count - 1 ) )
            {
                failed = true;
            }
        }

        if ( failed )
        {
            // opponent can reach the ball faster than the receiver.
            // then, break the loop, because ball speed is decreasing in the loop.
#ifdef DEBUG_PRINT_FAILED_PASS
            dlog.addText( Logger::PASS,
                          "%d: xxx type=%c unum=%d (%.1f %.1f) step=%d >= opp[%d]Step=%d,"
                          " firstSpeed=%.3f recvSpeed=%.3f nKick=%d",
                          M_total_count, M_pass_type,
                          receiver.player_->unum(),
                          receive_point.x, receive_point.y,
                          step,
                          ( opponent ? opponent->unum() : 0 ), o_step,
                          first_ball_speed, receive_ball_speed,
                          kick_count );
#endif
            break;
        }

        CooperativeAction::Ptr pass( new Pass( M_passer->unum(),
                                               receiver.player_->unum(),
                                               receive_point,
                                               first_ball_speed,
                                               step + kick_count,
                                               kick_count,
                                               FieldAnalyzer::to_be_final_action( wm ),
                                               description ) );
        pass->setIndex( M_total_count );

        switch ( M_pass_type ) {
        case 'D':
            M_direct_size += 1;
            break;
        case 'L':
            M_leading_size += 1;
            break;
        case 'T':
            M_through_size += 1;
        default:
            break;
        }
        // if ( M_pass_type == 'L'
        //      && success_count > 0 )
        // {
        //     M_courses.pop_back();
        // }

        M_courses.push_back( pass );

#ifdef DEBUG_PRINT_SUCCESS_PASS
        dlog.addText( Logger::PASS,
                      "%d: ok type=%c unum=%d step=%d  opp[%d]Step=%d"
                      " nKick=%d ball=(%.1f %.1f) recv=(%.1f %.1f) "
                      " speed=%.3f->%.3f dir=%.1f",
                      M_total_count, M_pass_type,
                      receiver.player_->unum(),
                      step,
                      ( opponent ? opponent->unum() : 0 ),
                      o_step,
                      kick_count,
                      M_first_point.x, M_first_point.y,
                      receive_point.x, receive_point.y,
                      first_ball_speed,
                      receive_ball_speed,
                      ball_move_angle.degree() );
        success_counts.push_back( M_total_count );
#endif

#ifndef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT
        break;
#endif

        if ( o_step <= step + 3 )
        {
#ifdef DEBUG_PRINT_SUCCESS_PASS
            dlog.addText( Logger::PASS,
                          "---- o_step(=%d) <= step+3(=%d) break...",
                          o_step, step+3 );
#endif
            break;
        }

        if ( min_step + 3 <= step )
        {
#ifdef DEBUG_PRINT_SUCCESS_PASS
            dlog.addText( Logger::PASS,
                          "---- step=%d >= min_step+?(=%d) break...",
                          step, min_step + 3 );
#endif
            break;
        }

        if ( M_passer->unum() != wm.self().unum() )
        {
            break;
        }

        ++success_count;
    }


#ifdef DEBUG_PRINT_SUCCESS_PASS
    if ( ! success_counts.empty() )
    {
        std::ostringstream ostr;
        std::vector< int >::const_iterator it = success_counts.begin();

        ostr << *it; ++it;
        for ( ; it != success_counts.end(); ++it )
        {
            ostr << ',' << *it;
        }

        dlog.addRect( Logger::PASS,
                      receive_point.x - 0.1, receive_point.y - 0.1,
                      0.2, 0.2,
                      "#00ff00" );
        dlog.addMessage( Logger::PASS,
                         receive_point,
                         ostr.str().c_str() );
    }
#ifdef DEBUG_PRINT_FAILED_PASS
    else
    {
        debug_paint_failed_pass( M_total_count, receive_point );
    }
#endif
#endif
}

/*-------------------------------------------------------------------*/
/*!

 */
int
StrictCheckPassGenerator::getNearestReceiverUnum( const Vector2D & pos )
{
    int unum = Unum_Unknown;
    double min_dist2 = std::numeric_limits< double >::max();

    for ( ReceiverCont::iterator p = M_receiver_candidates.begin();
          p != M_receiver_candidates.end();
          ++p )
    {
        double d2 = p->pos_.dist2( pos );
        if ( d2 < min_dist2 )
        {
            min_dist2 = d2;
            unum = p->player_->unum();
        }
    }

    return unum;
}

/*-------------------------------------------------------------------*/
/*!

 */
int
StrictCheckPassGenerator::predictReceiverReachStep( const Receiver & receiver,
                                                    const Vector2D & pos,
                                                    const bool use_penalty )
{
    const PlayerType * ptype = receiver.player_->playerTypePtr();
    double target_dist = receiver.inertia_pos_.dist( pos );
    int n_turn = ( receiver.player_->bodyCount() > 0
                   ? 1
                   : FieldAnalyzer::predict_player_turn_cycle( ptype,
                                                               receiver.player_->body(),
                                                               receiver.speed_,
                                                               target_dist,
                                                               ( pos - receiver.inertia_pos_ ).th(),
                                                               ptype->kickableArea(),
                                                               false ) );
    double dash_dist = target_dist;

    // if ( receiver.pos_.x < pos.x )
    // {
    //     dash_dist -= ptype->kickableArea() * 0.5;
    // }

    if ( use_penalty )
    {
        dash_dist += receiver.penalty_distance_;
    }

    // if ( M_pass_type == 'T' )
    // {
    //     dash_dist -= ptype->kickableArea() * 0.5;
    // }

    if ( M_pass_type == 'L' )
    {
        // if ( pos.x > -20.0
        //      && dash_dist < ptype->kickableArea() * 1.5 )
        // {
        //     dash_dist -= ptype->kickableArea() * 0.5;
        // }

        // if ( pos.x < 30.0 )
        // {
        //     dash_dist += 0.3;
        // }

        dash_dist *= 1.05;

        AngleDeg dash_angle = ( pos - receiver.pos_ ).th() ;

        if ( dash_angle.abs() > 90.0
             || receiver.player_->bodyCount() > 1
             || ( dash_angle - receiver.player_->body() ).abs() > 30.0 )
        {
            n_turn += 1;
        }
    }

    int n_dash = ptype->cyclesToReachDistance( dash_dist );

#ifdef DEBUG_PREDICT_RECEIVER
    dlog.addText( Logger::PASS,
                  "== receiver=%d receivePos=(%.1f %.1f) dist=%.2f dash=%.2f penalty=%.2f turn=%d dash=%d",
                  receiver.player_->unum(),
                  pos.x, pos.y,
                  target_dist, dash_dist, receiver.penalty_distance_,
                  n_turn, n_dash );
#endif
    return ( n_turn == 0
             ? n_turn + n_dash
             : n_turn + n_dash + 1 ); // 1 step penalty for observation delay.
    // if ( ! use_penalty )
    // {
    //     return n_turn + n_dash;
    // }
    // return n_turn + n_dash + 1; // 1 step penalty for observation delay.
}

/*-------------------------------------------------------------------*/
/*!

 */
int
StrictCheckPassGenerator::predictOpponentsReachStep( const WorldModel & wm,
                                                     const Vector2D & first_ball_pos,
                                                     const double & first_ball_speed,
                                                     const AngleDeg & ball_move_angle,
                                                     const Vector2D & receive_point,
                                                     const int max_cycle,
                                                     const AbstractPlayerObject ** opponent )
{
    const Vector2D first_ball_vel = Vector2D::polar2vector( first_ball_speed, ball_move_angle );

    double bonus_dist = -10000.0;
    int min_step = 1000;
    const AbstractPlayerObject * fastest_opponent = static_cast< AbstractPlayerObject * >( 0 );

    for ( OpponentCont::const_iterator o = M_opponents.begin();
          o != M_opponents.end();
          ++o )
    {
        int step = predictOpponentReachStep( wm,
                                             *o,
                                             first_ball_pos,
                                             first_ball_vel,
                                             ball_move_angle,
                                             receive_point,
                                             std::min( max_cycle, min_step ) );
        if ( step < min_step
             || ( step == min_step
                  && o->bonus_distance_ > bonus_dist ) )
        {
            bonus_dist = o->bonus_distance_;
            min_step = step;
            fastest_opponent = o->player_;
        }
    }

#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
    dlog.addText( Logger::PASS,
                  "______ opponent=%d(%.1f %.1f) step=%d",
                  fastest_opponent->unum(),
                  fastest_opponent->pos().x, fastest_opponent->pos().y,
                  min_step );
#endif

    if ( opponent )
    {
        *opponent = fastest_opponent;
    }
    return min_step;
}

/*-------------------------------------------------------------------*/
/*!

 */
int
StrictCheckPassGenerator::predictOpponentReachStep( const WorldModel & wm,
                                                    const Opponent & opponent,
                                                    const Vector2D & first_ball_pos,
                                                    const Vector2D & first_ball_vel,
                                                    const AngleDeg & ball_move_angle,
                                                    const Vector2D & receive_point,
                                                    const int max_cycle )
{
    static const Rect2D penalty_area( Vector2D( ServerParam::i().theirPenaltyAreaLineX(),
                                                -ServerParam::i().penaltyAreaHalfWidth() ),
                                      Size2D( ServerParam::i().penaltyAreaLength(),
                                              ServerParam::i().penaltyAreaWidth() ) );
    static const double CONTROL_AREA_BUF = 0.15;

    const ServerParam & SP = ServerParam::i();

    const PlayerType * ptype = opponent.player_->playerTypePtr();
    const int min_cycle = FieldAnalyzer::estimate_min_reach_cycle( opponent.pos_,
                                                                   ptype->realSpeedMax(),
                                                                   first_ball_pos,
                                                                   ball_move_angle );
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
    dlog.addText( Logger::PASS,
                  "++ opponent=%d(%.1f %.1f)",
                  opponent.player_->unum(),
                  opponent.pos_.x, opponent.pos_.y );
#endif

    if ( min_cycle < 0 )
    {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
        dlog.addText( Logger::PASS,
                      "__ never reach(1)",
                      opponent.player_->unum(),
                      opponent.player_->pos().x,
                      opponent.player_->pos().y );
#endif
        return 1000;
    }

    for ( int cycle = std::max( 1, min_cycle ); cycle <= max_cycle; ++cycle )
    {
        const Vector2D ball_pos = inertia_n_step_point( first_ball_pos,
                                                        first_ball_vel,
                                                        cycle,
                                                        SP.ballDecay() );
        const double control_area = ( opponent.player_->goalie()
                                      && penalty_area.contains( ball_pos )
                                      ? SP.catchableArea()
                                      : ptype->kickableArea() );

        const Vector2D inertia_pos = ptype->inertiaPoint( opponent.pos_, opponent.vel_, cycle );
        const double target_dist = inertia_pos.dist( ball_pos );

        double dash_dist = target_dist;

        if ( M_pass_type == 'T'
             && first_ball_vel.x > 2.0
             && ( receive_point.x > wm.offsideLineX()
                  || receive_point.x > 30.0 ) )
        {
#if 0
            dlog.addText( Logger::PASS,
                          "__ step=%d no bonus",
                          cycle );
#endif
        }
        else
        {
            dash_dist -= opponent.bonus_distance_;
        }

        if ( dash_dist - control_area - CONTROL_AREA_BUF < 0.001 )
        {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
            dlog.addText( Logger::PASS,
                          "__ step=%d already there. dist=%.1f bonusDist=%.1f",
                          cycle,
                          target_dist, opponent.bonus_distance_ );
#endif
            return cycle;
        }

        //if ( cycle > 1 )
        {
            if ( M_pass_type == 'T'
                 && first_ball_vel.x > 2.0
                 && ( receive_point.x > wm.offsideLineX()
                      || receive_point.x > 30.0 ) )
            {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
                dlog.addText( Logger::PASS,
                              "__ step=%d through pass: dash_dist=%.2f bonus=%.2f",
                              cycle,
                              dash_dist, control_area * 0.8 );
#endif
                //dash_dist -= control_area * 0.5;
                //dash_dist -= control_area * 0.8;
                dash_dist -= control_area;
            }
            else
            {
                if ( receive_point.x < 25.0 )
                {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
                    dlog.addText( Logger::PASS,
                                  "__ step=%d normal(1) dash_dist=%.2f bonus=%.2f",
                                  cycle,
                                  dash_dist, control_area + 0.5 );
#endif
                    dash_dist -= control_area;
                    dash_dist -= 0.5;
                }
                else
                {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
                    dlog.addText( Logger::PASS,
                                  "__ step=%d normal(2) dash_dist=%.2f bonus=%.2f",
                                  cycle,
                                  dash_dist, control_area + 0.8 + 0.2 );
#endif
                    //dash_dist -= control_area * 0.8;
                    dash_dist -= control_area;
                    dash_dist -= 0.2;
                }
            }
        }

        if ( dash_dist > ptype->realSpeedMax()
             * ( cycle + std::min( opponent.player_->posCount(), 5 ) ) )
        {
#if 0
            dlog.addText( Logger::PASS,
                          "__ step=%d dash_dist=%.1f reachable=%.1f",
                          cycle,
                          dash_dist, ptype->realSpeedMax()*cycle );
#endif
            continue;
        }

        //
        // dash
        //

        int n_dash = ptype->cyclesToReachDistance( dash_dist );

        if ( n_dash > cycle + opponent.player_->posCount() )
        {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
            dlog.addText( Logger::PASS,
                          "__ step=%d dash_dist=%.1f n_dash=%d",
                          cycle,
                          dash_dist, n_dash );
#endif
            continue;
        }

        //
        // turn
        //
        int n_turn = ( opponent.player_->bodyCount() > 1
                       ? 0
                       : FieldAnalyzer::predict_player_turn_cycle( ptype,
                                                                   opponent.player_->body(),
                                                                   opponent.speed_,
                                                                   target_dist,
                                                                   ( ball_pos - inertia_pos ).th(),
                                                                   control_area,
                                                                   true ));

        int n_step = ( n_turn == 0
                       ? n_turn + n_dash
                       : n_turn + n_dash + 1 ); // 1 step penalty for observation delay

        int bonus_step = 0;
        if ( opponent.player_->isTackling() )
        {
            bonus_step = -5; // Magic Number
        }

        // if ( receive_point.x < 0.0 )
        // {
        //     bonus_step += 1;
        // }

#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
        dlog.addText( Logger::PASS,
                      "__ step=%d oppStep=%d(t:%d,d:%d)"
                      " ballPos=(%.2f %.2f)"
                      " dist=%.2f ctrl=%.2f dash=%.2f bonus=%.1f"
                      " bonusStep=%d",
                      cycle,
                      n_step, n_turn, n_dash,
                      ball_pos.x, ball_pos.y,
                      target_dist, control_area, dash_dist, opponent.bonus_distance_,
                      bonus_step );
#endif
        if ( n_step - bonus_step <= cycle )
        {
#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
            dlog.addText( Logger::PASS,
                          "__ can reach" );
#endif
            return cycle;
        }
    }

#ifdef DEBUG_PREDICT_OPPONENT_REACH_STEP
    dlog.addText( Logger::PASS,
                  "__ never reach(2)" );
#endif

    return 1000;
}
