/*
 * Shell.java
 * IRCCore
 *
 * Created by tarchan on Nov 30, 2006.
 * Copyright (c) 2006 tarchan. All rights reserved.
 */
package com.mac.tarchan.misc;

import java.io.BufferedReader; // import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

import com.mac.tarchan.irc.IRCAddress;
import com.mac.tarchan.irc.IRCMessage;
import com.mac.tarchan.irc.IRCNamespace;
import com.mac.tarchan.irc.IRCNetwork;
import com.mac.tarchan.irc.IRCWriter;

/**
 * Shell
 * 
 * @author tarchan
 */
public class Shell {
    /**
     * Shell を起動します。
     * 
     * @param args IRCアドレス
     */
    public static void main( String... args ) {
        String address = null;
        if ( args.length >= 1 ) {
            address = args[0];
            // console.println("address=" + address);
        }
        new Shell().openConsole( address );
    }

    // 環境変数
    private HashMap<String, String> env = new HashMap<String, String>();

    private static final String DEBUG = "debug";

    private static final String ECHO = "echo";

    private static final String MULTI = "multi";

    private static final String IRC_HOST = "irc.host";

    private static final String IRC_PASS = "irc.pass";

    private static final String IRC_USER = "irc.user";

    private static final String IRC_NICK = "irc.nick";

    private static final String IRC_REAL = "irc.real";

    private static final String IRC_MODE = "irc.mode";

    private static final String IRC_CHANNEL = "irc.channel";

    public void openConsole( String address ) {
        /**
         * プロキシ
         * 
         * @see http://download.java.net/jdk/jdk-api-localizations/jdk-api-ja/builds/latest/html/ja/technotes/guides/net/proxies.html
         */
        System.setProperty( "java.net.useSystemProxies", "true" );
        put( "http.proxySet", System.getProperty( "http.proxySet" ) );
        put( "http.proxyHost", System.getProperty( "http.proxyHost" ) );
        put( "http.proxyPort", System.getProperty( "http.proxyPort" ) );
        put( "http.nonProxyHosts", System.getProperty( "http.nonProxyHosts" ) );
        put( "socksProxyHost", System.getProperty( "socksProxyHost" ) );
        put( "socksProxyPort", System.getProperty( "socksProxyPort" ) );

        put( "java.version", System.getProperty( "java.version" ) );
        put( "user.dir", System.getProperty( "user.dir" ) );
        // put(DEBUG, "on");
        put( DEBUG, "off" );
        put( ECHO, "off" );
        put( MULTI, "off" );
        put( IRC_HOST, "irc.tokyo.wide.ad.jp" );
        put( IRC_PASS, "" );
        put( IRC_USER, System.getProperty( "user.name" ) );
        put( IRC_NICK, get( IRC_USER ) );
        put( IRC_REAL, "IRCCore 2.0" );
        put( IRC_MODE, "" );
        put( IRC_CHANNEL, "#javabreak" );
        // put(IRC_CHANNEL, "");

        // console.println("Console=" + console);
        if ( console == null ) {
            System.err.println( "console is null" );
            System.exit( 1 );
        }
        console.printf( "Hello IRC network!\n" );
        console.printf( "Java version %s\n", System.getProperty( "java.version" ) );

        // IRCアドレスが指定された場合はログインする
        if ( address != null )
            login( "", address );
        run();
    }

    private static final Pattern SET_PATTERN = Pattern.compile( "/set" );

    private static final Pattern GET_PATTERN = Pattern.compile( "/get" );

    private static final Pattern JOIN_PATTERN = Pattern.compile( "/join" );

    private static final Pattern LOGIN_PATTERN = Pattern.compile( "/login" );

    private static final Pattern NOTICE_PATTERN = Pattern.compile( "/notice" );

    private static final Pattern QUIT_PATTERN = Pattern.compile( "/quit" );

    private static final Pattern EXIT_PATTERN = Pattern.compile( "/exit" );

    private static final Pattern OTHER_PATTERN = Pattern.compile( "/[a-zA-Z]" );

    // private static final MessageFormat TIME_FORMAT = new MessageFormat("{0,time,HH:mm}");
    private static final MessageFormat MSG_FORMAT = new MessageFormat( "{0,time,HH:mm} <{1}:{2}> {3}" );

    private static final MessageFormat NOTE_FORMAT = new MessageFormat( "{0,time,HH:mm} <<{1}:{2}>> {3}" );

    private String encoding = "iso2022jp";

    // private Console console = System.console();
    private PrintWriter console = new PrintWriter( System.out, true );

