package imdst.client;

import java.util.*;
import java.io.*;
import java.net.*;

import org.batch.lang.BatchException;
import imdst.util.ImdstDefine;

public class ImdstKeyValueClient {

    // ソケット
    private Socket socket = null;

    // サーバへの出力用
    private PrintWriter pw = null;

    // サーバからの受信用
    private BufferedReader br = null;

    // データセパレータ文字列
    private static final String sepStr = ImdstDefine.keyHelperClientParamSep;

    // 接続時のデフォルトのエンコーディング
    private static final String connectDefaultEncoding = ImdstDefine.keyHelperClientParamEncoding;

    // ブランク文字列の代行文字列
    private static final String blankStr = ImdstDefine.imdstBlankStrData;

    // 接続要求切断文字列
    private static final String connectExitStr = ImdstDefine.imdstConnectExitRequest;

    // Tagで取得出来るキー値のセパレータ文字列
    private static final String tagKeySep = ImdstDefine.imdstTagKeyAppendSep;

    /**
     * コンストラクタ
     *
     */
    public ImdstKeyValueClient() {
    }

    /**
     * 接続処理.<br>
     * エンコーディング指定なし.<br>
     *
     * @param server
     * @param port
     * @throws BatchException
     */
    public void connect(String server, int port) throws BatchException {
        this.connect(server, port, ImdstKeyValueClient.connectDefaultEncoding);
    }

