using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

// TODO: replace these with the processor input and output types.
using TInput = System.String;
using TOutput = System.String;
using System.ComponentModel;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using Microsoft.Xna.Framework.Content;
using System.Drawing;

namespace GroundImporter
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    ///
    /// TODO: change the ContentProcessor attribute to specify the correct
    /// display name for this processor.
    /// </summary>
    [ContentProcessor(DisplayName = "GroundImporter")]
    public class GroundImporter : ModelProcessor
    {

        /// <summary>頂点リストの分割数。</summary>
        int BpoSegment1 = 100;

        /// <summary>分割の余裕数。小さければ計算は速くなるが、制度が落ちる</summary>
        float BpoSegment2 = 10.0f;

        /// <summary>
        /// 図形の分割数
        /// </summary>
        ushort Dividecount = 1024;
        // 規定値の指定
        [DefaultValue(1024)]
        // 表示される文字列の指定
        [DisplayName("Divide Count")]
        // パラメーターの説明
        [Description("図形の分割数。多ければ多いほど制度はあがるが、計算処理は重くなり実行時のメモリを消費する。標準は1024")]
        // プロセッサパラメーター
        public ushort DivideCount
        {
            get { return Dividecount; }
            set { Dividecount = value; }
        }


        /// <summary>
        /// 衝突点が検出できなかった場合の動作フラグ
        /// </summary>
        bool UnHitfill = true;
        // 規定値の指定
        [DefaultValue(true)]
        // 表示される文字列の指定
        [DisplayName("UnHit Fill LowestPoint")]
        // パラメーターの説明
        [Description("衝突点が検出できなかった場合の動作。　trueであった場合、存在した頂点の中の最も低い点を使用する。falseの場合は float.MinValue を使用する")]
        // プロセッサパラメーター
        public bool UnHitFill
        {
            get { return UnHitfill; }
            set { UnHitfill = value; }
        }

        /// <summary>
        /// 衝突点が検出できなかった場合の動作フラグ
        /// </summary>
        bool makeimage = false;
        // 規定値の指定
        [DefaultValue(false     )]
        // 表示される文字列の指定
        [DisplayName("Put HeightProcessed Image")]
        // パラメーターの説明
        [Description("計算後の高さ情報を、画像として出力するかどうかのフラグ")]
        // プロセッサパラメーター
        public bool makeImage
        {
            get { return makeimage; }
            set { makeimage = value; }
        }
        /// <summary>
        /// メインプロセス
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            if (System.Diagnostics.Debugger.IsAttached)
                System.Diagnostics.Debugger.Break();

              ModelContent model = base.Process(input, context);
              putObj = new HeightMapSetter();

              putObj.SetedFact = new float[2];
              putObj.P_pos = new float[(int)Ground_Pos_ID.maxval];
              //各格納地の初期化
              putObj.P_pos[(int)Ground_Pos_ID.X_max] = float.MinValue;
              putObj.P_pos[(int)Ground_Pos_ID.X_min] = float.MaxValue;
              putObj.P_pos[(int)Ground_Pos_ID.Z_max] = float.MinValue;
              putObj.P_pos[(int)Ground_Pos_ID.Z_min] = float.MaxValue;
              putObj.P_pos[(int)Ground_Pos_ID.Y_max] = float.MinValue;
              putObj.P_pos[(int)Ground_Pos_ID.Y_min] = float.MaxValue;

              try
              {
                  //頂点リストを取得する
                  TraverseHierarchy(input);

                  //頂点リストの、最大値と最小値をセットする
                  getmaxes();

                  putObj.HeightMap = new float[DivideCount][];
                  process2();
                  if (makeimage)
                  {
                      Process3(input.Identity.SourceFilename + "_h.png");
                  }
              }
              catch(Exception e)
              {
                  String s = e.Message;
                  // TODO: process the input object, and return the modified data.
                  throw new NotImplementedException(e.Message);
              }
              model.Tag = putObj;
              return model;
        }

        /// <summary>頂点情報格納リスト</summary>
        //List<Vector3> VerList = new List<Vector3>();

        /// <summary>tagで付加される情報</summary>
        HeightMapSetter putObj;

        /// <summary>３角形の頂点構造体</summary>
        private struct Triangle
        {
            public Vector3[] vx;
        }

        /// <summary>ポリゴン情報の格納</summary>
        List<Triangle> TriangleList = new List<Triangle>();


        /// <summary>ある程度分割されたリスト</summary>
        List<Triangle>[][] BpoLists;

        /// <summary>
        /// 最大値と最小値の差を、BpoSegment1で割った数
        /// </summary>
        int divideFactX, divideFactZ;

        float BPOtoAllay_X, BPOtoAllay_Z;

        int missingCount = 0;

        /// <summary>
        /// 指定されたNodeContentツリーを巡回する
        /// </summary>
        void TraverseHierarchy(NodeContent node)
        {

            // 子ノードを巡回する
            foreach (NodeContent child in node.Children)
            {
                TraverseHierarchy(child);
            }

            // このノードはMeshContentか？
            MeshContent mesh = node as MeshContent;

            if (mesh != null)
            {
                // MeshContent内のジオメトリをループする。
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    IList<int> indices = geometry.Indices;

                    // 頂点情報を取得
                    IList<Microsoft.Xna.Framework.Vector3> positions = geometry.Vertices.Positions;

                    // 頂点チャンネルから法線情報を取得 ･･･今はいらないけどね
                    IList<Microsoft.Xna.Framework.Vector3> normals = geometry.Vertices.Channels.Get<Microsoft.Xna.Framework.Vector3>(VertexChannelNames.Normal());

                    for (int triangle = 0; triangle < indices.Count; triangle += 3)
                    {
                        // 三角形のインデックスを取得
                        int i1 = indices[triangle];
                        int i2 = indices[triangle + 1];
                        int i3 = indices[triangle + 2];

                        // 三角形頂点、triPos1 .. 3と法線情報　triNormal1 ..3を取得
                        Vector3 triPos1 = positions[i1];
                        Vector3 triPos2 = positions[i2];
                        Vector3 triPos3 = positions[i3];

                        Vector3 triNormal1 = normals[i1];
                        Vector3 triNormal2 = normals[i2];
                        Vector3 triNormal3 = normals[i3];

                        // ここで三角形の情報を使った処理をする

                        //三角形をそのままリストへ格納する
                        Triangle t = new Triangle();
                        t.vx = new Vector3[3];
                        t.vx[0] = positions[i1];
                        t.vx[1] = positions[i2];
                        t.vx[2] = positions[i3];
                        TriangleList.Add(t);
                    }
                }
            }
        }

        /// <summary>
        /// 頂点リストの、最大値と最小値をセットする
        /// </summary>
        /// <param name="putObj"></param>
        void getmaxes()
        {
            foreach (Triangle t in TriangleList)
            {
                foreach (Vector3 v in t.vx)
                {
                    if (putObj.P_pos[(int)Ground_Pos_ID.X_max] < v.X) putObj.P_pos[(int)Ground_Pos_ID.X_max] = v.X;
                    if (putObj.P_pos[(int)Ground_Pos_ID.X_min] > v.X) putObj.P_pos[(int)Ground_Pos_ID.X_min] = v.X;

                    if (putObj.P_pos[(int)Ground_Pos_ID.Y_max] < v.Y) putObj.P_pos[(int)Ground_Pos_ID.Y_max] = v.Y;
                    if (putObj.P_pos[(int)Ground_Pos_ID.Y_min] > v.Y) putObj.P_pos[(int)Ground_Pos_ID.Y_min] = v.Y;

                    if (putObj.P_pos[(int)Ground_Pos_ID.Z_max] < v.Z) putObj.P_pos[(int)Ground_Pos_ID.Z_max] = v.Z;
                    if (putObj.P_pos[(int)Ground_Pos_ID.Z_min] > v.Z) putObj.P_pos[(int)Ground_Pos_ID.Z_min] = v.Z;
                }
            }
        }

        /// <summary>
        /// 計算速度を速めるため、ポリゴンリストをグループに分ける
        /// </summary>
        void DivideTriangle()
        {
            //下準備。構造体を用意する
            //最大値と最小値の差を、BpoSegment1で割った数が、配列数になる（+1は、あまりを考慮した念のためのもの）
            divideFactX = (int)(putObj.P_pos[(int)Ground_Pos_ID.X_MintoMax] / BpoSegment1);
            divideFactZ = (int)(putObj.P_pos[(int)Ground_Pos_ID.Z_MintoMax] / BpoSegment1);
            BpoLists = new List<Triangle>[divideFactZ + 1][];
            for (int z = 0; z < BpoLists.Length; z++)
            {
                BpoLists[z] = new List<Triangle>[divideFactX + 1];
                for (int x = 0; x < BpoLists[z].Length; x++)
                {
                    BpoLists[z][x] = new List<Triangle>();
                }
            }

            //ポリゴンの分割処理
            //１つの頂点が複数のListの中に入ることもあるが、気にしなくてよい。
            float tmpFZ,tmpFX, difMax;
            foreach (Triangle t in TriangleList)
            {
                bool intoFlg = false;
                int nowZ, nowX;
                for (int z = 0; z < BpoLists.Length; z++)
                {
                    tmpFZ = putObj.P_pos[(int)Ground_Pos_ID.Z_min] + z * BpoSegment1;
                    tmpFZ = t.vx[0].Z - tmpFZ;

                    if (Math.Abs(tmpFZ) < BpoSegment1 + BpoSegment2)
                    {
                        //Z決定。Xも算出
                        for (int x = 0; x < BpoLists[0].Length; x++)
                        {
                            tmpFX = putObj.P_pos[(int)Ground_Pos_ID.X_min] + x * BpoSegment1;
                            tmpFX = t.vx[0].X - tmpFX;
                            if (Math.Abs(tmpFX) < BpoSegment1 + BpoSegment2)
                            {
                                //Xも決定。ここに入れる
                                intoFlg = true;
                                BpoLists[z][x].Add(t);
                            }
                        }
                    }
                }
                if (!intoFlg)
                {
                    throw new Exception("VertexList Divide Err!!");
                }
            }
           
        }

        /// <summary>
        /// heightmapの計算処理
        /// </summary>
        void process2()
        {
            putObj.SetedFact = new float[2];

            //factorを決定

            //ＸとＺの、「最小値から最大値までの間の値」を取得する
            float tmpf = putObj.P_pos[(int)Ground_Pos_ID.X_max] - putObj.P_pos[(int)Ground_Pos_ID.X_min];
            putObj.P_pos[(int)Ground_Pos_ID.X_MintoMax] = tmpf;

            // 分割数を上記で求めた値で割ると、「配列が1移動したときの、Ｘ軸の移動量」が分かる
            //が、下記で使う「分割ポリゴンリストが1移動したときへの対応地」は、別になる。
            BPOtoAllay_X = tmpf / DivideCount;
            putObj.SetedFact[0] = DivideCount / tmpf;

            //Ｚについても同様のことを
            tmpf = putObj.P_pos[(int)Ground_Pos_ID.Z_max] - putObj.P_pos[(int)Ground_Pos_ID.Z_min];
            putObj.P_pos[(int)Ground_Pos_ID.Z_MintoMax] = tmpf;
            BPOtoAllay_Z = tmpf / DivideCount;
            putObj.SetedFact[1] = DivideCount / tmpf;

            //Yも
            tmpf = putObj.P_pos[(int)Ground_Pos_ID.Y_max] - putObj.P_pos[(int)Ground_Pos_ID.Y_min];
            putObj.P_pos[(int)Ground_Pos_ID.Y_MintoMax] = tmpf;


            //ここでベクトルを決定しておく
            putObj.SetedfactLength = new Vector2(putObj.SetedFact[0], putObj.SetedFact[1]).Length();

            //計算速度向上のため、リストを分割する
            DivideTriangle();
            //上記で求めた値を使い、配列に「その地点の高さ」をひたすら記憶していく

            float nowX, nowZ, nowY, nowY2;
            bool hitflg;
            nowY = putObj.P_pos[(int)Ground_Pos_ID.Y_max] + 10;
            nowY2 = putObj.P_pos[(int)Ground_Pos_ID.Y_min] - 10;

            bool[][] allHitFlg = new bool[DivideCount][];

            for (int z = 0; z < DivideCount; z++)
            {
                putObj.HeightMap[z] = new float[DivideCount];
                allHitFlg[z] = new bool[DivideCount];
                nowZ = putObj.P_pos[(int)Ground_Pos_ID.Z_min] + BPOtoAllay_Z * z;
                for (int x = 0; x < DivideCount; x++)
                {
                    nowX = putObj.P_pos[(int)Ground_Pos_ID.X_min] + BPOtoAllay_X * x;
                    putObj.HeightMap[z][x] = getHeight(nowX, nowY, nowY2, nowZ, out hitflg);
                    allHitFlg[z][x] = hitflg;
                }
            }

            //最終的に検出できなかったら、周囲の点から保管するしかない
            for (int z = 0; z < DivideCount; z++)
            {
                for (int x = 0; x < DivideCount; x++)
                {
                    if (!allHitFlg[z][x])
                    {
                        if (UnHitfill)
                        {
                            //最終的に検出できなかったら、周囲の点から保管するしかない
                            int sumC = 0;
                            int tgtX_Max, tgtX_Min, tgtZ_Max, tgtZ_Min;
                            float tmpH = 0;


                            tgtZ_Max = Math.Min(z + 2, DivideCount);
                            tgtZ_Min = Math.Max(z - 1, 0);

                            tgtX_Max = Math.Min(x + 2, DivideCount);
                            tgtX_Min = Math.Max(x - 1, 0);
                            for (int tmpZ = tgtZ_Min; tmpZ < tgtZ_Max; tmpZ++)
                            {
                                for (int tmpX = tgtX_Min; tmpX < tgtX_Max; tmpX++)
                                {
                                    if (tmpX != x || tmpZ != z)
                                    {
                                        sumC++;
                                        tmpH += putObj.HeightMap[tmpZ][tmpX];
                                    }
                                }
                            }
                            putObj.HeightMap[z][x] = tmpH / sumC;
                        }
                        else
                        {
                            putObj.HeightMap[z][x] = float.MinValue;
                        }
                    }
                }
            }

  
        }

        private void Process3(string filename)
        {
            //ファイルに書き出す？
            Bitmap putImg = new Bitmap(putObj.HeightMap[0].Length, putObj.HeightMap.Length);
            System.Drawing.Color nowC = new System.Drawing.Color();

            for (int z = 0; z < Dividecount; z++)
            {
                for (int x = 0; x < Dividecount; x++)
                {
                    //Yの最大値を１とし、画像を作成する
                    float yPower = putObj.HeightMap[z][x] - putObj.P_pos[(int)Ground_Pos_ID.Y_MintoMax];
                    yPower = yPower / putObj.P_pos[(int)Ground_Pos_ID.Y_max];

                    byte nowB = (byte)(byte.MaxValue * yPower);
                    putImg.SetPixel(putObj.HeightMap[0].Length - x - 1, putObj.HeightMap.Length - z - 1, System.Drawing.Color.FromArgb(255, nowB, nowB, nowB));
                }
            }
            putImg.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
            putImg.Dispose();
        }

        /// <summary>
        /// 引数の地点に衝突する３角形を求める
        /// </summary>
        /// <param name="x"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        float getHeight(float x, float y_s, float y_e, float z, out bool o_hitflg)
        {
            bool m_hitglf = false;
            float nowHeight = putObj.P_pos[(int)Ground_Pos_ID.Y_min];
            Vector3 v_s, v_e, refV;
            v_s = new Vector3(x, y_s, z);
            v_e = new Vector3(x, y_e, z);

            
            float tmpFZ, tmpFX;
            int prosessedX=0, prosessedZ=0;
            //分割されたポリゴンリストから、衝突検出対象のポリゴンを探す
            for (int loopZ = 0; loopZ < BpoLists.Length; loopZ++)
            {
                tmpFZ = putObj.P_pos[(int)Ground_Pos_ID.Z_min] + loopZ * BpoSegment1;
                tmpFZ = z - tmpFZ;
                if (Math.Abs(tmpFZ) < BpoSegment1 + BpoSegment2)
                {
                    prosessedZ = loopZ;
                    for (int loopX = 0; loopX < BpoLists.Length; loopX++)
                    {
                        tmpFX = putObj.P_pos[(int)Ground_Pos_ID.X_min] + loopX * BpoSegment1;
                        tmpFX = x - tmpFX;
                        if (Math.Abs(tmpFX) < BpoSegment1 + BpoSegment2)
                        {
                            prosessedX = loopX;
                            foreach (Triangle t in BpoLists[loopZ][loopX])
                            {
                                bool l_hitflg = false;
                                refV = Vector3.Zero;
                                Judge_Line_Plane(ref v_s, ref v_e, t, ref refV, ref l_hitflg);

                                //衝突点があったら、「その点が一番高い点であったか」を確認する（ポリゴンの重なりを考慮する）
                                //重なりを考慮するため、衝突点が見つかってもbreakはしない。そのため、この処理はすんげー時間がかかる
                                //ところで「すんげー」って、もしかして群馬弁？
                                if (l_hitflg)
                                {
                                    m_hitglf = true;
                                    if (nowHeight < refV.Y) nowHeight = refV.Y;
                                }
                            }
                            break;
                        }
                    }
                    break;
                }               
            }

            if (!m_hitglf)
            {
                //検出できなかったら、隣接するセグメントを検索する
                int tgtX_Max, tgtX_Min, tgtZ_Max, tgtZ_Min;

                tgtZ_Max = Math.Min(prosessedZ + 2, BpoLists.Length);
                tgtZ_Min = Math.Max(prosessedZ - 1, 0);

                tgtX_Max = Math.Min(prosessedX + 2, BpoLists[0].Length);
                tgtX_Min = Math.Max(prosessedX - 1, 0);

                for (int z2 = tgtZ_Min; z2 < tgtZ_Max; z2++)
                {
                    for (int x2 = tgtX_Min; x2 < tgtX_Max; x2++)
                    {
                        //上のループで見た場所は、やる必要はない。
                        if (z2 != prosessedZ || x2 != prosessedX)
                        {
                            foreach (Triangle t in BpoLists[z2][x2])
                            {
                                bool l_hitflg = false;
                                refV = Vector3.Zero;
                                Judge_Line_Plane(ref v_s, ref v_e, t, ref refV, ref l_hitflg);
                                if (l_hitflg)
                                {
                                    m_hitglf = true;
                                    if (nowHeight < refV.Y) nowHeight = refV.Y;
                                }
                            }
                            if (m_hitglf) break;
                        }
                    }
                    if (m_hitglf) break;
                }

            }
            
           /*
            foreach (Triangle t in TriangleList)
            {
                bool l_hitflg = false;
                refV = Vector3.Zero;
                Judge_Line_Plane(ref v_s,ref v_e, t,ref refV,ref l_hitflg);

                //衝突点があったら、「その点が一番高い点であったか」を確認する（ポリゴンの重なりを考慮する）
                //重なりを考慮するため、衝突点が見つかってもbreakはしない。そのため、この処理はすんげー時間がかかる
                //ところで「すんげー」って、もしかして群馬弁？
                if (l_hitflg)
                {
                    if (nowHeight < refV.Y) nowHeight = refV.Y;
                }
            }
            */
            //どこにも当たらなかった場合、Ｙの最低地が返る･･
            //いちおーかなりまずい状態なので、エラーを返すことにする?
            if (!m_hitglf)
            {
                if (z != putObj.P_pos[(int)Ground_Pos_ID.Z_min] && x != putObj.P_pos[(int)Ground_Pos_ID.X_min])
                {
                    missingCount++;
                    //throw new Exception("高さ検出エラー：X:" + x.ToString() + ", Z:" + z.ToString());
                }
            }


            o_hitflg = m_hitglf;
            return nowHeight;
        }

        /// <summary>
        /// 指定された線分と３つの頂点の衝突点を検出する　　はず
        /// </summary>
        /// <param name="p">線分開始位置</param>
        /// <param name="q">線分終点位置</param>
        /// <param name="v">頂点情報（配列数：３）</param>
        /// <param name="refV">衝突点</param>
        /// <param name="flg">実際に衝突したかどうかのフラグ</param>
        void Judge_Line_Plane(ref Vector3 p, ref Vector3 q, Triangle triangle, ref Vector3 refV, ref bool flg)
       {
           Vector3 l;    //交点の位置ベクトル
           // 平面の方程式より n（法線ベクトル）を求める
           // ax + bx + cz + d = 0
           Vector3 n = (triangle.vx[1] - triangle.vx[0]), p2 = (triangle.vx[2] - triangle.vx[0]);
           Vector3 p1 = n;
 
           Vector3.Cross(ref p1, ref p2, out  n);    //（外積）

           // 直線の方程式より u を求める
           // l = p + tu
           Vector3 u = q - p;

           // tを求めることで、交点を求める
           float top = n.X * (triangle.vx[0].X - p.X) + n.Y * (triangle.vx[0].Y - p.Y) + n.Z * (triangle.vx[0].Z - p.Z);
           float bottom = Vector3.Dot(n, u);    //（内積）

           // 平行判定
           if (bottom == 0.0f) { flg = false; return; }    //平行である。

           // tを求める
           float t = top / bottom;

           // 0 =< t =< 1 以外の場合、交差していないので抜ける
           if (t < 0.0f || t > 1.0f) { flg = false; return; }

           // 面と線の交点を求める
           l = p + t * u;
           // 三角形内外判定    //triangle とn(法線ベクトル)が必要
           for (int i = 0; i < 3; i++)
           {
               // 外積を利用して内外判定
               Vector3 pn = (triangle.vx[((i + 1) == 3) ? 0 : i + 1] - triangle.vx[i]);
               //n = new Vector3(0, 1, 0);
               p1 = pn;
               p2 = (l - triangle.vx[i]);
               //Vector3.Cross(ref pn, ref p1, out p2);    //（外積）
               Vector3.Cross(ref p1, ref p2, out pn);    //（外積）

               // 法線の方向判定用
               pn.X = pn.X * n.X;
               pn.Y = pn.Y * n.Y;
               pn.Z = pn.Z * n.Z;

               // すべての場合の法線が同一方向ならば、l は三角ポリゴン内に存在する
               // -0.0001 は誤差を考慮
               if ((pn.X < -0.0001) || (pn.Y < -0.0001) || (pn.Z < -0.0001)) { flg = false; return; }
           }
           refV = l;
           flg = true;    //交点有り
       }

    }


    [ContentTypeWriter]
    public class HeightMapSetterWriter : ContentTypeWriter<HeightMapSetter>
    {
        protected override void Write(ContentWriter output, HeightMapSetter tagObj)
        {
            output.WriteObject<float[]>(tagObj.SetedFact);
            output.WriteObject<float>(tagObj.SetedfactLength);
            output.WriteObject<float[]>(tagObj.P_pos);
            output.WriteObject<float[][]>(tagObj.HeightMap);
        }

        public override string GetRuntimeType(TargetPlatform targetPlatform)
        {
            //return "GroundImporter.HeightMapSetter, GroundImporter";
            return typeof(HeightMapSetter).AssemblyQualifiedName;
        }

        public override string GetRuntimeReader(TargetPlatform targetPlatform)
        {
            //return "GroundImporter.HeightMapReader, GroundImporter";
            if (targetPlatform == TargetPlatform.Windows)
            {
                return typeof(HeightMapReader).AssemblyQualifiedName;
            }
            else 
            {
                return "Junk_XNA.HeightMapReader, Junk_XNA";
            }
        } 
    }

    
}