package SpeechPkg;

import com.sun.jna.*;
import com.sun.jna.ptr.FloatByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.*;

/**
 * Windows音声解析DLL.<br>
 * @author  MSLABO
 * @version Ver1.0
 * @see
 * <a href="https://creativecommons.org/licenses/by-sa/4.0/">
 * <img src="{@docRoot}/doc-files/by-sa.png" alt="クリエィティブ・コモンズライセンス">
 * </a><br><br>
 * <a href="http://mslabo.sakura.ne.jp/WordPress/">・関連情報公開元</a><br><br>
 * 本プログラムとドキュメント（以下、本APと呼称）の著作権はMSLABOにあります。<br>
 * 本APのMSLABO作成部分は、クリエィティブ・コモンズライセンスに従い提供されています。<br>
 * また本APが利用しているJNA、PROCESSINGなどの各モジュールについては、各ソフトウェアの
 * ライセンス規約に従うものとします。<br>
 * 利用にあたっては、必ず自己責任でお願い致します。<br>
 * 本APの不具合については極力善処を行う予定ですが、これを保証するものではありません。<br>
 * また本APに関する情報はすべてMSLABOのHPから一方的に公開するものとし、原則として、個別の
 * 問い合わせや要望は受け付けません。<br>
 * 上記事項に同意頂ける方のみ、利用が可能です。<br>
 * 本プログラムが、みなさまのPROCESSING利用の一助になれば幸いです。<br>
 */
public class WinSpeech {
    private final static String DLLNAME = "SystemSpeechNET.dll";
    private static NETDLL  INSTANCE = null;	//DLLライブラリポインタ
    private static String JNA_CODENAME = "shift_jis";
    private static HashMap<String, String> encodeMap;

    interface NETDLL extends Library {
        // 関数へのインターフェース
        void SetLogEncode(String codeName, String currentPath);
        void SetLogName(String filename);
        void SetGrammarName(String filename);
        void SetLogSwitch(boolean logSw);
        void CloseEngine();
        int OpenEngine(boolean mode);
        int SetDictationType(boolean mode);
        int SetInputDefaultAudio();
        int SetInputWavFile(String fileName);
        int StartAsyncRecognized(boolean mode);
        int StartRecognized(int timeOut);
        int StopAsyncRecognized();
        String GetSpeechRecognized();
        String GetSpeechHypothesized();
        void SetHypothesizedCallBackFunc(CallBackHypothesizedFunc lParam);
        void SetRecognizedCallbackFunc(CallBackRecognizedFunc lParam);
    }

    /**
     * Javaエンコード:C#エンコード対応MAP作成
     * @param def Javaのシステムエンコード既定値
     */
    private void MapInit(String def){
        encodeMap = new HashMap<String, String>();
        // map( Java側,C#側 )
        encodeMap.put("EUC_JP","EUC-JP");
        encodeMap.put("EUC-JP","EUC-JP");
        encodeMap.put("SJIS","shift_jis");
        encodeMap.put("Shift_JIS","shift_jis");
        encodeMap.put("MS932","shift_jis");
        encodeMap.put("windows-31j","shift_jis");
        encodeMap.put("EUC_JP_LINUX","EUC-JP");
        encodeMap.put("x-euc-jp-linux","EUC-JP");
        encodeMap.put("EUC_JP_Solaris","EUC-JP");
        encodeMap.put("x-eucJP-Open","EUC-JP");
        encodeMap.put("UTF8","utf-8");
        encodeMap.put("UTF-8","utf-8");
        encodeMap.put("UTF-16","utf-16");
        encodeMap.put("UnicodeBigUnmarked","unicodeFFFE");
        encodeMap.put("UTF-16BE","unicodeFFFE");
        encodeMap.put("UnicodeLittleUnmarked","utf-16");
        encodeMap.put("UTF-16LE","utf-16");
        encodeMap.put("UnicodeBig","unicodeFFFE");
        encodeMap.put("UnicodeLittle","utf-16");
    }

