/*
 * Copyright 2008 nori090
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.coderepos.nori090.df;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.coderepos.nori090.df.Card.TYPE;
import org.coderepos.nori090.df.Command.CAST;
import org.coderepos.nori090.df.Command.PLAY;
import org.coderepos.nori090.df.Command.START;
import org.coderepos.nori090.df.Command.TURN;

/**
 * @author nori090
 * @version $Rev: 226 $ $Date: 2008-11-03 04:23:51 +0900 (Mon, 03 Nov 2008) $
 */
public class GameCaptureData {
    private final DF df;

    /* 14枚の時の中間値 */
    private static final int AVE_1 = ( ( 196 - 61 ) / 2 ) + 61;

    /* 13枚の時の中間値 */
    private static final int AVE_2 = ( ( 184 - 54 ) / 2 ) + 54;

    /* １枚当たりの平均値 */
    private static final int DEFAULT_POWER = 9;

    int total_card_count = TYPE.values().length - 1;

    // 保守率（５０％以下で保守。）
    int hosyu = 50;

    GameCaptureData( DF df ) {
        this.df = df;
    }

    // 自分以外のプレーヤデータ
    PlayerDataList player = new PlayerDataList( 3 );

    // まだ場に出てないカード
    Set<TYPE> hold_all_cards = new HashSet<TYPE>();

    public Set<TYPE> getHoldAllCards() {
        return hold_all_cards;
    }

    public void start( START start ) {
        calc_hosyu( start.getNewCard(), false );
        calc_blocking_init( start );
        make_all_hold_cards_init( start );
    }

    private void make_all_hold_cards_init( START start ) {
        TYPE[] otherwise = null;
        if ( start.getRound() > 0 ) {
            _start_card_diff diff = new _start_card_diff( start );
            otherwise = otherwise_card( start.getNewCard(), diff.getOldChanges(), diff.getNewChanges() );
        }
        else {
            otherwise = otherwise_card( start.getNewCard(), null, null );
        }
        hold_all_cards.addAll( Arrays.asList( otherwise ) );
    }

    /**
     * 他者阻止率構築の初期化
     * 
     * @param start
     */
    private void calc_blocking_init( START start ) {
        // 初戦出なければ、カード交換が発生するので算出する。
        if ( start.getRound() > 0 ) {
            _start_card_diff diff = new _start_card_diff( start );
            // 交換前後のデータを揃えた上でプレーヤデータを生成する。
            make_player_init( start, diff.getOldChanges(), diff.getNewChanges() );
        }
        else {
            // 交換が発生しないプレーヤデータを生成する。
            make_player_init( start, null, null );
        }
    }

    class _start_card_diff {
        TYPE[] old_change;

        TYPE[] new_change;

        private _start_card_diff( START start ) {
            int rank = start.getRank();
            // 交換するカードの数
            int num = ( rank == 1 || rank == 4 ) ? 2 : 1;
            Set<TYPE> old = new HashSet<TYPE>( start.getOldCard().list.length + 1 );
            // 交換前のカード（最大２枚
            old_change = new TYPE[num];
            int old_index = 0;
            // 交換後のカード（最大２枚
            new_change = new TYPE[num];
            int new_index = 0;
            for ( TYPE t : start.getOldCard().list ) {
                old.add( t );
            }
            // 交換前のカードを設定する。
            for ( TYPE t : start.getNewCard().list ) {
                if ( !old.remove( t ) ) {
                    old_change[old_index] = t;
                    old_index++;
                }
            }
            // 交換後のカードを設定する。
            for ( TYPE t : old ) {
                new_change[new_index] = t;
                new_index++;
            }
        }

        TYPE[] getOldChanges() {
            return old_change;
        }

        TYPE[] getNewChanges() {
            return new_change;
        }
    }

