/*
 * 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.util.ArrayList;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * @author nori090
 * @version $Rev: 213 $ $Date: 2008-10-18 02:39:32 +0900 (Sat, 18 Oct 2008) $
 */
public abstract class Command {
    // コマンドタイプ
    public enum TYPE {
        UNKNOWN, INIT, DEAL, START, CAST, TURN, PLAY, END;
        static TYPE _valueOf( String type ) {
            for ( TYPE t : values() ) {
                if ( t.toString().toLowerCase().equals( type ) ) {
                    return t;
                }
            }
            return UNKNOWN;
        }

        Command create( String[] param ) {
            Command c = new UNKNOWN( param );
            switch ( this ) {
                case INIT:
                    c = new INIT( param );
                    break;
                case DEAL:
                    c = new DEAL( param );
                    break;
                case START:
                    c = new START( param );
                    break;
                case CAST:
                    c = new CAST( param );
                    break;
                case TURN:
                    c = new TURN( param );
                    break;
                case PLAY:
                    c = new PLAY( param );
                    break;
                case END:
                    c = new END( param );
                    break;
                case UNKNOWN:
                default:
                    break;
            }
            return c;
        }

        @SuppressWarnings( "unchecked" )
        Class getCommandClass() {
            Class c = UNKNOWN.class;
            switch ( this ) {
                case INIT:
                    c = INIT.class;
                    break;
                case START:
                    c = START.class;
                    break;
                case DEAL:
                    c = DEAL.class;
                    break;
                case CAST:
                    c = CAST.class;
                    break;
                case TURN:
                    c = TURN.class;
                    break;
                case PLAY:
                    c = PLAY.class;
                    break;
                case END:
                    c = END.class;
                    break;
                case UNKNOWN:
                default:
                    break;
            }
            return c;
        }
    }

    String raw;

    TYPE type;

    String[] parameter;

    public static Command create( String raw_str ) {
        StringTokenizer tokenizer = new StringTokenizer( raw_str );
        TYPE type = TYPE._valueOf( tokenizer.nextToken().toLowerCase() );
        ArrayList<String> list = new ArrayList<String>();
        while ( tokenizer.hasMoreTokens() ) {
            list.add( tokenizer.nextToken() );
        }
        String[] param = list.toArray( new String[] {} );
        Command c = type.create( param );
        c.raw = raw_str;
        c.type = type;
        c.parameter = param;
        return c;
    }

    String getRawString() {
        return raw;
    }

    TYPE getType() {
        return type;
    }

    String[] getParameter() {
        return parameter;
    }

    private static class UNKNOWN
        extends Command {
        public UNKNOWN( String[] param ) {
            // nop
        }
    }

    static class INIT
        extends Command {
        String filename;

        Random rand;

        Player[] players = new Player[4];

        int number;

        private INIT( String[] param ) {
            this.filename = param[0];
            this.rand = new Random( Integer.parseInt( param[1] ) );
            this.number = Integer.parseInt( param[2] );
            this.players[0] = new Player( param[3], 0 );
            this.players[1] = new Player( param[4], 1 );
            this.players[2] = new Player( param[5], 2 );
            this.players[3] = new Player( param[6], 3 );
        }

        public String getFilename() {
            return filename;
        }

        public Random getRand() {
            return rand;
        }

        public Player[] getPlayers() {
            return players;
        }

        public Player getMySelf() {
            return players[number];
        }
    }

    static class DEAL
        extends Command {
        // ラウンド数
        int round;

        // 前回のランク
        int rank;

        // 配られた手札
        Card deal;

        private DEAL( String[] param ) {
            this.round = Integer.parseInt( param[0] );
            this.rank = Integer.parseInt( param[1] );
            this.deal = new Card( param[2] );
        }

        public int getRound() {
            return round;
        }

        public int getRank() {
            return rank;
        }

        public Card getDeal() {
            return deal;
        }
    }

