/* 
 * PROJECT: NyARToolkitCS
 * --------------------------------------------------------------------------------
 * This work is based on the original ARToolKit developed by
 *   Hirokazu Kato
 *   Mark Billinghurst
 *   HITLab, University of Washington, Seattle
 * http://www.hitl.washington.edu/artoolkit/
 *
 * The NyARToolkitCS is C# edition ARToolKit class library.
 * Copyright (C)2008-2009 Ryo Iizuka
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For further information please contact.
 *	http://nyatla.jp/nyatoolkit/
 *	<airmail(at)ebony.plala.or.jp> or <nyatla(at)nyatla.jp>
 * 
 */
using System;
using System.Collections.Generic;
using System.Text;
using jp.nyatla.nyartoolkit.cs.core;
using jp.nyatla.nyartoolkit.cs.core2;

namespace jp.nyatla.nyartoolkit.cs.sandbox.x2
{
    /**
     * このクラスは、同時に１個のマーカを処理することのできる、アプリケーションプロセッサです。
     * マーカの出現・移動・消滅を、イベントで通知することができます。
     * クラスには複数のマーカを登録できます。一つのマーカが見つかると、プロセッサは継続して同じマーカを
     * １つだけ認識し続け、見失うまでの間は他のマーカを認識しません。
     * 
     * イベントは、 OnEnter→OnUpdate[n]→OnLeaveの順で発生します。
     * マーカが見つかるとまずOnEnterが１度発生して、何番のマーカが発見されたかがわかります。
     * 次にOnUpdateにより、現在の変換行列が連続して渡されます。最後にマーカを見失うと、OnLeave
     * イベントが発生します。
     * 
     */
    public abstract class SingleARMarkerProcesser_X2
    {
        /**
         * [[STRAGE CLASS]]
         * selectARCodeIndexFromListが値を返す時に使う変数型です。
         */

        private class TResult_selectARCodeIndex
        {
            public int direction;

            public double confidence;

            public int code_index;
        }
        /**オーナーが自由に使えるタグ変数です。
         */
        public object tag;

        private int _lost_delay_count = 0;

        private int _lost_delay = 5;

        private INyARSquareDetector _square_detect;

        protected NyARTransMat_X2 _transmat;

        private double _marker_width;

        private NyARMatchPatt_Color_WITHOUT_PCA[] _match_patt;

        private NyARSquareStack _square_list = new NyARSquareStack(100);

        private NyARColorPatt_Perspective_O2 _patt = null;

        private double _cf_threshold_new = 0.30;

        private double _cf_threshold_exist = 0.15;

        private int _threshold = 110;
        // [AR]検出結果の保存用
        private NyARBinRaster _bin_raster;

        private NyARRasterFilter_ARToolkitThreshold _tobin_filter;

        protected int _current_arcode_index = -1;

        private NyARMatchPattDeviationColorData _deviation_data;
        private NyARRasterThresholdAnalyzer_SlidePTile _threshold_detect;


	    protected SingleARMarkerProcesser_X2()
	    {
		    return;
	    }


	    protected void initInstance(NyARParam i_param,int i_raster_type)
	    {
		    NyARIntSize scr_size = i_param.getScreenSize();
		    // 解析オブジェクトを作る
		    this._square_detect = new NyARSquareDetector_Rle_X2(i_param.getDistortionFactor(), scr_size);
		    this._transmat = new NyARTransMat_X2(i_param);
		    this._tobin_filter=new NyARRasterFilter_ARToolkitThreshold(110,i_raster_type);

		    // ２値画像バッファを作る
		    this._bin_raster = new NyARBinRaster(scr_size.w, scr_size.h);
            this._threshold_detect = new NyARRasterThresholdAnalyzer_SlidePTile(15, i_raster_type, 4);
            return;
	    }
        /*自動・手動の設定が出来ないので、コメントアウト
        public void setThreshold(int i_threshold)
        {
            this._threshold = i_threshold;
            return;
        }*/