    /**
     * 他者のカードの割り振りを推論したプレーヤデータを作成する。
     * 
     * @param start
     * @param old_change
     * @param new_change
     */
    private void make_player_init( START start, TYPE[] old_change, TYPE[] new_change ) {
        Player myself = df.getMyself();
        int p = myself.getNumber();
        TYPE[] otherwise = otherwise_card( start.getNewCard(), old_change, new_change );
        int myrank = start.getRank();
        int target = getTradeTargetNumber( myrank, df.getRankList() );
        for ( Player player : df.getPlayers() ) {
            // 自分以外
            if ( p != player.getNumber() ) {
                PlayerData pd = new PlayerData( player.getName(), player.getNumber() );
                pd.isDaifugo = ( df.getRankList()[0] == pd.getNumber() );
                pd.card_count = pd.isDaifugo ? 14 : 13;
                // 初回以外なら、カードの交換を考慮する
                if ( start.getRound() > 0 ) {
                    Set<TYPE_DATA> td = new HashSet<TYPE_DATA>( otherwise.length + 1 );
                    if ( myrank == 1 || myrank == 4 ) {
                        // 交換相手の時
                        if ( player.getNumber() == target ) {
                            for ( TYPE t : otherwise ) {
                                // 相手に渡したカード。確実に保有している。
                                if ( old_change[0] == t || old_change[1] == t ) {
                                    TYPE_DATA d = new TYPE_DATA( t, 100 );
                                    td.add( d );
                                }
                                // 相手と交換したカードを保有リストから除外する。
                                else if ( new_change[0] != t && new_change[1] != t ) {
                                    // 初回レートは33% (1/3)
                                    TYPE_DATA d = new TYPE_DATA( t, 33 );
                                    td.add( d );
                                }
                            }
                        }
                        // 直接の関係者じゃない場合
                        else {
                            for ( TYPE t : otherwise ) {
                                // 自分と交換相手の手は分かるので保有リストから除外する。
                                if ( old_change[0] != t && old_change[1] != t && new_change[0] != t &&
                                    new_change[1] != t ) {
                                    // 初回レートは33% (1/3)
                                    TYPE_DATA d = new TYPE_DATA( t, 33 );
                                    td.add( d );
                                }
                            }
                        }
                    }
                    else if ( myrank == 2 || myrank == 3 ) {
                        // 交換相手の時
                        if ( player.getNumber() == target ) {
                            for ( TYPE t : otherwise ) {
                                // 相手に渡したカード。確実に保有している。
                                if ( old_change[0] == t ) {
                                    TYPE_DATA d = new TYPE_DATA( t, 100 );
                                    td.add( d );
                                }
                                // 相手とカードを保有リストから除外する。
                                else if ( new_change[0] != t ) {
                                    // 初回レートは33% (1/3)
                                    TYPE_DATA d = new TYPE_DATA( t, 33 );
                                    td.add( d );
                                }
                            }
                        }
                        // 直接の関係者じゃない場合
                        else {
                            for ( TYPE t : otherwise ) {
                                // 自分の交換相手の手は分かるので保有リストから除外する。
                                if ( old_change[0] != t && new_change[0] != t ) {
                                    // 初回レートは33% (1/3)
                                    TYPE_DATA d = new TYPE_DATA( t, 33 );
                                    td.add( d );
                                }
                            }
                        }
                    }
                    pd.card_list = td;
                }
                // 初回は愚直に全てのカードを他者へ割り当てる。
                else {
                    Set<TYPE_DATA> td = new HashSet<TYPE_DATA>( otherwise.length + 1 );
                    for ( TYPE t : otherwise ) {
                        // 初回レートは33% (1/3)
                        TYPE_DATA d = new TYPE_DATA( t, 33 );
                        td.add( d );
                    }
                    pd.card_list = td;
                }
                pd.refreshManyCardRate();
                this.player.add( pd );
            }
        }
    }

