using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Tao.OpenGl;

namespace Ornek2020
{
    class OBJLoader
    {
        public List<Object> ObjectList;
        public List<Vertex> VertexList;
        public List<Normal> NormalList;
        public List<TexCoord> TexCoordList;
        public List<Material> MaterialList;

        public System.Windows.Forms.Label MessageBox;
        public System.Windows.Forms.TextBox TextBox;

        public string OBJName;
        public float OBJposz = 0;

        private int ObjectNum;
        private int ObjListNum;
        private int VertexNum;
        private int NormalNum;
        private int FaceNum;
        private int TexCoordNum;
        private int MaterialNum;
        private int TotalVertices, TotalFaces;

        void AddObject(string oname)
        {
            ObjectNum++;
            ObjectList.Add(new Object(oname));
            FaceNum = -1;

            MessageBox.Text = "Loading part-" + ObjectNum + " (" + oname + ")";
            // + " : " + size1 +" vertices, "+ size2 + " Facees.";
        }

        public void AddVertex()
        {
            VertexNum++;
            VertexList.Add(new Vertex());
        }

        public void AddNormal()
        {
            NormalNum++;
            NormalList.Add(new Normal());
        }

        public void AddTexCoord()
        {
            TexCoordNum++;
            TexCoordList.Add(new TexCoord());
        }

        public void AddMaterial(string Name)
        {
            MaterialNum++;
            MaterialList.Add(new Material(Name));
        }

        public OBJLoader()
        {
            ObjectList = new List<Object>();
            VertexList = new List<Vertex>();
            NormalList = new List<Normal>();
            TexCoordList = new List<TexCoord>();
            MaterialList = new List<Material>();

            ObjectNum = VertexNum = NormalNum = TexCoordNum = MaterialNum = -1;
            TotalVertices = TotalFaces = 0;
        }


        float MinVal(float x1, float x2)
        {
            if (x1 < x2) return x1;
            return x2;
        }

        float MaxVal(float x1, float x2)
        {
            if (x1 > x2) return x1;
            return x2;
        }

        float NormalizeModel()
        {
            float minx = 1e10f, miny = 1e10f, minz = 1e10f;
            float maxx = -1e10f, maxy = -1e10f, maxz = -1e10f;

            for (int i = 0; i < VertexList.Count; i++)
            {
                minx = MinVal(minx, VertexList[i].X);
                maxx = MaxVal(maxx, VertexList[i].X);
                miny = MinVal(miny, VertexList[i].Y);
                maxy = MaxVal(maxy, VertexList[i].Y);
                minz = MinVal(minz, VertexList[i].Z);
                maxz = MaxVal(maxz, VertexList[i].Z);
            }

            // Örnekler [minval, maxval]
            // [ 10, 20] --> ort= 15, min= -5, max= +5 olmalı xnew = x-ort
            // [-10, 30] --> ort= 10, min=-20, max=+20 olmalı xnew = x-ort
            // [-20,-10] --> ort=-15, min= -5, max= +5 olmalı xnew = x-ort
            float ortx = (minx + maxx) / 2.0f;
            float orty = (miny + maxy) / 2.0f;
            float ortz = (minz + maxz) / 2.0f;

            for (int i = 0; i < VertexList.Count; i++)
            {
                VertexList[i].X -= ortx;
                VertexList[i].Y -= orty;
                VertexList[i].Z -= ortz;
            }

            // Kontrol için
            minx = miny = minz = 1e10f;
            maxx = maxy = maxz = -1e10f;
            ObjectNum = 0;
            for (int i = 0; i < VertexList.Count; i++)
            {
                minx = MinVal(minx, VertexList[i].X);
                maxx = MaxVal(maxx, VertexList[i].X);
                miny = MinVal(miny, VertexList[i].Y);
                maxy = MaxVal(maxy, VertexList[i].Y);
                minz = MinVal(minz, VertexList[i].Z);
                maxz = MaxVal(maxz, VertexList[i].Z);
            }

            // Z pozisyonu obje ekran sığacak şekilde geriye çekilmeli
            float zpos1 = -2f * maxy / (float)Math.Tan(22.5);
            float zpos2 = -2f * maxx / (float)Math.Tan(22.5);
            return MinVal(MinVal(zpos1, zpos2), minz);
        }

        Normal GetNormal(Vertex v1, Vertex v2, Vertex v3)
        {
            Normal normal = new Normal();
            normal.Nx = v1.Y * (v2.Z - v3.Z) + v2.Y * (v3.Z - v1.Z) + v3.Y * (v1.Z - v2.Z);
            normal.Ny = v1.Z * (v2.X - v3.X) + v2.Z * (v3.X - v1.X) + v3.Z * (v1.X - v2.X);
            normal.Nz = v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y);
            float len = normal.Nx * normal.Nx + normal.Ny * normal.Ny + normal.Nz * normal.Nz;
            len = (float)Math.Sqrt(len);
            normal.Nx = normal.Nx / len;
            normal.Ny = normal.Ny / len;
            normal.Nz = normal.Nz / len;
            return normal;
        }