    /**
     * コンストラクタ：クラスを初期化します.<br><br>
     * 指定されたパス配下にあるSytemSpeechNET.DLLを読み込みます。<br>
     * DLLが見つからない場合は、IllegalArgumentExceptionが発生します。
     * またDLLの読み込みに失敗するとUnsatisfiedLinkErrorが発生します。
     * DLLのパスは、ログファイルの出力先および文法ファイルの存在パスとしても扱われます。<br>
     * @param libPath DLLパス
     * @throws IllegalArgumentException 指定パスにDLLが存在しません
     * @throws UnsatisfiedLinkError DLL読み込み例外
     *  <br>
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   //DLLの場所には、スケッチフォルダーのcode配下を指定
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public WinSpeech(String libPath) throws IllegalArgumentException, UnsatisfiedLinkError {
        //システムエンコード文字をC#用エンコード文字に変換する
        String systemEncod = System.getProperty("file.encoding");
        MapInit(systemEncod);
        String csEncode = encodeMap.get(systemEncod);
        if( csEncode == null ){
            csEncode = systemEncod;
        }

        if( !csEncode.equals(JNA_CODENAME) ){
            //C#エンコード(システムエンコード文字)が shift_jis じゃない場合
            //jna.encodingを shift_jis にする
            //こうしないとJNA経由で渡したStringが文字化けする
            System.setProperty("jna.encoding", JNA_CODENAME );
        }

//        System.out.println(String.format("system=%s : C#=%s : jna=%s",
//                systemEncod, csEncode, JNA_CODENAME));

        String currentPath = new File(libPath).getPath();
        Path driveName = FileSystems.getDefault().getPath(currentPath).getRoot();

        //pathにドライブ名直下は許可しない
        if( currentPath.equals(driveName.toString())){
            throw new IllegalArgumentException("指定パスにドライブ名は指定できません:" + libPath );
        }

        // DLLインスタンス生成
        if( new File(libPath).exists() ){
            NativeLibrary.addSearchPath( DLLNAME, libPath );
            INSTANCE = (NETDLL) Native.load(DLLNAME, NETDLL.class);

            //C#側に、ログとコンソール出力時のエンコード指定を伝える
            INSTANCE.SetLogEncode(csEncode, currentPath);

        } else {
            throw new IllegalArgumentException("指定パスにDLLが存在しません:" + libPath );
        }
    }

    /**
     * コンストラクタ：クラスを初期化します.<br><br>
     * SytemSpeechNET.DLLは実行モジュールのカレント位置にあるものとして読み込まれます。<br>
     * DLLが見つからない場合は、IllegalArgumentExceptionが発生します。
     * またDLLの読み込みに失敗するとUnsatisfiedLinkErrorが発生します。
     * DLLのパスは、ログファイルの出力先および文法ファイルの存在パスとしても扱われます。<br>
     *  <br>
     * @throws IllegalArgumentException 指定パスにDLLが存在しません
     * @throws UnsatisfiedLinkError DLL読み込み例外
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   //DLLの場所には、実行モジュールのカレント位置とみなします
     *   WinSpeech ws = new WinSpeech();
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public WinSpeech() throws IllegalArgumentException, UnsatisfiedLinkError {
        //パス省略時はカレントフォルダーを指定してインスタス作成
        String path = new File(".").getAbsoluteFile().getParent();
        new WinSpeech( path );
    }

    /**
     * ログファイル名を切り替えます.<br><br>
     * デフォルトのログファイル名は[SytemSpeechNET.txt]です。<br>
     * ログファイルはインスタンス作成時に指定したパス（DLLの存在位置）に作成されます。<br>
     * @param fileName ログファイル名
     *  <br>
     *  <pre>
     * {@code
     *  import SpeechPkg.*;
     *
     *  void setup(){
     *    //インスタンスを生成する
     *    WinSpeech ws = new WinSpeech(sketchPath("code"));
     *    //ログファイル名を変更する
     *    ws.SetLogName("MyLog.txt");
     *  }
     *  void draw(){
     *  }
     * }</pre>
     */
    public void SetLogName(String fileName){
        if( fileName != null && !fileName.equals("")){
            INSTANCE.SetLogName(fileName);
        } else {
            System.err.println("SetLogName error");
        }
    }