    /**
     * トレードする相手の番号を取得する。
     * 
     * @param myrank
     * @param ranklist
     * @return 相手番号
     */
    private int getTradeTargetNumber( int myrank, int[] ranklist ) {
        if ( myrank == 1 )
            return ranklist[3];
        if ( myrank == 2 )
            return ranklist[2];
        if ( myrank == 3 )
            return ranklist[1];
        if ( myrank == 4 ) {
            return ranklist[0];
        }
        return 0;
    }

    /**
     * 渡されたカード以外の全て（配布されたであろう）のカードを返す。
     * 
     * @param c
     * @return カードリスト
     * @param 結果に含めたいカード
     */
    private TYPE[] otherwise_card( Card c, TYPE[]... ops ) {
        TYPE[] values = TYPE.values();
        Set<TYPE> cards = new HashSet<TYPE>( values.length );
        for ( TYPE t : values ) {
            // パスはいらない。
            if ( !TYPE.PP.equals( t ) ) {
                cards.add( t );
            }
        }
        // 手持ちカードは消す
        for ( TYPE t : c.list ) {
            cards.remove( t );
        }
        // オプション追加
        for ( TYPE[] ts : ops ) {
            if ( ts != null ) {
                for ( TYPE t : ts ) {
                    cards.add( t );
                }
            }
        }
        return cards.toArray( new TYPE[] {} );
    }

    private static final TYPE[] PASS = new TYPE[] { TYPE.PP };

    /**
     * 阻止率を再構築する
     * 
     * @param play
     * @param count 有効者数
     */
    private void calc_blocking( final PLAY play, final int count ) {
        player.map( new CallBack() {
            @Override
            void eval( PlayerData target ) {
                if ( target.getNumber() == play.getNumber() ) {
                    if ( target.card_count != 0 ) {
                        // 出した手札が１枚の時
                        if ( play.getCard().list.length == 1 ) {
                            if ( play.getCard().equals( TYPE.PP ) ) {
                                PLAY last = played_list.getLastAvailablePlay( target.getNumber() );
                                if ( last != null ) {
                                    if ( last.getCard().list.length == 1 ) {
                                        target.updateHas1Rate( play, count );
                                    }
                                    else {
                                        calc_blocking_switch( target, last.getCard().list.length, play, count );
                                    }
                                }
                            }
                            else {
                                target.updateHas1Rate( play, count );
                            }
                        }
                        else {
                            calc_blocking_switch( target, play.getCard().list.length, play, count );
                        }
                        target.refreshManyCardRate();
                    }
                    if ( df.isDebug() ) {
                        df.foutln( target.toString() );
                        df.foutln( target.toStringHas2Rate() );
                        df.foutln( target.toStringHas3Rate() );
                        df.foutln( target.toStringHas4Rate() );
                    }
                }
                else {
                    target.refreshManyCardRate();
                }
            }
        } );
    }

    private void calc_blocking_switch( PlayerData target, int num, PLAY play, int count ) {
        switch ( num ) {
            case 2:
                target.updateHas2Rate( play, count );
                break;
            case 3:
                target.updateHas3Rate( play, count );
                break;
            case 4:
                target.updateHas4Rate( play, count );
                break;
        }
    }

    /**
     * 保守率を再構築する
     * 
     * @param card
     */
    private void calc_hosyu( Card card, boolean isRevolve ) {
        int total = 0;
        for ( TYPE t : card.list ) {
            total += t.intValue( isRevolve );
        }
        int base = 0;
        // 初回
        if ( df.getTurnCount() == 0 ) {
            // 大富豪
            if ( df.isDaifugo ) {
                base = AVE_1;
            }
            // その他
            else {
                base = AVE_2;
            }
        }
        // その他
        else {
            int p = df.getTurnCount() * DEFAULT_POWER;
            if ( df.isDaifugo ) {
                base = AVE_1 - p;
            }
            else {
                base = AVE_2 - p;
            }
        }
        hosyu = ( total * 50 ) / base;
    }

    public void turn( TURN turn ) {
        calc_hosyu( turn.getMyCards(), turn.isRevolve );
    }

    private PlayList played_list = new PlayList();

