/*
 * IRCWriter.java
 * IRCCore
 *
 * Created by tarchan on Aug 07, 2006.
 * Copyright (c) 2006 tarchan. All rights reserved.
 */
package com.mac.tarchan.irc;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashMap;

/**
 * IRC向けに調整した出力バッファです。改行コードは常にCRLFを出力します。一定時間のうちにしきい値以上のバイト数を出力しません。
 * 
 * @author tarchan
 * @author nori090
 */
public class IRCWriter
    extends PrintWriter {

    public enum CMD {
        /** PASS <password> */
        PASS() {
            public String format() {
                return "PASS %s";
            }
        },
        /** NICK <nickname> */
        NICK() {
            public String format() {
                return "NICK %s";
            }
        },
        /** USER <user> <mode> <unused> <realname> */
        USER() {
            public String format() {
                return "USER %s %d * :%s";
            }
        },
        /** QUIT [<quit message>] */
        QUIT() {
            public String format() {
                return "QUIT :%s";
            }
        },
        /** JOIN (<channel> *(","<channel>) [<key> *(","<key>)]) / "0" */
        JOIN() {
            public String format() {
                return "JOIN %s %s";
            }
        },
        /** PART <channel> *(","<channel>) [:<part message>] */
        PART() {
            public String format() {
                return "PART %s :%s";
            }
        },
        /** TOPIC <channel> [:<topic>] */
        TOPIC() {
            public String format() {
                return "TOPIC %s :%s";
            }
        },
        /** INVITE <nickname> <channel> */
        INVITE() {
            public String format() {
                return "INVITE %s %s";
            }
        },
        /** PRIVMSG <msgtarget> <text to be sent> */
        PRIVMSG() {
            public String format() {
                return "PRIVMSG %s :%s";
            }
        },
        /** NOTICE <msgtarget> <text to be sent> */
        NOTICE() {
            public String format() {
                return "NOTICE %s :%s";
            }
        },
        /** PONG <server1> [<server2>] */
        PONG() {
            public String format() {
                return "PONG %s";
            }
        };

        public abstract String format();
    }

    public IRCWriter( Writer out ) {
        super( out, true );
        for ( CMD c : CMD.values() ) {
            setFormat( c.name().toUpperCase(), c.format() );
        }
    }

    /** コマンドフォーマット */
    private HashMap<String, String> formatMap = new HashMap<String, String>();

    /**
     * ユーザー定義のフォーマットを登録する。
     * 
     * @param command
     * @param format
     * @return
     */
    public IRCWriter setFormat( String command, String format ) {
        formatMap.put( command.toUpperCase(), format );
        return this;
    }

    /**
     * {@link IRCWriter#setFormat(String, String)}で登録した名前の<br>
     * フォーマットを探します。無ければ引数をそのまま返します。
     * 
     * @param format
     * @return
     */
    private String getFormat( String format ) {
        String command = format.toUpperCase();
        return formatMap.containsKey( command ) ? formatMap.get( command ) : format;
    }

    /** IRC行区切り */
    private static final String CRLF = "\r\n";

    /**
     * {@link IRCWriter#setFormat(String, String)}で登録した<br>
     * フォーマットを元に 書き込みを行います。
     */
    @Override
    public PrintWriter format( String format, Object... args ) {
        format = getFormat( format );
        super.format( format + CRLF, args );
        return this;
    }

    /**
     * ビルトインのフォーマットを利用して書き込みを行います。
     * 
     * @param cmd
     * @param args
     * @return
     */
    public PrintWriter format( CMD cmd, Object... args ) {
        super.format( cmd.format() + CRLF, args );
        return this;
    }

    public void flush() {
        // penalty wait
        // 制限時間に達したら一回休み
        if ( isLimitTime() ) {
            try {
                Thread.sleep( PENALTY_TIME );
            }
            catch ( InterruptedException x ) {
                x.printStackTrace();
            }
        }
        addPenaltyTime();

        // super flush
        super.flush();
    }

    /** 最大持ち時間 */
    private static final int LIMIT_TIME = 10 * 1000;

    /** 単位ペナルティ時間 */
    private static final int PENALTY_TIME = 2 * 1000;

    /** 合計ペナルティ時間 */
    private long penaltyTime;

    protected boolean isLimitTime() {
        // get current time
        final long currentTime = System.currentTimeMillis();

        // message timer >= current time, always
        if ( penaltyTime < currentTime ) {
            penaltyTime = currentTime;
        }

        // message timer - current time < limit time, always
        long limitTime = penaltyTime - currentTime;
        if ( limitTime > LIMIT_TIME )
            limitTime = LIMIT_TIME;
        final int limitPercent = 100 * (int) limitTime / LIMIT_TIME;

        // System.out.println("penalty: " + (penaltyTime - currentTime) + " ms,"
        // + limitPercent + " %");

        return limitPercent >= 100;
    }

    protected void addPenaltyTime() {
        penaltyTime += PENALTY_TIME;
    }
}