    /**
     * 文法ファイル名を切り替えます.<br><br>
     * デフォルトの文法ファイル名は[Grammar.txt]です。<br>
     * 文法ファイルはインスタンス作成時に指定したパス（DLLの存在位置）にあるものみなします。<br>
     * 文法ファイルは Shift-JIS で記述してください。<br><br>
     * 文法ファイル例：<br>
     *<br>
     * #半角のシャープで始まる行はコメントです<br>
     * あかいろ<br>
     * いるか<br>
     * オレンジ<br>
     *<br>
     * @param fileName 文法ファイル名
     *  <br>
     *  <pre>
     * {@code
     *  import SpeechPkg.*;
     *
     *  void setup(){
     *    //インスタンスを生成する
     *    WinSpeech ws = new WinSpeech(sketchPath("code"));
     *    //文法ファイル名を変更する
     *    ws.SetGrammarName("MyGrammar.txt");
     *  }
     *  void draw(){
     *  }
     * }</pre>
     */
    public void SetGrammarName(String fileName){
        if( fileName != null && !fileName.equals("")){
            INSTANCE.SetGrammarName(fileName);
        } else {
            System.err.println("SetGrammarName error");
        }
    }

    /**
     * SytemSpeechNET.dllがログを出力するかどうかを切り替えます.<br><br>
     * デフォルトでは、ログは出力しません。<br>
     * @param logSw True:ログ出力ON
     *  <br>
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *   //ログを記録する
     *   ws.SetLogSwitch( true );
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public void SetLogSwitch(boolean logSw){
        INSTANCE.SetLogSwitch(logSw);
    }

    /**
     * 解析エンジンをCloseします.<br><br>
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *   //解析エンジンをOpen/Closeする
     *   ws.OpenEngine( );
     *   ws.CloseEngine();
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public void CloseEngine(){
        INSTANCE.CloseEngine();
    }

    /**
     * 文法解析モード
     */
    public final static boolean GRAMMAR = true;
    /**
     * 自由会話解析モード
     */
    public final static boolean DICTATION = false;

    /**
     * 解析エンジンをOpenします.<br><br>
     * GRAMMARを指定すると文法解析モードで、DICTATIONを指定すると自由会話解析モードで動作します。<br>
     * 文法解析モード時には、文法ファイルが必要となります。<br>
     * 文法ファイルはインスタンス作成時に指定したパス（DLLの存在位置）にあるものみなします。<br>
     * 入力には標準入力マイクを利用します。<br>
     * @param mode 解析モード(GRAMMAR/DICTATION)
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine(WinSpeech.GRAMMAR);
     *
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int OpenEngine(boolean mode){
        int ret = INSTANCE.OpenEngine(mode);
        if( ret != 0 ){
            System.err.println("OpenEngine error:" + ret);
            return ret;
        }
        //System.out.println("OpenEngine");
        return 0;
    }

    /**
     * 解析エンジンをOpenします.<br><br>
     * 自由会話解析モードで動作します。入力には標準入力マイクを利用します。<br>
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int OpenEngine(){
        return OpenEngine(DICTATION);
    }

    /**
     * 音声解析モードを設定します.<br><br>
     * GRAMMARを指定すると文法解析モードで、DICTATIONを指定すると自由会話解析モードで動作します。<br>
     * 文法解析モード時には、文法ファイルが必要となります。<br>
     * 文法ファイルはインスタンス作成時に指定したパス（DLLの存在位置）にあるものみなします。<br>
     * @param mode 解析モード(GRAMMAR/DICTATION)
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *
     *   //文法解析を指定
     *   ws.SetDictationType(WinSpeech.GRAMMAR);
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int SetDictationType(boolean mode){
        int ret = INSTANCE.SetDictationType(mode);
        if( ret != 0 ){
            System.err.println("SetDictationType error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * 入力に標準入力マイクを利用します.<br><br>
     * マイクが接続されていない（あるいは無効）な場合は、エラーとなります。<br>
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *   //標準入力マイクを利用
     *   ws.SetInputDefaultAudio();
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int SetInputDefaultAudio(){
        int ret = INSTANCE.SetInputDefaultAudio();
        if( ret != 0 ){
            System.err.println("SetInputDefaultAudio error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * 入力にWAVファイルを利用します.<br><br>
     * WAVファイルは、サンプリングレートが44100で、16bitのモノクロチャネルのものが推奨です。<br>
     * @param fileName WAVファイル名
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *   //WAVファイルを利用する
     *   ws.SetInputWavFile("c:\\temp\\femal.wav");
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int SetInputWavFile( String fileName ){
        int ret = INSTANCE.SetInputWavFile( fileName );
        if( ret != 0 ){
            System.err.println("SetInputWavFile error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * シングル解析
     */
    public final static boolean SINGLE = true;
    /**
     * 連続解析
     */
    public final static boolean MULTIPLE = false;