    /**
     * 場に出された手札を下にデータを再構築する。
     * 
     * @param play
     */
    public void play( final PLAY play ) {
        hold_all_cards.removeAll( Arrays.asList( play.getCard().list ) );
        final int num = play.getNumber();
        CallBack c = new CallBack() {
            int count = 0;

            @Override
            public void eval( PlayerData pd ) {
                // 手札を出した人はカード保有数を減らす
                if ( pd.number == num && !play.getCard().equals( PASS ) ) {
                    pd.card_count -= play.getCard().list.length;
                }
                // 上がりの時
                if ( pd.number == num && play.getRank() != 0 ) {
                    pd.card_count = 0;
                    pd.card_list.clear();
                    pd.has2rate.clear();
                    pd.has3rate.clear();
                    pd.has4rate.clear();
                }
                for ( TYPE t : play.getCard().list ) {
                    pd.card_list.remove( new TYPE_DATA( t, 0 ) );
                }
                if ( pd.card_count != 0 ) {
                    count++;
                }
            }
        };
        player.map( c );
        // 自分の手でない場合
        if ( play.getNumber() != df.getMyself().getNumber() ) {
            // 生存しているプレーヤが0でない場合
            if ( c.getProperty( Integer.class, "count" ).intValue() != 0 ) {
                calc_blocking( play, c.getProperty( Integer.class, "count" ).intValue() );
            }
        }
        played_list.add( play );
    }

    /**
     * 都落ちしたプレーヤの手札を元に、データを再構築する。
     * 
     * @param cast
     */
    public void cast( final CAST cast ) {
        player.map( new CallBack() {
            @Override
            void eval( PlayerData pd ) {
                if ( pd.number == cast.getNumber() ) {
                    pd.card_list.clear();
                    pd.card_count = 0;
                    pd.has2rate.clear();
                    pd.has3rate.clear();
                    pd.has4rate.clear();
                }
                else {
                    for ( TYPE t : cast.getCard().list ) {
                        pd.card_list.remove( new TYPE_DATA( t, 0 ) );
                    }
                }
            }
        } );
        hold_all_cards.removeAll( Arrays.asList( cast.getCard().list ) );
    }

    private class PlayList
        extends ArrayList<PLAY> {

        private static final long serialVersionUID = -159024813772069179L;

        private PlayList() {
            super();
        }

        private PlayList( Collection<? extends PLAY> c ) {
            super( c );
        }

        private PlayList( int initialCapacity ) {
            super( initialCapacity );
        }

        public PLAY getLastPlay() {
            return this.get( this.size() - 1 );
        }

        public PLAY getLastAvailablePlay( int number ) {
            for ( int i = this.size() - 1; i >= 0; i-- ) {
                PLAY p = this.get( i );
                if ( p.getNumber() == number ) {
                    return null;
                }
                if ( p.getCard().list[0] != TYPE.PP && !p.isStop ) {
                    return p;
                }
            }
            return null;
        }
    }

    private class PlayerDataList
        extends ArrayList<PlayerData> {

        private static final long serialVersionUID = 782997959185496189L;

        private PlayerDataList() {
            super();
        }

        private PlayerDataList( Collection<? extends PlayerData> c ) {
            super( c );
        }

        private PlayerDataList( int initialCapacity ) {
            super( initialCapacity );
        }

        public PlayerData getByNumber( int num ) {
            for ( PlayerData pd : this ) {
                if ( pd.getNumber() == num )
                    return pd;
            }
            return null;
        }

        public void map( CallBack c ) {
            for ( PlayerData pd : this ) {
                pd.execute( c );
            }
        }
    }