    private BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) );

    private IRCNetwork irc = IRCNetwork.getNetwork( "default" );

    private IRCWriter out;

    // public Object client = new Object()
    // {
    private void debug( IRCMessage message ) {
        // デバッグモードの場合
        boolean debug = getBoolean( DEBUG );
        if ( debug )
            console.printf( "%s\n", message.toString() );
    }

    public void reply( IRCMessage message ) {
        // デバッグモードの場合
        boolean debug = getBoolean( DEBUG );
        if ( debug )
            console.printf( "%s\n", message.toString() );
        // else return;

        // その他のコマンド
        String command = message.getCommand();
        String trail = message.getTrailing( encoding );
        console.printf( "%s: %s\n", command, trail );
    }

    public void replyPing( IRCMessage message ) {
        // console.printf("PING %s\n", message.getTrailing());
    }

    public void replyError( IRCMessage message ) {
        String command = message.getCommand();
        String trail = message.getTrailing();
        console.printf( "%s: %s\n", command, trail );
    }

    public void reply001( IRCMessage message ) {
        debug( message );
        String command = message.getCommand();
        String trail = message.getTrailing( encoding );
        console.printf( "%s: %s\n", command, trail );
        // console.printf("Join us!\n");
        String channel = get( IRC_CHANNEL );
        out.format( "JOIN", channel, "" );
    }

    private void printmsg( MessageFormat format, IRCMessage message ) {
        debug( message );
        // メッセージを表示
        String channel = message.getTarget();
        String trail = message.getTrailing( encoding );
        String currentChannel = get( IRC_CHANNEL );
        String currentNick = get( IRC_NICK );

        // 現在のチャンネル向けでない場合は表示しない
        boolean multi = getBoolean( MULTI );
        if ( !multi && !channel.equals( ( currentChannel ) ) && !channel.equals( currentNick ) )
            return;

        String[] ctcp = IRCMessage.splitCTCP( message.getMessage() );
        System.out.println( Arrays.asList( ctcp ) );
        if ( ctcp.length >= 2 ) {
        }

        String nick = message.getNick();
        // String time = TIME_FORMAT.format(new Object[]{new Date()});
        // console.printf("%s <%s:%s> %s\n", time, channel, nick, trail);
        console.printf( "%s\n", format.format( new Object[] { new Date(), channel, nick, trail } ) );
    }

    public void replyPrivmsg( IRCMessage message ) {
        printmsg( MSG_FORMAT, message );
    }

    public void replyNotice( IRCMessage message ) {
        printmsg( NOTE_FORMAT, message );
    }

    public void replyNick( IRCMessage message ) {
        debug( message );
        // ニックネームを変更
        String newNick = message.getTrailing();
        String oldNick = message.getNick();
        console.printf( "%s -> %s\n", oldNick, newNick );

        // 自分のニックネーム変更の場合はデータを更新
        String myNick = get( IRC_NICK );
        if ( oldNick.equals( myNick ) ) {
            put( IRC_NICK, newNick );
        }
    }

    public void replyJoin( IRCMessage message ) {
        debug( message );
        // チャンネルに参加
        String nick = message.getNick();
        String user = message.getUser();
        String host = message.getHost();
        String channel = message.getTrailing();
        // + sumisumi(~sumisumi@53.66.30.125.dy.iij4u.or.jp) to %irodorie
        console.printf( "+ %s(%s@%s) to %s\n", nick, user, host, channel );
    }

    public void replyPart( IRCMessage message ) {
        debug( message );
        // チャンネルを閉じる
        String nick = message.getNick();
        String reason = message.getTrailing( encoding );
        // ! sumisumi (Connection reset by peer)
        console.printf( "! %s (%s)\n", nick, reason );
    }

    public void replyQuit( IRCMessage message ) {
        replyPart( message );
    }

    public void replyMode( IRCMessage message ) {
        debug( message );
        // モードを変更
        String nick = message.getNick();
        String channel = message.getTarget();
        if ( nick.equals( channel ) ) {
            // ユーザモードを変更
            String mode = message.getTrailing();
            console.printf( "Mode by Server: %s %s\n", mode, nick );
        }
        else {
            // チャンネルモードを変更
            String mode = message.getParam( 1 );
            List<String> list = message.getParamsAsList( 2 );
            String target = null;
            for ( String user : list ) {
                if ( target == null )
                    target = user;
                else
                    target += " " + user;
            }
            // Mode by SIMA_WK: %irodorie +o sumisumi
            console.printf( "Mode by %s: %s %s\n", nick, mode, target );
        }
    }

    // };

    /**
     * コンソールからの入力を読み込みします。
     */
    public void run() {
        while ( true ) {
            try {
                // input
                String channel = get( IRC_CHANNEL );
                String nick = get( IRC_NICK );
                // String line = console.readLine("%s %s$ ", channel, nick);
                console.printf( "%s:%s$ ", channel, nick );
                String line = reader.readLine();
                ;

                // echo
                boolean echo = getBoolean( ECHO );
                if ( echo )
                    console.printf( "%s\n", line );

                // parse
                // params[0] は入力全体
                String[] params = line.split( " ", 3 );
                params[0] = line;
                if ( SET_PATTERN.matcher( line ).find() )
                    set( params );
                else if ( GET_PATTERN.matcher( line ).find() )
                    get( params );
                else if ( JOIN_PATTERN.matcher( line ).find() )
                    join( params );
                else if ( LOGIN_PATTERN.matcher( line ).find() )
                    login( params );
                else if ( QUIT_PATTERN.matcher( line ).find() )
                    quit( params );
                else if ( EXIT_PATTERN.matcher( line ).find() )
                    exit( params );
                else if ( NOTICE_PATTERN.matcher( line ).find() )
                    notice( params );
                else if ( OTHER_PATTERN.matcher( line ).find() )
                    command( params );
                else
                    privmsg( params );
            }
            catch ( RuntimeException x ) {
                console.println( "Client Error: " + x.toString() );
                // x.printStackTrace();
            }
            catch ( IOException x ) {
                console.println( "Read Line Error: " + x.toString() );
                // x.printStackTrace();
            }
        }
    }

    public String put( String key, String value ) {
        return env.put( key, value );
    }

    public String get( String key ) {
        String value = env.get( key );
        if ( value == null )
            value = System.getProperty( key );
        return value;
    }

    public boolean getBoolean( String key ) {
        String value = get( key );
        if ( value == null )
            return false;
        return value.equals( "on" ) || value.equals( "true" );
    }

    public void set( String... params ) {
        if ( params.length < 3 ) {
            console.printf( "!print values\n" );
            for ( String key : env.keySet() ) {
                String value = env.get( key );
                console.printf( "%s=%s\n", key, value );
            }
        }
        else {
            console.printf( "!set %s='%s'\n", params[1], params[2] );
            put( params[1], params[2] );
        }
    }

    public void get( String... params ) {
        String key = params[1];
        String value = get( key );
        console.printf( "%s=%s\n", key, value );
    }

    public void join( String... params ) {
        if ( params.length <= 1 ) {
            console.printf( "!current channel: %s\n", get( IRC_CHANNEL ) );
        }
        else {
            String channel = params[1];
            channel = IRCNamespace.normalizeChannel( channel, "jp" );
            String keyword = params.length > 2 ? params[2] : "";
            console.printf( "!change channel: %s %s\n", channel, keyword );
            put( IRC_CHANNEL, channel );
            out.format( "JOIN", channel, keyword );
        }
    }

    public void login( String... params ) {
        if ( params.length < 2 ) {
            console.printf( "Usage: /login [address]\n" );
            return;
        }

        console.printf( "!login to %s\n", params[1] );
        IRCAddress addr = new IRCAddress( params[1] );
        String channel = addr.getChannel();

        addr.setPassword( get( IRC_PASS ) );
        addr.setUser( get( IRC_USER ) );
        addr.setNick( get( IRC_NICK ) );
        addr.setReal( get( IRC_REAL ) );
        addr.setMode( 0xf );
        // addr.setChannel(get(IRC_CHANNEL));
        // console.printf("address=%s\n", addr);
        // irc.setClient(client);
        irc.addClient( this );
        irc.setLocale( "jp" );
        irc.login( addr );
        out = irc.writer( encoding );

        // チャンネルが指定された場合は移動する
        // console.println("channel=" + channel);
        // if (channel != null) join("", channel);
        if ( channel != null )
            put( IRC_CHANNEL, channel );
    }

    public void privmsg( String... params ) {
        String line = params[0];
        if ( line.trim().length() == 0 )
            return;

        String channel = get( IRC_CHANNEL );
        String nick = get( IRC_NICK );
        // console.printf("!privmsg: %s\n", channel);
        // line = IRCMessage.encode(line, encoding);
        console.printf( "%s\n", MSG_FORMAT.format( new Object[] { new Date(), channel, nick, line } ) );
        // irc.privmsg(channel, line);
        out.format( "PRIVMSG", channel, line );
    }

    public void notice( String... params ) {
        String line = params[0];
        if ( line.trim().length() == 0 )
            return;

        String channel = get( IRC_CHANNEL );
        // String[] params = line.split(" ", 3);
        switch ( params.length ) {
            case 1:
                // パラメータ不足なので何もしない
                return;
            case 2:
                line = params[1];
                break;
            case 3:
                if ( IRCNamespace.isChannel( params[1] ) ) {
                    channel = params[1];
                    line = params[2];
                }
                else {
                    line = params[1] + " " + params[2];
                    // line = line.split(" ", 2)[1];
                }
                break;
            default:
                throw new IllegalArgumentException( "input=" + line );
        }
        console.printf( "!notice %s %s %s\n", channel, line, Arrays.asList( params ) );
        // irc.notice(channel, line);
        out.format( "NOTICE", channel, line );
    }

    public void command( String... params ) {
        String line = params[0].substring( 1 );
        console.printf( "!command: %s\n", line );
        // irc.writeLine(line);
        out.format( line );
    }

    public void quit( String... params ) {
        console.printf( "!quit\n" );
        String[] token = params[0].split( " ", 2 );
        String reason = token.length == 2 ? token[1] : "";
        out.format( "QUIT", reason );
        // if (token.length <= 1)
        // {
        // irc.writeCommand("QUIT");
        // }
        // else
        // {
        // irc.writeCommand("QUIT", token[1]);
        // }
    }

    public void exit( String... params ) {
        console.printf( "!exit\n" );
        System.exit( 0 );
    }
}