    /**
     * 非同期解析開始.<br><br>
     * 非同期による音声解析を開始します。SINGLEなら１回だけ解析し、MULTIPLEなら連続で解析します。<br>
     * 非同期解析はStopAsyncRecognized()が呼ばれると停止します。<br>
     * @param mode 解析方法（SINGLE/MULTIPLE）
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine(WinSpeech.GRAMMAR);
     *   //非同期解析（連続）開始
     *   ws.StartAsyncRecognized(WinSpeech.MULTIPLE);
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int StartAsyncRecognized(boolean mode){
        int ret = INSTANCE.StartAsyncRecognized(mode);
        if( ret != 0 ){
            System.err.println("StartAsyncRecognized error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * 同期解析開始.<br><br>
     * 同期による音声解析を開始します。解析が終了するまでブロックされ、上位に処理が戻りません。<br>
     * timeOutは音声の入力を待つ時間です。timeOut秒以上、有効な音声が入力されなかった場合、エラーと
     * なります。0以下なら無限に音声入力を待ちます。<br>
     * @param timeOut 指定秒数だけ音声の入力を待つ。0以下なら無限待ち
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * void setup(){
     *   //インスタンスを生成する
     *   WinSpeech ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine(WinSpeech.GRAMMAR);
     *   //同期解析開始（１０秒だけ音声入力を待つ）
     *   ws.StartRecognized(10);
     * }
     * void draw(){
     * }
     * }</pre>
     */
    public int StartRecognized(int timeOut){
        int ret = INSTANCE.StartRecognized(timeOut);
        if( ret != 0 ){
            System.err.println("StartRecognized error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * 連続した非同期解析を停止します.<br><br>
     * StartAsyncRecognized()ではじめた非同期連続解析を停止します。<br>
     * @return 0以外：異常
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * WinSpeech ws;
     * void setup(){
     *   //インスタンスを生成する
     *   ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *   //非同期解析開始
     *   ws.StartAsyncRecognized(WinSpeech.MULTIPLE);
     * }
     * void draw(){
     *   //結果を取得
     *   String rec = ws.GetSpeechRecognized();
     *   if( !rec.equals("") ){
     *     //なにか解析できたら表示する
     *     println( rec );
     *
     *     if( rec.equals("終わり")){
     *       //[終わり]と入力されたら、解析停止
     *       ws.StopAsyncRecognized();
     *       println("end");
     *     }
     *   }
     * }
     * }</pre>
     */
    public int StopAsyncRecognized(){
        int ret = INSTANCE.StopAsyncRecognized();
        if( ret != 0 ){
            System.err.println("StopAsyncRecognized error:" + ret);
            return ret;
        }
        return 0;
    }

    /**
     * 現在までに未取得の解析結果を受け取ります.<br><br>
     * @return 解析結果。なにも解析されたものが無ければ空文字が戻ります
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * WinSpeech ws;
     * void setup(){
     *   //インスタンスを生成する
     *   ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *   //非同期解析開始
     *   ws.StartAsyncRecognized(WinSpeech.MULTIPLE);
     * }
     * void draw(){
     *   //結果を取得
     *   String rec = ws.GetSpeechRecognized();
     *   if( !rec.equals("") ){
     *     //なにか解析できたら表示する
     *     println( rec );
     *
     *     if( rec.equals("終わり")){
     *       //[終わり]と入力されたら、解析停止
     *       ws.StopAsyncRecognized();
     *       println("end");
     *     }
     *   }
     * }
     * }</pre>
     */
    public String GetSpeechRecognized(){
        return INSTANCE.GetSpeechRecognized();
    }

    /**
     * 現在までに認識された語句から、最も信頼性のあるものを取得します.<br><br>
     * @return 解析結果。なにも解析されたものが無ければ空文字が戻ります
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     *
     * WinSpeech ws;
     *
     * void setup(){
     *   ws = new WinSpeech(sketchPath("code"));
     *   ws.SetLogSwitch( true );
     *
     *   //WAVを文法解析
     *   ws.OpenEngine( WinSpeech.GRAMMAR );
     *   ws.SetInputWavFile( dataPath("shop_youngman01_06_1.wav") );
     *
     *   //解析開始
     *   int ret = ws.StartRecognized( 0 );
     *   if( ret != 0 ){
     *     //解析エラーなら、最も信頼度の高い解析結果を得る
     *     String val = ws.GetSpeechHypothesized();
     *     if( !val.equals("") ){
     *       println( "[" + val + "]" );
     *     }
     *   }
     * }
     *
     * void draw(){
     * }
     * }</pre>
     */
    public String GetSpeechHypothesized(){
        return INSTANCE.GetSpeechHypothesized();
    }

    /**
     * 解析失敗時コールバック関数登録.<br><br>
     * 解析が失敗した際に呼ばれるJavaの関数を定義します。<br>
     * 解析失敗時に、 現在までに認識された語句から、最も信頼性のあるものを取得し、その信頼度とともに返却します。<br>
     * なにも認識された語句がない場合は、空文字と信頼度0が戻されます。<br>
     * コールバック関数の名前は任意ですが、publicで、String型の引数とfloatの引数を受け取るvoid戻り値のものにしてください。<br>
     * @param obj コールバック関数があるクラス
     * @param func コールバック関数名
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     * import java.lang.reflect.InvocationTargetException;
     *
     * WinSpeech ws;
     * void setup(){
     *   //インスタンスを生成する
     *   ws = new WinSpeech(sketchPath("code"));
     *   ws.SetLogSwitch( true );
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine( WinSpeech.GRAMMAR );
     *   //解析終了時のコールバック関数を登録する
     *   ws.SetHypothesizedCallBackFunc(this,"callBackFunc");
     *
     *   //非同期解析開始
     *   ws.SetInputWavFile( dataPath("03_macho_before.wav") );
     *   ws.StartAsyncRecognized(WinSpeech.SINGLE);
     * }
     * void draw(){
     * }
     *
     * //コールバックされる関数
     * //void 戻り値で、引数は Stringとfloatである事
     * //rec : これまでに得られた解析結果のうち最大の信頼度を持つもの
     * //conf ：　解析信頼度
     * public void callBackFunc(String rec, float conf){
     *   if( !rec.equals("") ){
     *     //なにか解析できたら表示する
     *     println( "[" + rec + "](" + conf + ")" );
     *   }
     * }
     * }</pre>
     */
    public void SetHypothesizedCallBackFunc(Object obj , String func)
    {
        //指定クラスからコールバック関数を得る
        Class clazz = obj.getClass();
        try{
            final Method p5Func = clazz.getMethod(func, String.class, float.class );

            INSTANCE.SetHypothesizedCallBackFunc(new CallBackHypothesizedFunc(){
                //C#から呼び出される関数
                @Override
                public void invoke(PointerByReference data, FloatByReference confidence)
                {
                    //解析結果をJava側にリフレクションする
                    Pointer p = data.getValue();
                    float conf = confidence.getValue();

                    String buffer = p.getString(0);
                    try{
                        if (p5Func != null) p5Func.invoke( obj, buffer, conf );
                    } catch( IllegalAccessException
                            | InvocationTargetException e) {
                        e.printStackTrace();
                        return;
                    }
                }
            });

        } catch( NoSuchMethodException e ){
            e.printStackTrace();
            return;
        }

        return ;
    }

    /**
     * C#->Javaコールバック関数用インタフェース
     */
    private interface CallBackHypothesizedFunc extends Callback {
        public void invoke(PointerByReference data, FloatByReference confidence);
    }

    /**
     * 解析終了時コールバック関数登録.<br><br>
     * 解析が正常に終了した際に呼ばれるJavaの関数を定義します。<br>
     * 解析が終わった際に、その結果を返却します。<br>
     * コールバック関数の名前は任意ですが、publicで、String型の引数を１つだけ受け取るvoid戻り値のものにしてください。<br>
     * @param obj コールバック関数があるクラス
     * @param func コールバック関数名
     *  <pre>
     * {@code
     * import SpeechPkg.*;
     * import java.lang.reflect.InvocationTargetException;
     *
     * WinSpeech ws;
     * void setup(){
     *   //インスタンスを生成する
     *   ws = new WinSpeech(sketchPath("code"));
     *
     *   //解析エンジンをOpenする
     *   ws.OpenEngine();
     *   //解析終了時のコールバック関数を登録する
     *   ws.SetCallbackRecognizedFunc(this,"callBackFunc");
     *
     *   //非同期解析開始
     *   ws.StartAsyncRecognized(WinSpeech.MULTIPLE);
     * }
     * void draw(){
     * }
     *
     * //コールバックされる関数
     * //void 戻り値で、引数は Stringである事
     * //rec : 解析結果
     * public void callBackFunc(String rec){
     *   if( !rec.equals("") ){
     *     //なにか解析できたら表示する
     *     println( "[" + rec + "]" );
     *
     *     if( rec.trim().equals("終わり")){
     *       //[終わり]と入力されたら、解析停止
     *       ws.CloseEngine();
     *     }
     *   }
     * }
     * }</pre>
     */
    public void SetCallbackRecognizedFunc(Object obj , String func)
    {
        //指定クラスからコールバック関数を得る
        Class clazz = obj.getClass();

        try {
            final Method p5Func = clazz.getMethod(func, String.class);

            INSTANCE.SetRecognizedCallbackFunc(new CallBackRecognizedFunc(){
                //C#から呼び出される関数
                @Override
                public void invoke(PointerByReference data, IntByReference size)
                {
                    //解析結果をJava側にリフレクションする
                    Pointer p = data.getValue();

                    String buffer = p.getString(0);
                    try {
                        if (p5Func != null) p5Func.invoke(obj, buffer);
                    } catch( IllegalAccessException
                            | InvocationTargetException e) {
                        e.printStackTrace();
                        return;
                    }
                }
            });
        } catch( NoSuchMethodException e ){
            e.printStackTrace();
            return;
        }

        return ;
    }

    /**
     * C#->Javaコールバック関数用インタフェース
     */
    private interface CallBackRecognizedFunc extends Callback {
        public void invoke(PointerByReference data, IntByReference size);
    }
}