    /**
     * プレーヤーデータ構造
     * 
     * @author nori090
     * @version $Rev: 226 $ $Date: 2008-11-03 04:23:51 +0900 (Mon, 03 Nov 2008) $
     */
    private class PlayerData
        extends Player {
        Set<TYPE_DATA> card_list;

        Map<TYPE, PairData> has2rate = new HashMap<TYPE, PairData>();

        Map<TYPE, PairData> has3rate = new HashMap<TYPE, PairData>();

        Map<TYPE, PairData> has4rate = new HashMap<TYPE, PairData>();

        boolean isDaifugo = false;

        int card_count = 0;

        private PlayerData( String name, int number ) {
            super( name, number );
        }

        void execute( CallBack c ) {
            c.eval( this );
        }

        void updateHas1Rate( PLAY play, int count ) {
            TYPE default_type = getDefaultTYPE( play.getCard().list );
            if ( default_type == getDefaultTYPE( TYPE.C8 ) ) {
                return;
            }
            int holdrate = 4 - count;
            if ( df.getPlayCount() == 1 ) {
                // 初回は８切以外は、最小のカードを出してくるはず。
                // 出された手より小さい手は、持っていない可能性がある。
                int power = play.getCard().list[0].intValue( df.isRevolve );
                for ( TYPE_DATA data : this.card_list ) {
                    if ( data.type.intValue( df.isRevolve ) < power ) {
                        data.hold_rate -= holdrate;
                    }
                    else {
                        data.hold_rate += 1;
                    }
                }
            }
            else {
                if ( play.getCard().equals( TYPE.PP ) ) {
                    // 最後の有効なカード
                    PLAY last = played_list.getLastAvailablePlay( this.getNumber() );
                    // 場が切れてない場合
                    if ( last != null ) {
                        // 出されたカードが1枚だった場合
                        int power = last.getCard().list[0].intValue( df.isRevolve );
                        // そのカードパワー以上を持っていない可能性あり
                        for ( TYPE_DATA data : this.card_list ) {
                            if ( data.type.intValue( df.isRevolve ) > power ) {
                                data.hold_rate -= holdrate;
                            }
                            else {
                                data.hold_rate += 1;
                            }
                        }
                    }
                }
                // 手札を出した時
                else {
                    // 最後の有効なカード
                    PLAY last = played_list.getLastAvailablePlay( this.getNumber() );
                    // 有効な手が無い場合
                    if ( last == null ) {
                        // 新規で１枚出す
                        int power = play.getCard().list[0].intValue( df.isRevolve );
                        for ( TYPE_DATA data : this.card_list ) {
                            if ( data.type.intValue( df.isRevolve ) < power ) {
                                data.hold_rate -= holdrate;
                            }
                            else {
                                data.hold_rate += 1;
                            }
                        }
                    }
                    // 有効な手がある場合
                    else {
                        // 出されたカードが1枚だった場合
                        int low = last.getCard().list[0].intValue( df.isRevolve );
                        int high = play.getCard().list[0].intValue( df.isRevolve );
                        for ( TYPE_DATA data : this.card_list ) {
                            if ( data.type.intValue( df.isRevolve ) > low && data.type.intValue( df.isRevolve ) < high ) {
                                data.hold_rate -= holdrate;
                            }
                            else {
                                data.hold_rate += 1;
                            }
                        }
                    }
                }
            }
        }

        void refreshManyCardRate() {
            refreshHas2Rate();
            refreshHas3Rate();
            refreshHas4Rate();
        }

        void refreshHas2Rate() {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 2 );
            refreashHasNRate( has2rate, list );
        }