    static class CAST
        extends Command {
        // 渡すカード/都落ちしたプレイヤーが持っていたカード
        Card card;

        String comment;

        // 都落ちしたプレイヤー番号です。
        int number;

        // 都落ち後の順位です。常に4になります。
        int rank;

        /**
         * サーバーから送られてくるデータを想定したコンストラクタ
         * 
         * @param param
         */
        private CAST( String[] param ) {
            number = Integer.parseInt( param[0] );
            card = new Card( param[1] );
            rank = Integer.parseInt( param[2] );
        }

        /**
         * サーバーへ送信することを想定したコンストラクタ
         * 
         * @param c
         * @param comment
         * @throws DFException
         */
        public CAST( Card c, String comment )
            throws DFException {
            if ( c == null ) {
                throw new DFException( "カードが設定されていません。" );
            }
            this.card = c;
            this.comment = comment;
        }

        /**
         * クライアント側から送信する用
         * 
         * @return
         */
        public String createCommandString() {
            if ( comment == null ) {
                return String.format( "cast %s\n", card.toString() );
            }
            else {
                return String.format( "cast %s %s\n", card.toString(), comment );
            }
        }

        public Card getCard() {
            return card;
        }

        public int getNumber() {
            return number;
        }

        public int getRank() {
            return rank;
        }
    }

    static class START
        extends Command {
        // ラウンド数
        int round;

        // 前回のランク
        int rank;

        // 親プレーヤ番号
        int parent;

        // 交換前のカード
        Card oldCard;

        // 交換後のカード
        Card newCard;

        private START( String[] param ) {
            this.round = Integer.parseInt( param[0] );
            this.rank = Integer.parseInt( param[1] );
            this.parent = Integer.parseInt( param[2] );
            this.oldCard = new Card( param[3] );
            this.newCard = new Card( param[4] );
        }

        public int getRound() {
            return round;
        }

        public int getRank() {
            return rank;
        }

        public int getParent() {
            return parent;
        }

        public Card getOldCard() {
            return oldCard;
        }

        public Card getNewCard() {
            return newCard;
        }

    }

    static class TURN
        extends Command {
        // 自分の前の人が出した手
        Card last;

        // 革命状態にあるかどうかを表わします。falseの場合革命無し、trueの場合革命になります
        boolean isRevolve = false;

        // 自分の現在の手札です
        Card myCards;

        private TURN( String[] param ) {
            this.last = new Card( param[0] );
            this.isRevolve = ( "1".equals( param[1].trim() ) ) ? true : false;
            this.myCards = new Card( param[2] );
        }

        public Card getLast() {
            return last;
        }

        public boolean isRevolve() {
            return isRevolve;
        }

        public Card getMyCards() {
            return myCards;
        }

    }

    static class PLAY
        extends Command {

        int number;

        int rank;

        String comment;

        Card card;

        // この手札で場が切れた場合に1になります。
        // 場が継続する場合は0です。
        boolean isStop;

        private PLAY( String[] param ) {
            number = Integer.parseInt( param[0] );
            card = new Card( param[1] );
            rank = Integer.parseInt( param[2] );
            isStop = Integer.parseInt( param[3] ) == 1 ? true : false;
            comment = param.length > 4 ? param[4] : null;
        }

        public PLAY( Card c, String comment )
            throws DFException {
            if ( c == null ) {
                throw new DFException( "カードが設定されていません。" );
            }
            this.card = c;
            this.comment = comment;
        }

        public String createCommandString() {
            if ( comment == null ) {
                return String.format( "play %s\n", card.toString() );
            }
            else {
                return String.format( "play %s %s\n", card.toString(), comment );
            }
        }

        public String getComment() {
            return comment;
        }

        public void setComment( String comment ) {
            this.comment = comment;
        }

        public Card getCard() {
            return card;
        }

        public void setCard( Card card ) {
            this.card = card;
        }

        public int getNumber() {
            return number;
        }

        public int getRank() {
            return rank;
        }

        public boolean isStop() {
            return isStop;
        }
    }

    static class END
        extends Command {
        int round;

        boolean isExit;

        int[] rank = { 0, 0, 0, 0 };

        private END( String[] param ) {
            round = Integer.parseInt( param[0] );
            isExit = ( "1".equals( param[1].trim() ) ) ? true : false;
            rank[0] = Integer.parseInt( param[2] );
            rank[1] = Integer.parseInt( param[3] );
            rank[2] = Integer.parseInt( param[4] );
            rank[3] = Integer.parseInt( param[5] );
        }

        public int getRound() {
            return round;
        }

        public boolean isExit() {
            return isExit;
        }

        public int[] getRank() {
            return rank;
        }

    }

    @SuppressWarnings( "unchecked" )
    public static final <E> E cast( Object obj ) {
        return (E) obj;
    }
}