    /**検出するマーカコードの配列を指定します。 検出状態でこの関数を実行すると、
     * オブジェクト状態に強制リセットがかかります。
     */
        public void setARCodeTable(NyARCode[] i_ref_code_table, int i_code_resolution, double i_marker_width)
        {
            if (this._current_arcode_index != -1)
            {
                // 強制リセット
                reset(true);
            }
            // 検出するマーカセット、情報、検出器を作り直す。
            this._patt = new NyARColorPatt_Perspective_O2(i_code_resolution, i_code_resolution,4,25);
            this._deviation_data = new NyARMatchPattDeviationColorData(i_code_resolution, i_code_resolution);
            this._marker_width = i_marker_width;

            this._match_patt = new NyARMatchPatt_Color_WITHOUT_PCA[i_ref_code_table.Length];
            for (int i = 0; i < i_ref_code_table.Length; i++)
            {
                this._match_patt[i] = new NyARMatchPatt_Color_WITHOUT_PCA(i_ref_code_table[i]);
            }
            return;
        }

        public void reset(bool i_is_force)
        {
            if (this._current_arcode_index != -1 && i_is_force == false)
            {
                // 強制書き換えでなければイベントコール
                this.onLeaveHandler();
            }
            // カレントマーカをリセット
            this._current_arcode_index = -1;
            return;
        }

        public void detectMarker(INyARRgbRaster i_raster)
        {
            // サイズチェック
            if (!this._bin_raster.getSize().isEqualSize(i_raster.getSize().w, i_raster.getSize().h))
            {
                throw new NyARException();
            }

            // コードテーブルが無ければここで終わり
            if (this._match_patt == null)
            {
                return;
            }

            // ラスタを(1/4の画像の)２値イメージに変換する.
            this._tobin_filter.setThreshold(this._threshold);
            this._tobin_filter.doFilter(i_raster, this._bin_raster);

            NyARSquareStack square_stack = this._square_list;
            // スクエアコードを探す
            this._square_detect.detectMarker(this._bin_raster, square_stack);
            // 認識処理
            if (this._current_arcode_index == -1)
            { // マーカ未認識
                detectNewMarker(i_raster, square_stack);
            }
            else
            { // マーカ認識中
                detectExistMarker(i_raster, square_stack, this._current_arcode_index);
            }
            return;
        }


        private NyARMatchPattResult __detectMarkerLite_mr = new NyARMatchPattResult();

        /**ARCodeのリストから、最も一致するコード番号を検索します。
         */
        private bool selectARCodeIndexFromList(INyARRgbRaster i_raster, NyARSquare i_square, TResult_selectARCodeIndex o_result)
        {
            // 現在コードテーブルはアクティブ？
            if (this._match_patt == null)
            {
                return false;
            }
            // 評価基準になるパターンをイメージから切り出す
            if (!this._patt.pickFromRaster(i_raster, i_square))
            {
                return false;
            }
            //評価データを作成して、評価器にセット
            this._deviation_data.setRaster(this._patt);
            NyARMatchPattResult mr = this.__detectMarkerLite_mr;
            int code_index = 0;
            int dir = 0;
            double c1 = 0;
            // コードと比較する
            for (int i = 0; i < this._match_patt.Length; i++)
            {
                this._match_patt[i].evaluate(this._deviation_data, mr);
                double c2 = mr.confidence;
                if (c1 < c2)
                {
                    code_index = i;
                    c1 = c2;
                    dir = mr.direction;
                }
            }
            o_result.code_index = code_index;
            o_result.direction = dir;
            o_result.confidence = c1;
            return true;
        }

        private TResult_selectARCodeIndex __detect_X_Marker_detect_result = new TResult_selectARCodeIndex();

        /**新規マーカ検索 現在認識中のマーカがないものとして、最も認識しやすいマーカを１個認識します。
         */
        private void detectNewMarker(INyARRgbRaster i_raster, NyARSquareStack i_stack)
        {
            int number_of_square = i_stack.getLength();
            double cf = 0;
            int dir = 0;
            int code_index = -1;
            int square_index = 0;
            TResult_selectARCodeIndex detect_result = this.__detect_X_Marker_detect_result;
            for (int i = 0; i < number_of_square; i++)
            {
                if (!selectARCodeIndexFromList(i_raster, (NyARSquare)i_stack.getItem(i), detect_result))
                {
                    // 見つからない。
                    return;
                }
                if (detect_result.confidence < this._cf_threshold_new)
                {
                    continue;
                }
                if (detect_result.confidence < cf)
                {
                    // 一致度が低い。
                    continue;
                }
                cf = detect_result.confidence;
                code_index = detect_result.code_index;
                square_index = i;
                dir = detect_result.direction;
            }
            // 認識状態を更新
            bool is_id_found=updateStatus(this._square_list.getItem(square_index), code_index, cf, dir);
		    //閾値フィードバック(detectExistMarkerにもあるよ)
		    if(!is_id_found){
			    //マーカがなければ、探索+DualPTailで基準輝度検索
			    this._threshold_detect.analyzeRaster(i_raster);
			    this._threshold=(this._threshold+this._threshold_detect.getThreshold())/2;
		    }
        }