        void refreshHas3Rate() {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 3 );
            refreashHasNRate( has3rate, list );
        }

        void refreshHas4Rate() {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 4 );
            refreashHasNRate( has4rate, list );
        }

        private void refreashHasNRate( Map<TYPE, PairData> map, List<PairData> list ) {
            HashMap<TYPE, PairData> back = new HashMap<TYPE, PairData>( map );
            map.clear();
            for ( PairData data : list ) {
                PairData old = back.get( data.type );
                if ( old == null ) {
                    map.put( data.type, data );
                }
                else {
                    old.count = data.count;
                    map.put( old.type, old );
                }
            }
        }

        void updateHas2Rate( PLAY play, int count ) {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 2 );
            updateHasNRate( 2, count, has2rate, list, play );
        }

        void updateHas3Rate( PLAY play, int count ) {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 3 );
            updateHasNRate( 3, count, has3rate, list, play );
        }

        void updateHas4Rate( PLAY play, int count ) {
            List<PairData> list = new PowerTable().addAll( card_list ).nPair( 4 );
            updateHasNRate( 4, count, has4rate, list, play );
        }

        private void updateHasNRate( int num, int count, Map<TYPE, PairData> map, List<PairData> list, PLAY play ) {
            TYPE default_type = getDefaultTYPE( play.getCard().list );
            if ( default_type == getDefaultTYPE( TYPE.C8 ) ) {
                map.remove( default_type );
            }
            else {
                HashMap<TYPE, PairData> back = new HashMap<TYPE, PairData>( map );
                map.clear();
                int rate = num * count;
                if ( df.getPlayCount() == 1 ) {
                    int power = default_type.intValue( df.isRevolve );
                    for ( PairData data : list ) {
                        PairData old = back.get( data.type );
                        if ( old != null ) {
                            if ( old.count != data.count ) {
                                old.rate -= 9;
                            }
                            if ( old.type.intValue( df.isRevolve ) < power ) {
                                old.rate -= rate;
                            }
                            map.put( data.type, old );
                        }
                    }
                }
                else {
                    if ( play.getCard().equals( TYPE.PP ) ) {
                        PLAY last = played_list.getLastAvailablePlay( this.getNumber() );
                        if ( last != null ) {
                            int power = getDefaultTYPE( last.getCard().list ).intValue( df.isRevolve );
                            for ( PairData data : list ) {
                                PairData old = back.get( data.type );
                                if ( old != null ) {
                                    if ( old.count != data.count ) {
                                        old.rate -= 9;
                                    }
                                    if ( old.type.intValue( df.isRevolve ) > power ) {
                                        old.rate -= rate;
                                    }
                                    map.put( data.type, old );
                                }
                            }
                        }
                    }
                    else {
                        PLAY last = played_list.getLastAvailablePlay( this.getNumber() );
                        if ( last == null ) {
                            int power = getDefaultTYPE( play.getCard().list ).intValue( df.isRevolve );
                            for ( PairData data : list ) {
                                PairData old = back.get( data.type );
                                if ( old != null ) {
                                    if ( old.count != data.count ) {
                                        old.rate -= 9;
                                    }
                                    if ( old.type.intValue( df.isRevolve ) < power ) {
                                        old.rate -= rate;
                                    }
                                    map.put( data.type, old );
                                }
                            }
                        }
                        else {
                            int low = getDefaultTYPE( last.getCard().list ).intValue( df.isRevolve );
                            int high = getDefaultTYPE( play.getCard().list ).intValue( df.isRevolve );
                            for ( PairData data : list ) {
                                PairData old = back.get( data.type );
                                if ( old != null ) {
                                    if ( old.count != data.count ) {
                                        old.rate -= 9;
                                    }
                                    if ( old.type.intValue( df.isRevolve ) > low &&
                                        old.type.intValue( df.isRevolve ) < high ) {
                                        old.rate -= rate;
                                    }
                                    map.put( data.type, old );
                                }
                            }
                        }
                    }
                }
            }
        }

        private TYPE getDefaultTYPE( TYPE... types ) {
            if ( types.length == 1 ) {
                return mapping.get( types[0] );
            }
            for ( TYPE t : types ) {
                if ( TYPE.FF != t ) {
                    return mapping.get( t );
                }
            }
            return null;
        }

        private final HashMap<TYPE, TYPE> mapping = new HashMap<TYPE, TYPE>() {

            private static final long serialVersionUID = -6336685309009626129L;

            {
                super.put( TYPE.CA, TYPE.SA );
                super.put( TYPE.C2, TYPE.S2 );
                super.put( TYPE.C3, TYPE.S3 );
                super.put( TYPE.C4, TYPE.S4 );
                super.put( TYPE.C5, TYPE.S5 );
                super.put( TYPE.C6, TYPE.S6 );
                super.put( TYPE.C7, TYPE.S7 );
                super.put( TYPE.C8, TYPE.S8 );
                super.put( TYPE.C9, TYPE.S9 );
                super.put( TYPE.C0, TYPE.S0 );
                super.put( TYPE.CJ, TYPE.SJ );
                super.put( TYPE.CQ, TYPE.SQ );
                super.put( TYPE.CK, TYPE.SK );
                super.put( TYPE.HA, TYPE.SA );
                super.put( TYPE.H2, TYPE.S2 );
                super.put( TYPE.H3, TYPE.S3 );
                super.put( TYPE.H4, TYPE.S4 );
                super.put( TYPE.H5, TYPE.S5 );
                super.put( TYPE.H6, TYPE.S6 );
                super.put( TYPE.H7, TYPE.S7 );
                super.put( TYPE.H8, TYPE.S8 );
                super.put( TYPE.H9, TYPE.S9 );
                super.put( TYPE.H0, TYPE.S0 );
                super.put( TYPE.HJ, TYPE.SJ );
                super.put( TYPE.HQ, TYPE.SQ );
                super.put( TYPE.HK, TYPE.SK );
                super.put( TYPE.DA, TYPE.SA );
                super.put( TYPE.D2, TYPE.S2 );
                super.put( TYPE.D3, TYPE.S3 );
                super.put( TYPE.D4, TYPE.S4 );
                super.put( TYPE.D5, TYPE.S5 );
                super.put( TYPE.D6, TYPE.S6 );
                super.put( TYPE.D7, TYPE.S7 );
                super.put( TYPE.D8, TYPE.S8 );
                super.put( TYPE.D9, TYPE.S9 );
                super.put( TYPE.D0, TYPE.S0 );
                super.put( TYPE.DJ, TYPE.SJ );
                super.put( TYPE.DQ, TYPE.SQ );
                super.put( TYPE.DK, TYPE.SK );
                super.put( TYPE.SA, TYPE.SA );
                super.put( TYPE.S2, TYPE.S2 );
                super.put( TYPE.S3, TYPE.S3 );
                super.put( TYPE.S4, TYPE.S4 );
                super.put( TYPE.S5, TYPE.S5 );
                super.put( TYPE.S6, TYPE.S6 );
                super.put( TYPE.S7, TYPE.S7 );
                super.put( TYPE.S8, TYPE.S8 );
                super.put( TYPE.S9, TYPE.S9 );
                super.put( TYPE.S0, TYPE.S0 );
                super.put( TYPE.SJ, TYPE.SJ );
                super.put( TYPE.SQ, TYPE.SQ );
                super.put( TYPE.SK, TYPE.SK );
                super.put( TYPE.PP, TYPE.PP );
                super.put( TYPE.FF, TYPE.FF );
            }

            @Override
            public TYPE put( TYPE key, TYPE value ) {
                throw new NoSuchMethodError();
            }

            @Override
            public void putAll( Map<? extends TYPE, ? extends TYPE> m ) {
                throw new NoSuchMethodError();
            }
        };

        class PowerTable {
            HashMap<TYPE, HashSet<TYPE_DATA>> m = new HashMap<TYPE, HashSet<TYPE_DATA>>();

            PowerTable addAll( Set<TYPE_DATA> set ) {
                for ( TYPE_DATA data : set ) {
                    add( data );
                }
                return this;
            }

            PowerTable add( TYPE_DATA t ) {
                if ( m.containsKey( mapping.get( t.type ) ) ) {
                    m.get( mapping.get( t.type ) ).add( t );
                }
                else {
                    HashSet<TYPE_DATA> set = new HashSet<TYPE_DATA>();
                    set.add( t );
                    m.put( mapping.get( t.type ), set );
                }
                return this;
            }

            List<PairData> nPair( int pair ) {
                ArrayList<PairData> list = new ArrayList<PairData>();
                boolean hasJ = m.containsKey( TYPE.FF );
                for ( TYPE t : m.keySet() ) {
                    if ( t != TYPE.FF ) {
                        HashSet<TYPE_DATA> set = m.get( t );
                        int num = hasJ ? set.size() + 1 : set.size();
                        if ( num >= pair ) {
                            int rate100 = 0;
                            for ( TYPE_DATA d : set ) {
                                if ( d.hold_rate == 100 )
                                    rate100++;
                            }
                            PairData p = new PairData();
                            p.count = num / pair;
                            p.type = t;
                            p.rate = rate100 >= pair ? 100 : 50;
                            list.add( p );
                        }
                    }
                }
                return list;
            }
        }

        class PairData {
            // パワー
            TYPE type;

            // レート
            int rate;

            // ペアの数
            int count;

        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            TYPE_DATA[] list = card_list.toArray( new TYPE_DATA[] {} );
            Arrays.sort( list, new Comparator<TYPE_DATA>() {
                @Override
                public int compare( TYPE_DATA o1, TYPE_DATA o2 ) {
                    int i1 = o1.type.intValue();
                    int i2 = o2.type.intValue();
                    return ( i1 < i2 ? -1 : ( i1 == i2 ? 0 : 1 ) );
                }
            } );
            for ( TYPE_DATA data : list ) {
                sb.append( "[" ).append( data.type ).append( ":" ).append( data.hold_rate ).append( "]" );
            }
            return String.format( "%s: card[%d] data:%s", this.getName(), this.card_count, sb.toString() );
        }

        public String toStringHas2Rate() {
            return toStringHasNRate( "2", has2rate );
        }

        public String toStringHas3Rate() {
            return toStringHasNRate( "3", has3rate );
        }

        public String toStringHas4Rate() {
            return toStringHasNRate( "4", has4rate );
        }

        private String toStringHasNRate( String id, Map<TYPE, PairData> map ) {
            StringBuilder sb = new StringBuilder();
            TYPE[] list = map.keySet().toArray( new TYPE[] {} );
            Arrays.sort( list, Card.getPowerComparator( false, df.isRevolve ) );
            for ( TYPE t : list ) {
                PairData data = map.get( t );
                sb.append( "[" ).append( data.type ).append( ":" ).append( data.rate ).append( "]" );
            }
            return String.format( "%s: rate[%s] data:%s", this.getName(), id, sb.toString() );
        }
    }

    private abstract class CallBack {
        abstract void eval( PlayerData pd );

        @SuppressWarnings( "unchecked" )
        <T> T getProperty( Class<T> t, String name ) {
            try {
                Field f = this.getClass().getField( name );
                return (T) f.get( this );
            }
            catch ( Exception e ) {
                try {
                    Field f = this.getClass().getDeclaredField( name );
                    f.setAccessible( true );
                    return (T) f.get( this );
                }
                catch ( Exception e1 ) {
                    e1.printStackTrace();
                }
            }
            return null;
        }
    }

    /**
     * １枚のカードに対する確率
     * 
     * @author nori090
     * @version $Rev: 226 $ $Date: 2008-11-03 04:23:51 +0900 (Mon, 03 Nov 2008) $
     */
    private class TYPE_DATA {

        TYPE type;

        // 持っている確率
        int hold_rate;

        private TYPE_DATA( TYPE type, int blocking_rate ) {
            super();
            this.type = type;
            this.hold_rate = blocking_rate;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ( ( type == null ) ? 0 : type.hashCode() );
            return result;
        }

        @Override
        public boolean equals( Object obj ) {
            if ( this == obj )
                return true;
            if ( obj == null )
                return false;
            if ( getClass() != obj.getClass() )
                return false;
            final TYPE_DATA other = (TYPE_DATA) obj;
            if ( type == null ) {
                if ( other.type != null )
                    return false;
            }
            else if ( !type.equals( other.type ) )
                return false;
            return true;
        }
    }
}