    /**
     * 接続処理.<br>
     * エンコーディング指定有り.<br>
     *
     * @param server
     * @param port
     * @param encoding
     * @throws BatchException
     */
    public void connect(String server, int port, String encoding) throws BatchException {
        try {
            this.socket = new Socket(server, port);
 
            this.pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), encoding)));
            this.br = new BufferedReader(new InputStreamReader(socket.getInputStream(), encoding));

        } catch (Exception e) {
            try {
                if (this.br != null) {
                    this.br.close();
                    this.br = null;
                }

                if (this.pw != null) {
                    this.pw.close();
                    this.pw = null;
                }

                if (this.socket != null) {
                    this.socket.close();
                    this.socket = null;
                }
            } catch (Exception e2) {
                // 無視
                this.socket = null;
            }
            throw new BatchException(e);
        }
    }

    public void close() throws BatchException {
        try {
            if (this.pw != null) {
                // 接続切断を通知
                this.pw.println(connectExitStr);
                this.pw.flush();

                this.pw.close();
                this.pw = null;
            }

            if (this.br != null) {
                this.br.close();
                this.br = null;
            }


            if (this.socket != null) {
                this.socket.close();
                this.socket = null;
            }
        } catch (Exception e) {
            throw new BatchException(e);
        }
    }

    /**
     * Serverへデータを送信する.<br>
     * Tagなし.<br>
     *
     * @param keyStr
     * @param value
     * @return boolean
     * @throws BatchException
     */
    public boolean setValue(String keyStr, String value) throws BatchException {
        return this.setValue(keyStr, null, value);
    }

    /**
     * Serverへデータを送信する.<br>
     * Tag有り.<br>
     *
     * @param keyStr
     * @param tagStrs
     * @param value
     * @return boolean
     * @throws BatchException
     */
    public boolean setValue(String keyStr, String[] tagStrs, String value) throws BatchException {
        boolean ret = false; 
        String serverRetStr = null;
        String[] serverRet = null;

        StringBuffer serverRequestBuf = null;

        try {
            if (this.socket == null) throw new BatchException("No ServerConnect!!");

            // エラーチェック
            // Keyに対する無指定チェック
            if (keyStr == null ||  keyStr.equals("")) {
                throw new BatchException("The blank is not admitted on a key");
            }

            // valueに対する無指定チェック(Valueはnullやブランクの場合は代行文字列に置き換える)
            if (value == null ||  value.equals("")) {
                value = ImdstKeyValueClient.blankStr;
            }


            // 文字列バッファ初期化
            serverRequestBuf = new StringBuffer();


            // 処理番号連結
            serverRequestBuf.append("1");
            // セパレータ連結
            serverRequestBuf.append(ImdstKeyValueClient.sepStr);


            // Key連結(Keyはデータ送信時には必ず文字列が必要)
            serverRequestBuf.append(keyStr);
            // セパレータ連結
            serverRequestBuf.append(ImdstKeyValueClient.sepStr);


            // Tag連結
            // Tag指定の有無を調べる
            if (tagStrs == null || tagStrs.length < 1) {

                // ブランク規定文字列を連結
                serverRequestBuf.append(ImdstKeyValueClient.blankStr);
            } else {

                // Tag数分連結
                serverRequestBuf.append(tagStrs[0]);
                for (int i = 1; i < tagStrs.length; i++) {
                    serverRequestBuf.append(tagKeySep);
                    serverRequestBuf.append(tagStrs[i]);
                }
            }

            // セパレータ連結
            serverRequestBuf.append(ImdstKeyValueClient.sepStr);


            // Value連結
            // Key連結(Keyはデータ送信時には必ず文字列が必要)
            serverRequestBuf.append(value);

            // サーバ送信
            pw.println(serverRequestBuf.toString());
            pw.flush();

            // サーバから結果受け取り
            serverRetStr = br.readLine();

            serverRet = serverRetStr.split(ImdstKeyValueClient.sepStr);

            // 処理の妥当性確認
            if (serverRet.length == 3 && serverRet[0].equals("1")) {
                if (serverRet[1].equals("true")) {

                    // 処理成功
                    ret = true;
                } else{

                    // 処理失敗(メッセージ格納)
                    throw new BatchException(serverRet[1]);
                }
            } else {
                
                // 妥当性違反
                throw new BatchException("Execute Violation of validity [" + serverRetStr + "]");
            }
        } catch (BatchException be){
            throw be;
        } catch (Exception e) {
            throw new BatchException(e);
        }
        return ret;
    }


    /**
     * ServerからKeyでデータを取得する.<br>
     * Tag有り.<br>
     *
     * @param keyStr
     * @return String[] 要素1(データ有無):"true" or "false",要素2(データ):"データ文字列"
     * @throws BatchException
     */
    public String[] getValue(String keyStr) throws BatchException {
        String[] ret = new String[2]; 
        String serverRetStr = null;
        String[] serverRet = null;

        StringBuffer serverRequestBuf = null;

        try {
            if (this.socket == null) throw new BatchException("No ServerConnect!!");

            // エラーチェック
            // Keyに対する無指定チェック
            if (keyStr == null ||  keyStr.equals("")) {
                throw new BatchException("The blank is not admitted on a key");
            }

            // 文字列バッファ初期化
            serverRequestBuf = new StringBuffer();


            // 処理番号連結
            serverRequestBuf.append("2");
            // セパレータ連結
            serverRequestBuf.append(ImdstKeyValueClient.sepStr);


            // Key連結(Keyはデータ送信時には必ず文字列が必要)
            serverRequestBuf.append(keyStr);


            // サーバ送信
            pw.println(serverRequestBuf.toString());
            pw.flush();

            // サーバから結果受け取り
            serverRetStr = br.readLine();

            serverRet = serverRetStr.split(ImdstKeyValueClient.sepStr);

            // 処理の妥当性確認
            if (serverRet[0].equals("2")) {
                if (serverRet[1].equals("true")) {

                    // データ有り
                    ret[0] = serverRet[1];

                    // Valueがブランク文字か調べる
                    if (serverRet[2].equals(ImdstKeyValueClient.blankStr)) {
                        ret[1] = "";
                    } else {
                        ret[1] = serverRet[2];
                    }
                } else if(serverRet[1].equals("false")) {

                    // データなし
                    ret[0] = serverRet[1];
                    ret[1] = null;
                } else if(serverRet[1].equals("error")) {

                    // エラー発生
                    ret[0] = serverRet[1];
                    ret[1] = serverRet[2];
                }
            } else {

                // 妥当性違反
                throw new BatchException("Execute Violation of validity");
            }
        } catch (BatchException be){
            throw be;
        } catch (Exception e) {
            throw new BatchException(e);
        }
        return ret;
    }


    /**
     * ServerからTagでKey値群を取得する.<br>
     *
     * @param tagStr
     * @return Object[] 要素1(データ有無):"true" or "false",要素2(データ):"データ文字列"
     * @throws BatchException
     */
    public Object[] getTagKeys(String tagStr) throws BatchException {
        Object[] ret = new Object[2]; 
        String serverRetStr = null;
        String[] serverRet = null;

        StringBuffer serverRequestBuf = null;

        try {
            if (this.socket == null) throw new BatchException("No ServerConnect!!");

            // エラーチェック
            // Keyに対する無指定チェック
            if (tagStr == null ||  tagStr.equals("")) {
                throw new BatchException("The blank is not admitted on a tag");
            }

            // 文字列バッファ初期化
            serverRequestBuf = new StringBuffer();


            // 処理番号連結
            serverRequestBuf.append("3");
            // セパレータ連結
            serverRequestBuf.append(ImdstKeyValueClient.sepStr);


            // tag連結(Keyはデータ送信時には必ず文字列が必要)
            serverRequestBuf.append(tagStr);


            // サーバ送信
            pw.println(serverRequestBuf.toString());
            pw.flush();

            // サーバから結果受け取り
            serverRetStr = br.readLine();

            serverRet = serverRetStr.split(ImdstKeyValueClient.sepStr);

            // 処理の妥当性確
            if (serverRet[0].equals("4")) {
                if (serverRet[1].equals("true")) {

                    // データ有り
                    ret[0] = serverRet[1];

                    // Valueがブランク文字か調べる
                    if (serverRet[2].equals(ImdstKeyValueClient.blankStr)) {
                        String[] tags = {""};
                        ret[1] = tags;
                    } else {
                        String[] tags = null;
                        String[] cnvTags = null;

                        tags = serverRet[2].split(tagKeySep);

                        ret[1] = tags;
                    }
                } else if(serverRet[1].equals("false")) {

                    // データなし
                    ret[0] = serverRet[1];
                    ret[1] = null;
                } else if(serverRet[1].equals("error")) {

                    // エラー発生
                    ret[0] = serverRet[1];
                    ret[1] = serverRet[2];
                }
            } else {

                // 妥当性違反
                throw new BatchException("Execute Violation of validity [" + serverRet[0] + "]");
            }
        } catch (BatchException be){
            throw be;
        } catch (Exception e) {
            throw new BatchException(e);
        }
        return ret;
    }
}