        /**マーカの継続認識 現在認識中のマーカを優先して認識します。 
         * （注）この機能はたぶん今後いろいろ発展するからNewと混ぜないこと。
         */
        private void detectExistMarker(INyARRgbRaster i_raster, NyARSquareStack i_stack, int i_current_id)
        {
            int number_of_square = i_stack.getLength();
            double cf = 0;
            int dir = 0;
            int code_index = -1;
            int square_index = 0;
            TResult_selectARCodeIndex detect_result = this.__detect_X_Marker_detect_result;
            for (int i = 0; i < number_of_square; i++)
            {
                if (!selectARCodeIndexFromList(i_raster, (NyARSquare)i_stack.getItem(i), detect_result))
                {
                    // 見つからない。
                    return;
                }
                // 現在のマーカを認識したか？
                if (detect_result.code_index != i_current_id)
                {
                    // 認識中のマーカではないので無視
                    continue;
                }
                if (detect_result.confidence < this._cf_threshold_exist)
                {
                    continue;
                }
                if (detect_result.confidence < cf)
                {
                    // 一致度が高い方を選ぶ
                    continue;
                }
                cf = detect_result.confidence;
                code_index = detect_result.code_index;
                dir = detect_result.direction;
                square_index = i;
            }
            // 認識状態を更新
            bool is_id_found=updateStatus(this._square_list.getItem(square_index), code_index, cf, dir);
            //閾値フィードバック(detectExistMarkerにもあるよ)
            if (!is_id_found)
            {
                //マーカがなければ、探索+DualPTailで基準輝度検索
                this._threshold_detect.analyzeRaster(i_raster);
                this._threshold = (this._threshold + this._threshold_detect.getThreshold()) / 2;
            }

        }

        private NyARTransMatResult __NyARSquare_result = new NyARTransMatResult();

        /**オブジェクトのステータスを更新し、必要に応じてハンドル関数を駆動します。
         */
        private bool updateStatus(NyARSquare i_square, int i_code_index, double i_cf, int i_dir)
        {
            NyARTransMatResult result = this.__NyARSquare_result;
            if (this._current_arcode_index < 0)
            {// 未認識中
                if (i_code_index < 0)
                {// 未認識から未認識の遷移
                    // なにもしないよーん
                    return false;
                }
                else
                {// 未認識から認識の遷移
                    this._current_arcode_index = i_code_index;
                    // イベント生成
                    // OnEnter
                    this.onEnterHandler(i_code_index);
                    // 変換行列を作成
                    this._transmat.transMat(i_square, i_dir, this._marker_width, result);
                    // OnUpdate
                    this.onUpdateHandler(i_square, result);
                    this._lost_delay_count = 0;
                    return true;
                }
            }
            else
            {// 認識中
                if (i_code_index < 0)
                {// 認識から未認識の遷移
                    this._lost_delay_count++;
                    if (this._lost_delay < this._lost_delay_count)
                    {
                        // OnLeave
                        this._current_arcode_index = -1;
                        this.onLeaveHandler();
                    }
                    return false;
                }
                else if (i_code_index == this._current_arcode_index)
                {// 同じARCodeの再認識
                    // イベント生成
                    // 変換行列を作成
                    this._transmat.transMat(i_square, i_dir, this._marker_width, result);
                    // OnUpdate
                    this.onUpdateHandler(i_square, result);
                    this._lost_delay_count = 0;
                    return true;
                }
                else
                {// 異なるコードの認識→今はサポートしない。
                    throw new NyARException();
                }
            }
        }

        protected abstract void onEnterHandler(int i_code);

        protected abstract void onLeaveHandler();

        protected abstract void onUpdateHandler(NyARSquare i_square, NyARTransMatResult result);
    }

}
