/*
 * 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.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.Random;

import org.coderepos.nori090.df.Command.CAST;

/**
 * @author nori090
 * @version $Rev: 220 $ $Date: 2008-10-19 09:41:10 +0900 (Sun, 19 Oct 2008) $
 */
public abstract class DFAbst {

    private final boolean debug = Boolean.parseBoolean( System.getProperty( "arekuma.df.debug", "false" ) );

    private final boolean trace = Boolean.parseBoolean( System.getProperty( "arekuma.df.trace", "false" ) );

    /** 大富豪プロトコルバージョン */
    public static final long VERSION = 2L;

    private Player myself;

    private Player[] players;

    private Player[] _cache;

    private int[] _cache_rank;

    private String filename;

    private FileWriter fout;

    private Random rand;

    private int turnCount = 0;

    private int playCount = 0;

    private final String separator = System.getProperty( "line.separator" );

    protected boolean isDaifugo = false;

    protected boolean isRevolve = false;

    BufferedReader br = new BufferedReader( new InputStreamReader( System.in, Charset.forName( "sjis" ) ) );

    static PrintStream out = System.out;

    public DFAbst( String name ) {
        if ( name == null || "".equals( name.trim() ) )
            throw new IllegalArgumentException( "名前は必須です。" );
        if ( name.indexOf( " " ) != -1 )
            throw new IllegalArgumentException( "名前にブランクは入れられません。" );
        this.myself = new Player( name, 0 );
        _init();
    }

    /**
     * 大富豪ＡＩの初期化処理です。インスタンス生成時に１度だけ呼び出される。はず。
     */
    abstract void init();

    /**
     * 自分の番になった時に呼び出されます。
     * 
     * @param turn
     * @return 場に出す手札
     */
    abstract Command.PLAY turn( Command.TURN turn );

    /**
     * ゲームが開始される時に呼び出されます。（ラウンドの開始合図）
     * 
     * @param start
     */
    abstract void start( Command.START start );

    /**
     * 他者が場に手札を出した場合に呼び出されます。
     * 
     * @param play
     */
    abstract void play( Command.PLAY play );

    /**
     * ゲーム開始時に自分が大富豪、富豪のみ呼び出されます。<br>
     * 大富豪の場合は、２枚、富豪の場合は１枚の手札をCASTコマンドで返します。
     * 
     * @param deal
     * @return 貧民、大貧民に差し出す手札
     */
    abstract Command.CAST deal( Command.DEAL deal );

    /**
     * 大富豪だったプレイヤーが都落ちした時に呼び出されます。
     * 
     * @param cast 都落ちしたプレイヤーの手札
     */
    abstract void cast( CAST cast );

    private void _init() {
        hello_c();
        Command c = read();
        while ( c.type != Command.TYPE.INIT ) {
            c = read();
        }
        Command.INIT init = Command.cast( c );
        this.filename = init.getFilename();
        try {
            fout = new FileWriter( this.filename, true );
        }
        catch ( IOException e ) {
        }
        this.myself = init.getMySelf();
        this.players = init.getPlayers();
        this.rand = init.getRand();
        init();
        start();
    }

    private void start() {
        boolean exit = false;

        while ( !exit ) {
            Command c = read();
            while ( c.type != Command.TYPE.START && c.type != Command.TYPE.DEAL ) {
                c = read();
            }
            if ( c.type == Command.TYPE.DEAL ) {
                Command.DEAL deal = Command.cast( c );
                if ( deal.getRank() == 1 || deal.getRank() == 2 ) {
                    Command.CAST cast = deal( deal );
                    write( cast.createCommandString() );
                }
                while ( c.type != Command.TYPE.START ) {
                    c = read();
                }
            }
            Command.START start = Command.cast( c );
            isDaifugo = start.getNewCard().list.length == 14 ? true : false;
            isRevolve = false;
            turnCount = 0;
            playCount = 0;
            start( start );
            c = read();
            while ( c.type != Command.TYPE.END ) {
                if ( c.type == Command.TYPE.PLAY ) {
                    playCount++;
                    Command.PLAY play = Command.cast( c );
                    if ( play.getCard().list.length == 4 )
                        isRevolve = true;
                    play( play );
                }
                else if ( c.type == Command.TYPE.CAST ) {
                    Command.CAST cast = Command.cast( c );
                    cast( cast );
                }
                else if ( c.type == Command.TYPE.TURN ) {
                    turnCount++;
                    Command.TURN turn = Command.cast( c );
                    isRevolve = turn.isRevolve;
                    Command.PLAY play = turn( turn );
                    write( play.createCommandString() );
                }
                else {
                    System.err.println( "unknown err" );
                }
                c = read();
            }
            Command.END end = Command.cast( c );
            if ( end.isExit ) {
                exit = true;
            }
            _cache_rank = end.getRank();
            players[_cache_rank[0]].addPoint( 3 );
            players[_cache_rank[1]].addPoint( 2 );
            players[_cache_rank[2]].addPoint( 1 );
            foutln( "途中経過" );
            foutln( "\t" + players[0].toString() );
            foutln( "\t" + players[1].toString() );
            foutln( "\t" + players[2].toString() );
            foutln( "\t" + players[3].toString() );
        }
        foutln( players[0].toString() );
        foutln( players[1].toString() );
        foutln( players[2].toString() );
        foutln( players[3].toString() );
        if ( fout != null ) {
            try {
                fout.flush();
                fout.close();
            }
            catch ( IOException e ) {
            }
        }
    }

    public Player getMyself() {
        return myself;
    }

    public Player[] getPlayers() {
        if ( _cache == null ) {
            _cache = new Player[players.length];
            System.arraycopy( players, 0, _cache, 0, players.length );
        }
        return _cache;
    }

    /**
     * 前回のランクを返す（次回の開始順）
     * 
     * @return [0=>1位の番号, 1=>2位の番号, 2=>3位の番号, 3=>4位の番号]
     */
    public int[] getRankList() {
        if ( _cache_rank == null ) {
            _cache_rank = new int[] { 0, 0, 0, 0 };
        }
        return _cache_rank;
    }

    public String getFilename() {
        return filename;
    }

    private Command read() {
        try {
            return Command.create( br.readLine() );
        }
        catch ( Exception e ) {
            throw new RuntimeException( e );
        }
    }

    private void hello_c( String name, long version ) {
        write( String.format( "hello %s %d\n", name, version ) );
    }

    private void hello_c() {
        hello_c( this.myself.getName(), VERSION );
    }

    private void write( String s ) {
        try {
            out.write( s.getBytes( Charset.forName( "sjis" ) ) );
            out.flush();
        }
        catch ( IOException e ) {
            throw new RuntimeException( e );
        }
    }

    protected void fout( String s ) {
        if ( fout != null ) {
            try {
                fout.write( s );
                fout.flush();
            }
            catch ( IOException e ) {
                e.printStackTrace();
            }
        }
    }

    protected void foutln( String s ) {
        fout( s );
        fout( separator );
    }

    public int getTurnCount() {
        return turnCount;
    }

    public int getPlayCount() {
        return playCount;
    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isTrace() {
        return trace;
    }

}
