using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using jp.nyatla.nyartoolkit.cs.core;
using jp.nyatla.nyartoolkit.cs.nyidmarker;
using jp.nyatla.nyartoolkit.cs.processor;
using jp.nyatla.nyartoolkit.cs.utils;
using NyARToolkitCSUtils.Capture;
using NyARToolkitCSUtils.Direct3d;
using NyARToolkitCSUtils.NyAR;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace SingleNyIdMarkerDirect3d
{
    class TextPanel
    {
        private Device _device;
        private System.Drawing.Font _font;
        public TextPanel(Device i_device,int i_size)
        {
            this._device = i_device;
            this._font = new System.Drawing.Font("System", i_size);
            return;
        }
        public void draw(String i_str, float i_scale)
        {
            Mesh m= Mesh.TextFromFont(this._device, this._font, i_str, 5.0f, 0.1f);

            m.DrawSubset(0);
            m.Dispose();
            return;
        }
    }

    /**
     * １個のRawBit-Idマーカを認識するロジッククラス。
     * detectMarker関数の呼び出しに同期して、transmatとcurrent_idパラメタを更新します。
     * 
     *
     */
    class MarkerProcessor : SingleNyIdMarkerProcesser
    {
        private Object _sync_object = new Object();
        public NyARTransMatResult transmat = null;
        public int current_id = -1;

        public MarkerProcessor(NyARParam i_cparam, int i_raster_format)
        {
            //アプリケーションフレームワークの初期化
            initInstance(i_cparam, new NyIdMarkerDataEncoder_RawBit(),100, i_raster_format);
            return;
        }
        /**
         * アプリケーションフレームワークのハンドラ（マーカ出現）
         */
        protected override void onEnterHandler(INyIdMarkerData i_code)
        {
            lock (this._sync_object)
            {
                NyIdMarkerData_RawBit code = (NyIdMarkerData_RawBit)i_code;
                if (code.length > 4)
                {
                    //4バイト以上の時はint変換しない。
                    this.current_id = -1;//undefined_id
                }
                else
                {
                    this.current_id = 0;
                    //最大4バイト繋げて１個のint値に変換
                    for (int i = 0; i < code.length; i++)
                    {
                        this.current_id = (this.current_id << 8) | code.packet[i];
                    }
                }
                this.transmat = null;
            }
        }
        /**
         * アプリケーションフレームワークのハンドラ（マーカ消滅）
         */
        protected override void onLeaveHandler()
        {
            lock (this._sync_object)
            {
                this.current_id = -1;
                this.transmat = null;
            }
            return;
        }
        /**
         * アプリケーションフレームワークのハンドラ（マーカ更新）
         */
        protected override void onUpdateHandler(NyARSquare i_square, NyARTransMatResult result)
        {
            lock (this._sync_object)
            {
                this.transmat = result;
            }
        }
    }

    public partial class SimpleLiteD3d : IDisposable, CaptureListener
    {
        private const int SCREEN_WIDTH = 640;
        private const int SCREEN_HEIGHT = 480;
        private const String AR_CODE_FILE = "../../../../../data/patt.hiro";
        private const String AR_CAMERA_FILE = "../../../../../data/camera_para.dat";
        //DirectShowからのキャプチャ
        private CaptureDevice _cap;
        private MarkerProcessor _processor;
        //NyAR
        private DsBGRX32Raster _raster;
        private NyARD3dUtil _utils;
        //背景テクスチャ
        private NyARSurface_XRGB32 _surface;
        /// Direct3D デバイス
        private Device _device = null;
        //表示オブジェクト
        private TextPanel _text;

        private NyARTransMatResult __OnBuffer_nyar_transmat = new NyARTransMatResult();
        private Matrix _trans_mat = new Matrix();
        /* 非同期イベントハンドラ
         * CaptureDeviceからのイベントをハンドリングして、バッファとテクスチャを更新する。
         */
        public void OnBuffer(CaptureDevice i_sender, double i_sample_time, IntPtr i_buffer, int i_buffer_len)
        {
            int w = i_sender.video_width;
            int h = i_sender.video_height;
            int s = w * (i_sender.video_bit_count / 8);
            //テクスチャにRGBを取り込み()
            lock (this)
            {
                //カメラ映像をARのバッファにコピー
                this._raster.setBuffer(i_buffer, i_sender.video_vertical_flip);
                //フレームワークに画像を転送
                this._processor.detectMarker(this._raster);
                //マーカーは見つかったかな？
                if (this._processor.transmat!=null)
                {
                    //あればMatrixを計算
                    this._utils.toD3dMatrix(this._processor.transmat, ref this._trans_mat);
                }
                //テクスチャ内容を更新
                this._surface.CopyFromXRGB32(this._raster);
            }
            return;
        }
        /* キャプチャを開始する関数
         */
        public void StartCap()
        {
            this._cap.StartCapture();
            return;
        }
        /* キャプチャを停止する関数
         */
        public void StopCap()
        {
            this._cap.StopCapture();
            return;
        }


        /* Direct3Dデバイスを準備する関数
         */
        private Device PrepareD3dDevice(Control i_window)
        {
            PresentParameters pp = new PresentParameters();

            pp.Windowed = true;
            pp.SwapEffect = SwapEffect.Flip;
            pp.BackBufferFormat = Format.X8R8G8B8;
            pp.BackBufferCount = 1;
            pp.EnableAutoDepthStencil = true;
            pp.AutoDepthStencilFormat = DepthFormat.D16;
            CreateFlags fl_base = CreateFlags.FpuPreserve;


            try
            {
                return new Device(0, DeviceType.Hardware, i_window.Handle, fl_base | CreateFlags.HardwareVertexProcessing, pp);
            }
            catch (Exception ex1)
            {
                Debug.WriteLine(ex1.ToString());
                try
                {
                    return new Device(0, DeviceType.Hardware, i_window.Handle, fl_base | CreateFlags.SoftwareVertexProcessing, pp);
                }
                catch (Exception ex2)
                {
                    // 作成に失敗
                    Debug.WriteLine(ex2.ToString());
                    try
                    {
                        return new Device(0, DeviceType.Reference, i_window.Handle, fl_base | CreateFlags.SoftwareVertexProcessing, pp);
                    }
                    catch (Exception ex3)
                    {
                        throw ex3;
                    }
                }
            }
        }

        public bool InitializeApplication(Form1 topLevelForm, CaptureDevice i_cap_device)
        {
            topLevelForm.ClientSize = new Size(SCREEN_WIDTH, SCREEN_HEIGHT);
            //キャプチャを作る(QVGAでフレームレートは30)
            i_cap_device.SetCaptureListener(this);
            i_cap_device.PrepareCapture(SCREEN_WIDTH, SCREEN_HEIGHT, 30);
            this._cap = i_cap_device;

            //ARラスタを作る(DirectShowキャプチャ仕様)。
            this._raster = new DsBGRX32Raster(i_cap_device.video_width, i_cap_device.video_height, i_cap_device.video_width * i_cap_device.video_bit_count / 8);

            //AR用カメラパラメタファイルをロードして設定
            NyARParam ap = new NyARParam();
            ap.loadARParamFromFile(AR_CAMERA_FILE);
            ap.changeScreenSize(SCREEN_WIDTH, SCREEN_HEIGHT);

            //プロセッサの準備
            this._processor = new MarkerProcessor(ap, this._raster.getBufferType());
            this._processor.setMarkerWidth(100);

            //Direct3d用のユーティリティ準備
            this._utils = new NyARD3dUtil();

            //3dデバイスを準備する
            this._device = PrepareD3dDevice(topLevelForm);
            this._device.RenderState.ZBufferEnable = true;
            this._device.RenderState.Lighting = false;
            this._device.RenderState.CullMode = Cull.CounterClockwise;

            Viewport vp = new Viewport();
            vp.X = 0;
            vp.Y = 0;
            vp.Height = ap.getScreenSize().h;
            vp.Width = ap.getScreenSize().w;
            vp.MaxZ = 1.0f;
            //ビューポート設定
            this._device.Viewport = vp;

            this._text = new TextPanel(this._device, 1);
            //カメラProjectionの設定
            Matrix tmp = new Matrix();
            this._utils.toCameraFrustumRH(ap, ref tmp);
            this._device.Transform.Projection = tmp;

            // ビュー変換の設定(左手座標系ビュー行列で設定する)
            // 0,0,0から、Z+方向を向いて、上方向がY軸
            this._device.Transform.View = Matrix.LookAtLH(
                new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), new Vector3(0.0f, 1.0f, 0.0f));

            //背景サーフェイスを作成
            this._surface = new NyARSurface_XRGB32(this._device, SCREEN_WIDTH, SCREEN_HEIGHT);

            return true;
        }
        //メインループ処理
        public void MainLoop()
        {
            //ARの計算
            Matrix trans_matrix = this._trans_mat;
            lock (this)
            {
                // 背景サーフェイスを直接描画
                Surface dest_surface = this._device.GetBackBuffer(0, 0, BackBufferType.Mono);
                Rectangle src_dest_rect = new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
                this._device.StretchRectangle(this._surface.d3d_surface, src_dest_rect, dest_surface, src_dest_rect, TextureFilter.None);

                // 3Dオブジェクトの描画はここから
                this._device.BeginScene();
                this._device.Clear(ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

                if (this._processor.current_id!=-1)
                {
                    long d = DateTime.Now.Ticks;
                    float r =  (d / 500000) % 360;
                    Matrix transform_mat2 = Matrix.Scaling(20.0f, 20.0f, 20.0f);
                    transform_mat2 *= Matrix.RotationX((float)(90.0 * 3.14 / 180.0));
                    transform_mat2 *= Matrix.Translation(-50f, 0, 50.0f);

                    transform_mat2 *= Matrix.RotationZ((float)(r * Math.PI / 180));

                    //変換行列を掛ける                     
                    transform_mat2 *= trans_matrix;
                    // 計算したマトリックスで座標変換
                    this._device.SetTransform(TransformType.World, transform_mat2);

                    this._text.draw("MarkerId:" + this._processor.current_id, 1);
                    // レンダリング（描画）
                    this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
                }

                // 描画はここまで
                this._device.EndScene();

                // 実際のディスプレイに描画
                this._device.Present();
            }
            return;
        }

        // リソースの破棄をするために呼ばれる
        public void Dispose()
        {
            lock (this)
            {
                if (this._surface != null)
                {
                    this._surface.Dispose();
                }
                // Direct3D デバイスのリソース解放
                if (this._device != null)
                {
                    this._device.Dispose();
                }
            }
        }
    }
}