        public void LoadOBJ(string fileName)
        {
            OBJName = fileName;
            string item;
            int index;
            float xx, yy, zz;
            ObjectNum = -1;

            try
            {            
                foreach (string objLine in File.ReadLines(fileName))
                {
                    string[] input = objLine.Split((string[])null, StringSplitOptions.RemoveEmptyEntries);
                    if (input.Length == 0) continue; // Boş satırdır
                    index = 0;
                    item = input[0];
                    if (item == "#") continue; // Açıklama satırıdır
                    if (item == "mtllib")
                    {
                        try
                        {
                            foreach (string mtlLine in File.ReadLines(input[1]))
                            {
                                input = mtlLine.Split((string[])null, StringSplitOptions.RemoveEmptyEntries);
                                if (input.Length == 0) continue; // Boş satırdır
                                index = 0;
                                item = input[0];
                                if (item == "#") continue; // Açıklama satırıdır
                                if (item == "newmtl")
                                {
                                    AddMaterial(input[1]);
                                }
                                else if (item == "Ns")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Ns = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                                else if (item == "Ni")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Ni = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                                else if (item == "d")
                                {
                                    item = input[1];
                                    float tr = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    MaterialList[MaterialNum].Tr = tr;
                                    MaterialList[MaterialNum].Ka[3] = tr;
                                    MaterialList[MaterialNum].Kd[3] = tr;
                                    MaterialList[MaterialNum].Ks[3] = tr;
                                }
                                else if (item == "Tr")
                                {
                                    item = input[1];
                                    float tr = 1.0f-float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    MaterialList[MaterialNum].Tr = tr;
                                    MaterialList[MaterialNum].Ka[3] = tr;
                                    MaterialList[MaterialNum].Kd[3] = tr;
                                    MaterialList[MaterialNum].Ks[3] = tr;
                                }
                                else if (item == "illum")
                                {
                                    MaterialList[MaterialNum].Illum = int.Parse(input[1]);
                                }
                                else if (item == "Ka")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Ka[0] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[2];
                                    MaterialList[MaterialNum].Ka[1] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[3];
                                    MaterialList[MaterialNum].Ka[2] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                                else if (item == "Kd")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Kd[0] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[2];
                                    MaterialList[MaterialNum].Kd[1] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[3];
                                    MaterialList[MaterialNum].Kd[2] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                                else if (item == "Ks")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Ks[0] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[2];
                                    MaterialList[MaterialNum].Ks[1] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[3];
                                    MaterialList[MaterialNum].Ks[2] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                                else if (item == "Ke")
                                {
                                    item = input[1];
                                    MaterialList[MaterialNum].Ke[0] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[2];
                                    MaterialList[MaterialNum].Ke[1] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                    item = input[3];
                                    MaterialList[MaterialNum].Ke[2] = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                                }
                            } // foreach mtlLine
                        }
                        catch (FileNotFoundException fnf)
                        {
                            continue;
                        }
                    }
                    else if (item == "object")
                    {
                        // İsmiyle bir obje açılır.
                        AddObject(input[++index]);
                        FaceNum = -1;
                    }
                    else if (item == "g" && ObjectNum < 0)
                    {
                        // İlk obje açılır.
                        AddObject(input[++index]);
                        FaceNum = -1;
                    }
                    else if (item == "o")
                    {
                        AddObject(input[1]);
                    }
                    else if (item == "usemtl")
                    {
                        // İsminden materyal bulunur.
                        for (int i = 0; i < MaterialList.Count; i++)
                            if (MaterialList[i].Name == input[1])
                            {
                                ObjectList[ObjectNum].FaceMaterial = 
                                    MaterialList[i];
                                break;
                            }                        
                    }
                    else if (item == "v")
                    {
                        AddVertex();

                        item = input[++index];
                        xx = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        VertexList[VertexNum].X = xx;
                        item = input[++index];
                        yy = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        VertexList[VertexNum].Y = yy;
                        item = input[++index];
                        zz = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        VertexList[VertexNum].Z = zz;
                    }
                    else if (item == "vn")
                    {
                        AddNormal();

                        item = input[++index];
                        xx = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        NormalList[NormalNum].Nx = xx;
                        item = input[++index];
                        yy = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        NormalList[NormalNum].Ny = yy;
                        item = input[++index];
                        zz = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        NormalList[NormalNum].Nz = zz;
                    }
                    else if (item == "vt")
                    {
                        AddTexCoord();

                        item = input[++index];
                        xx = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        TexCoordList[TexCoordNum].S = xx;
                        item = input[++index];
                        yy = float.Parse(item, CultureInfo.InvariantCulture.NumberFormat);
                        TexCoordList[TexCoordNum].T = yy;
                    }
                    else if (item == "f")
                    {
                        string[] pnum; // Face stringi
                        int vind, tind, nind; // Face'den okunan indeksler

                        if (FaceNum < 0) FaceNum = 0;
                        ObjectList[ObjectNum].AddFace();

                        // Kaç köşe tanımı varsa hepsini alır
                        while (index < input.Length - 1)
                        {
                            index++;
                            item = input[index];
                            vind = tind = nind = 0;
                            if (item.IndexOf("/") >= 0)
                            {
                                pnum = item.Split('/');
                                if (pnum[0] != "") vind = int.Parse(pnum[0]) - 1;
                                if (pnum[1] != "") tind = int.Parse(pnum[1]) - 1;
                                if (pnum[2] != "") nind = int.Parse(pnum[2]) - 1;
                                ObjectList[ObjectNum].FaceList[FaceNum].VertexIndex.Add(vind);
                                ObjectList[ObjectNum].FaceList[FaceNum].TexCoordIndex.Add(tind);
                                ObjectList[ObjectNum].FaceList[FaceNum].NormalIndex.Add(nind);
                            }
                            else
                            {  // Eğer '/' yoksa, yani satır f 1 2 3 gibiyse
                               // sadece köşe tanımları vardır.
                                vind = int.Parse(item) - 1;
                                ObjectList[ObjectNum].FaceList[FaceNum].VertexIndex.Add(vind);
                            }
                        }
                        FaceNum++;
                        TotalFaces++;
                    }
                } // foreach sonu
                // Modeli merkeze oturtur.
                OBJposz = NormalizeModel();

                // Compile edersek daha da hızlanır.
                ObjListNum = Gl.glGenLists(1); 
                Gl.glNewList(ObjListNum, Gl.GL_COMPILE);

                int minObj = 0;
                //TextBox.Text = ObjectList[minObj].Name;
                //int maxObj = 157;
                ObjectNum = minObj;
                while (ObjectNum < ObjectList.Count) // 
                {
                    Gl.glDisable(Gl.GL_BLEND);
                    Material mat = ObjectList[ObjectNum].FaceMaterial;
                    if (mat != null)
                    {
                        if (mat.Illum < 0)
                        {
                            ObjectNum++;
                            continue; // Color off
                        }
                        if (mat.Tr > 0)
                        {
                            Gl.glEnable(Gl.GL_BLEND);
                            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
                        }
                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT, mat.Ka);
                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_DIFFUSE, mat.Kd);
                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SPECULAR, mat.Ks);
                        Gl.glMaterialf(Gl.GL_FRONT, Gl.GL_SHININESS, mat.Ns);
                    }
                    else
                    {
                        float[] metalAmb = { 0.1f, 0.1f, 0.1f, 1.0f };
                        float[] metalDif = { 0.4f, 0.4f, 0.4f, 1.0f };
                        float[] metalSpe = { 0.6f, 0.6f, 0.6f, 1.0f };

                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT, metalAmb);
                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_DIFFUSE, metalDif);
                        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SPECULAR, metalSpe);
                        Gl.glMateriali(Gl.GL_FRONT, Gl.GL_SHININESS, 32);
                    }
                    for (int i = 0; i < ObjectList[ObjectNum].FaceList.Count; i++)
                    {
                        Face fc = ObjectList[ObjectNum].FaceList[i];

                        if (NormalList.Count > 0)
                        {
                            Gl.glBegin(Gl.GL_POLYGON);
                            for (int k = 0; k < fc.VertexIndex.Count; ++k)
                            {
                                Vertex v = VertexList[(int)fc.VertexIndex[k]];
                                Normal n = NormalList[(int)fc.NormalIndex[k]];

                                Gl.glNormal3f(n.Nx, n.Ny, n.Nz);
                                Gl.glVertex3f(v.X, v.Y, v.Z);
                            }
                            Gl.glEnd();
                        }
                        else
                        {
                            // Üç köşeden Normali bul
                            Normal n = GetNormal(
                                VertexList[(int)fc.VertexIndex[0]],
                                VertexList[(int)fc.VertexIndex[1]],
                                VertexList[(int)fc.VertexIndex[2]]);
                            Gl.glBegin(Gl.GL_POLYGON);
                            Gl.glNormal3f(n.Nx, n.Ny, n.Nz);
                            for (int k = 0; k < fc.VertexIndex.Count; ++k)
                            {
                                Vertex v = VertexList[(int)fc.VertexIndex[k]];
                                Gl.glVertex3f(v.X, v.Y, v.Z);
                            }
                            Gl.glEnd();
                        }
                    }
                    ObjectNum++;
                }
                Gl.glEndList( );

                TotalVertices += (VertexNum + 1);
                TotalFaces += (FaceNum + 1);
                MessageBox.Text = "Total : " + TotalVertices + " vertices, " + TotalFaces + " Faces.";
            }
            catch (FileNotFoundException fnf)
            {
                return;
            }
        }

        public void ShowOBJ()
        {
            Gl.glCallList(ObjListNum);
        }
    }
}
