using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MojaPierwszaGraMonoGame
{
    class Kwadryka
    {
        private GraphicsDevice gd;
        private VertexBuffer buforWerteksów = null;
        private int liczbaVertexów;
        private int liczbaWerteksówWPaśmie;
        private int liczbaTrójkątówWPaśmie;
        private int liczbaPasm = 1;
        private IndexBuffer indexBuffer;
        private bool użycieBuforaIndeksów;

        private Kwadryka(GraphicsDevice gd, int liczbaSekcji)
        {
            this.gd = gd;
            liczbaTrójkątówWPaśmie = 2 * liczbaSekcji;
            liczbaWerteksówWPaśmie = 2 + liczbaTrójkątówWPaśmie;
            użycieBuforaIndeksów = false;
        }

        #region Metody statyczny
        static private VertexPositionColor[] BudujTablicęWerteksówŚciętegoStożka(
            float promieńDolny, float promieńGórny, float położeniePodstawy, float wysokość, int liczbaSekcji, 
            Color kolor)
        {
            int liczbaWerteksów = 2 * (liczbaSekcji + 1);

            VertexPositionColor[] tablicaWerteksów = new VertexPositionColor[liczbaWerteksów];
            float przyrostKąta = MathHelper.TwoPi / liczbaSekcji;

            for (int i = 0; i <= liczbaSekcji; i++)
            {
                float kąt = i * przyrostKąta;
                Vector3 kierunek = new Vector3((float)Math.Cos(kąt), (float)Math.Sin(kąt), 0);
                tablicaWerteksów[2 * i].Position = promieńDolny * kierunek + położeniePodstawy * Vector3.UnitZ;                
                tablicaWerteksów[2 * i].Color = kolor;
                tablicaWerteksów[2 * i + 1].Position = promieńGórny * kierunek + (położeniePodstawy + wysokość) * Vector3.UnitZ;
                tablicaWerteksów[2 * i + 1].Color = kolor;
            }

            return tablicaWerteksów;
        }

        static private VertexPositionColor[] BudujTablicęWerteksówOkręgu(
            float promień, float położeniePodstawy, int liczbaSekcji,
            Color kolor)
        {
            int liczbaWerteksów = liczbaSekcji;

            VertexPositionColor[] tablicaWerteksów = new VertexPositionColor[liczbaWerteksów];
            float przyrostKąta = MathHelper.TwoPi / liczbaSekcji;

            for (int i = 0; i < liczbaSekcji; i++)
            {
                float kąt = i * przyrostKąta;
                Vector3 kierunek = new Vector3((float)Math.Cos(kąt), (float)Math.Sin(kąt), 0);
                tablicaWerteksów[i].Position = promień * kierunek + położeniePodstawy * Vector3.UnitZ;
                tablicaWerteksów[i].Color = kolor;
            }

            return tablicaWerteksów;
        }

        static private Kwadryka StwórzKwadrykę( //pseudokonstruktor
            GraphicsDevice gd,
            float promieńDolny, float promieńGórny, float położeniePodstawy, float wysokość, int liczbaSekcji, 
            Color kolor)
        {
            Kwadryka kwadryka = new Kwadryka(gd, liczbaSekcji);

            VertexPositionColor[] tablicaWerteksów = Kwadryka.BudujTablicęWerteksówŚciętegoStożka(
                promieńDolny, promieńGórny, położeniePodstawy, wysokość, liczbaSekcji, kolor);
            kwadryka.ZbudujBuforWerteksów(tablicaWerteksów);
            return kwadryka;
        }

        static public Kwadryka StwórzŚciętyStożek(
            GraphicsDevice gd,
            float promieńDolny, float promieńGórny, float położeniePodstawy, float wysokość, int liczbaSekcji,
            Color kolor)
        {
            return StwórzKwadrykę(gd, promieńDolny, promieńGórny, położeniePodstawy, wysokość, liczbaSekcji, kolor);
        }

        static public Kwadryka StwórzWalec(
            GraphicsDevice gd,
            float promień, float położeniePodstawy, float wysokość, int liczbaSekcji,
            Color kolor)
        {
            return StwórzKwadrykę(gd, promień, promień, położeniePodstawy, wysokość, liczbaSekcji, kolor);
        }

        static public Kwadryka StwórzStożek(
            GraphicsDevice gd,
            float promieńDolny, float położeniePodstawy, float wysokość, int liczbaSekcji,
            Color kolor)
        {
            return Kwadryka.StwórzKwadrykę(gd, 0, promieńDolny, położeniePodstawy, wysokość, liczbaSekcji, kolor);
        }

        static public Kwadryka StwórzDysk(
            GraphicsDevice gd,
            float promieńWewnętrzny, float promieńZewnętrzny, float położeniePodstawy, int liczbaSekcji,
            Color kolor)
        {
            return Kwadryka.StwórzKwadrykę(gd, promieńZewnętrzny, promieńWewnętrzny, położeniePodstawy, 0, liczbaSekcji, kolor);
        }

        static public Kwadryka StwórzKoło(
            GraphicsDevice gd,
            float promień, float położeniePodstawy, int liczbaSekcji,
            Color kolor)
        {
            return StwórzKwadrykę(gd, promień, 0, położeniePodstawy, 0, liczbaSekcji, kolor);
        }

        static public Kwadryka StwórzSferę(
            GraphicsDevice gd,
            float promień, int liczbaSekcjiNaRównoleżnikach, int liczbaSekcjiNaPołudnikach,
            Color kolor)
        {
            Kwadryka kwadryka = new Kwadryka(gd, liczbaSekcjiNaRównoleżnikach);
            kwadryka.liczbaPasm = liczbaSekcjiNaPołudnikach;
            kwadryka.użycieBuforaIndeksów = true;
            double kątTheta;
            float wysokość;
            float promieńWewnętrzny;

            List<VertexPositionColor> listaWerteksów = new List<VertexPositionColor>();
            int[] indices = indices = new int[liczbaSekcjiNaPołudnikach * 2 * (kwadryka.liczbaPasm + 1)];

            double przyrostKątaTheta = Math.PI / liczbaSekcjiNaPołudnikach;

            wysokość = (float)(promień * Math.Cos(0));
            listaWerteksów.Add(new VertexPositionColor((wysokość) * Vector3.UnitZ, kolor)); // Pierwszy poziom odwołuje sę do jednego punktu

            for (int i = 1; i <= liczbaSekcjiNaPołudnikach - 1; i++) // generowanie każdego południka z osobna
            {
                kątTheta = i * przyrostKątaTheta;
                wysokość = (float)(promień * Math.Cos(kątTheta));
                promieńWewnętrzny = (float)(promień * Math.Sin(kątTheta));

                VertexPositionColor[] werteksyPasma = BudujTablicęWerteksówOkręgu(
                    promieńWewnętrzny, wysokość, liczbaSekcjiNaRównoleżnikach, 
                    kolor); // Funkja generująca pkt okręgu zależnie od położenia

                listaWerteksów.AddRange(werteksyPasma);
            }

            wysokość = (float)(promień * Math.Cos(Math.PI));
            listaWerteksów.Add(new VertexPositionColor((wysokość) * Vector3.UnitZ, kolor)); // Ostatni poziom odwołuje sę do jednego punktu

            for (int i = 0; i < liczbaSekcjiNaPołudnikach; i++)
            {
                for (int j = 0; j <= kwadryka.liczbaPasm; j++)
                {
                    if (i == 0) // Perwszy przebieg po stałym punkcie
                    {
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] = 0;
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = j + 1;
                        if (j == kwadryka.liczbaPasm) // 2PI = 0PI
                        {
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] = 0;
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = 1;
                        }
                    }
                    else if (i == liczbaSekcjiNaPołudnikach - 1) // Przebieg ostatni
                    {
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] =  (i - 1) * kwadryka.liczbaPasm + j + 1;
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = listaWerteksów.Count() - 1;
                        if (j == kwadryka.liczbaPasm) // 2PI = 0PI
                        {
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] = (i - 1) * kwadryka.liczbaPasm + 1 ;
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = listaWerteksów.Count() - 1;
                        }
                    }
                    else
                    {
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] = (i-1) * (kwadryka.liczbaPasm) + j + 1;
                        indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = i * (kwadryka.liczbaPasm) + j + 1;
                        if (j == kwadryka.liczbaPasm) // 2PI = 0PI
                        {
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j] = (i - 1) * (kwadryka.liczbaPasm) + 1;
                            indices[i * kwadryka.liczbaWerteksówWPaśmie + 2 * j + 1] = i * (kwadryka.liczbaPasm) + 1;
                        }
                    }
                }
            }

            kwadryka.ZbudujBuforWerteksów(listaWerteksów.ToArray());
            kwadryka.ZbudujBuforIndices(indices); // Deklaracja bufora indeksów

            //return listaWerteksów.ToArray(); //tu był błąd z końca zajęć!!!!!!!!
            return kwadryka;
        }
        #endregion

        #region Metody
        private void ZbudujBuforWerteksów(VertexPositionColor[] tablicaWerteksów)
        {
            buforWerteksów = new VertexBuffer(gd, VertexPositionColor.VertexDeclaration, tablicaWerteksów.Length, BufferUsage.WriteOnly);
            buforWerteksów.SetData<VertexPositionColor>(tablicaWerteksów);
            liczbaVertexów = tablicaWerteksów.Length;
        }

        private void ZbudujBuforIndices(int[] indices)
        {
            indexBuffer = new IndexBuffer(gd, typeof(int), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }

        public void Rysuj(Effect efekt)
        {
            foreach (EffectPass przebieg in efekt.CurrentTechnique.Passes)
            {
                przebieg.Apply();
                gd.SetVertexBuffer(buforWerteksów);
                if ( użycieBuforaIndeksów ) gd.Indices = indexBuffer;

                for (int i = 0; i < liczbaPasm; i++)
                {
                    if (użycieBuforaIndeksów) gd.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, liczbaVertexów, i * liczbaWerteksówWPaśmie, liczbaTrójkątówWPaśmie);
                    else gd.DrawPrimitives(PrimitiveType.TriangleStrip, i * liczbaWerteksówWPaśmie, liczbaTrójkątówWPaśmie);
                }
            }
        }
        #endregion        
    }
}
