using Earthblend.Tts.Common.Core.MeshGenerators.Base;
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Earthblend.Tts.Common.Core.Members;
using System.Windows.Input;
using System.Xml.Serialization;
using Microsoft.VisualStudio.Services.Common;
using Earthblend.Tts.Common.Core.Enumerations;
using Earthblend.Nts.Common;
using VirtualCanvasTestCode.Enumerations;

namespace Earthblend.Tts.Common.Core.MeshGenerators
{

    public class StackedCylinderMeshGenerator : CylindricalMeshBase
    {
        public StackedCylinderMeshGenerator()
        {
            PropertyChanged(new DependencyPropertyChangedEventArgs());
        }


        public StackedCylinderMeshGenerator Duplicate()
        {
            StackedCylinderMeshGenerator meshGenerator = new StackedCylinderMeshGenerator();

            meshGenerator.Stacks = Stacks;
            meshGenerator.Length = Length;
            meshGenerator.Slices = Slices;
            meshGenerator.Stacks = Stacks;
            meshGenerator.Radius = Radius;
            meshGenerator.ThetaStart = ThetaStart;
            meshGenerator.ThetaStop = ThetaStop;
            meshGenerator.Axis = Axis;

            meshGenerator.Top = Top;
            meshGenerator.Bottom = Bottom;
            meshGenerator.EndStacks = EndStacks;
            meshGenerator.StackRadii = StackRadii;
            meshGenerator.Fold = Fold;

            return meshGenerator;
        }

        public static readonly DependencyProperty EndStacksProperty = DependencyProperty.Register("EndStacks", typeof(int), typeof(StackedCylinderMeshGenerator), new PropertyMetadata(1, PropertyChanged), ValidateEndStacks);

        static bool ValidateEndStacks(object obj) { return (int)obj > 0; }

        public int EndStacks { set { SetValue(EndStacksProperty, value); } get { return (int)GetValue(EndStacksProperty); } }
        public static readonly DependencyProperty FoldProperty = DependencyProperty.Register("Fold", typeof(double), typeof(StackedCylinderMeshGenerator), new PropertyMetadata(1.0 / 3, PropertyChanged), ValidateFold);

        public Boolean Top { set { SetValue(TopProperty, value); } get { return (Boolean)GetValue(TopProperty); } }
        public static readonly DependencyProperty TopProperty = DependencyProperty.Register("Top", typeof(Boolean), typeof(StackedCylinderMeshGenerator), new PropertyMetadata(false, PropertyChanged), ValidateTop);

        public Boolean Bottom { set { SetValue(BottomProperty, value); } get { return (Boolean)GetValue(BottomProperty); } }
        public static readonly DependencyProperty BottomProperty = DependencyProperty.Register("Bottom", typeof(Boolean), typeof(StackedCylinderMeshGenerator), new PropertyMetadata(false, PropertyChanged), ValidateTop);


        public SerializableDictionary<int, MetaData3D> StackRadii { set { SetValue(StackRadiiProperty, value); } get { return (SerializableDictionary<int, MetaData3D>)GetValue(StackRadiiProperty); } }
        public static readonly DependencyProperty StackRadiiProperty = DependencyProperty.Register("StackRadii", typeof(SerializableDictionary<int, MetaData3D>), typeof(StackedCylinderMeshGenerator), new PropertyMetadata(new SerializableDictionary<int, MetaData3D>(), PropertyChanged));

        public int TopPoints { get; set; } = 0;
        public int CenterPoints { get; set; } = 0;
        public int BottomPoints { get; set; } = 0;

        static bool ValidateFold(object obj) { return (double)obj < 0.5; }
        static bool ValidateTop(object obj) { return true; }

        public double Fold
        {
            set { SetValue(FoldProperty, value); }
            get { return (double)GetValue(FoldProperty); }
        }
        private ICommand _decreaseEndStacksCommandIc; [XmlIgnore] public ICommand DecreaseEndStacksCommandIc => _decreaseEndStacksCommandIc ?? (_decreaseEndStacksCommandIc = new RelayCommand<Object>(DecreaseEndStacksCommandIcExecuted));
        private ICommand _increaseEndStacksCommandIc; [XmlIgnore] public ICommand IncreaseEndStacksCommandIc => _increaseEndStacksCommandIc ?? (_increaseEndStacksCommandIc = new RelayCommand<Object>(IncreaseEndStacksCommandIcExecuted));

        public void DecreaseEndStacksCommandIcExecuted(Object sender) { EndStacks--; }
        public void IncreaseEndStacksCommandIcExecuted(Object sender) { EndStacks++; }

        protected override void Triangulate(DependencyPropertyChangedEventArgs args,
                                            Point3DCollection vertices,
                                            Vector3DCollection normals,
                                            Int32Collection indices,
                                            PointCollection textures)
        {
            // Clear all four collections.
            vertices.Clear();
            normals.Clear();
            indices.Clear();
            textures.Clear();

            //Count Points
            TopPoints = 0;
            CenterPoints = 0;
            BottomPoints = 0;

            //Top
            for (int stack = 0; stack <= EndStacks; stack++)
            {
                for (int slice = 0; slice <= Slices; slice++)
                {
                    TopPoints++;
                }
            }
            //Center 
            for (int stack = 0; stack <= Stacks; stack++)
            {
                for (int slice = 0; slice <= Slices; slice++)
                {
                    CenterPoints++;
                }
            }
            //Bottom
            for (int stack = 0; stack <= EndStacks; stack++)
            {
                for (int slice = 0; slice <= Slices; slice++)
                {
                    BottomPoints++;
                }
            }

            //Check Dictionary Size
            if (StackRadii.Count < CenterPoints)
            {
                for (int i = StackRadii.Count; i < CenterPoints; i++)
                {
                    StackRadii.Add(i, new MetaData3D() { });
                }
            }

            // Begin at the top end. Fill the collections.
            if (Top) for (int stack = 0; stack <= EndStacks; stack++)
                {
                    double y = Length - (Length / 2);
                    double radius = stack * Radius / EndStacks;
                    int top = (stack + 0) * (Slices + 1);
                    int bot = (stack + 1) * (Slices + 1);

                    for (int slice = 0; slice <= Slices; slice++)
                    {
                        double theta = slice * 2 * Math.PI / Slices;
                        double x = -(radius + 0) * Math.Sin(theta);
                        double z = -(radius + 0) * Math.Cos(theta);

                        vertices.Add(new Point3D(x, y, z));
                        normals.Add(new Vector3D(0, 1, 0));
                        textures.Add(new Point((double)slice / Slices, Fold * stack / EndStacks));

                        if (stack < EndStacks && slice < Slices)
                        {
                            if (stack != 0)
                            {
                                indices.Add(top + slice);
                                indices.Add(bot + slice);
                                indices.Add(top + slice + 1);
                            }

                            indices.Add(top + slice + 1);
                            indices.Add(bot + slice);
                            indices.Add(bot + slice + 1);
                        }
                    }
                }

            int offset = vertices.Count;

            // Length of the cylinder: Fill in the collections.
            int pointNumber = 0;

            for (int stack = 0; stack <= Stacks; stack++)
            {
                double y = (Length - stack * Length / Stacks) - (Length / 2);
                int top = offset + (stack + 0) * (Slices + 1);
                int bot = offset + (stack + 1) * (Slices + 1);

                for (int slice = 0; slice <= Slices; slice++)
                {
                    double theta = slice * 2 * Math.PI / Slices;
                    double x = -(Radius + ((MetaData3D)StackRadii[pointNumber]).Radius) * Math.Sin(theta);
                    double z = -(Radius + ((MetaData3D)StackRadii[pointNumber]).Radius) * Math.Cos(theta);

                    ((MetaData3D)StackRadii[pointNumber]).Position = new Point3D(x, y, z);
                    ((MetaData3D)StackRadii[pointNumber]).Stack = stack;
                    ((MetaData3D)StackRadii[pointNumber]).Slice = slice;

                    switch (Axis)
                    {
                        case TargetAxis.X:
                            vertices.Add(new Point3D(x, y, z));
                            break;
                        case TargetAxis.Y:
                            vertices.Add(new Point3D(z, x, y));
                            break;
                        case TargetAxis.Z:
                            vertices.Add(new Point3D(y, z, x));
                            break;
                    }

                    normals.Add(new Vector3D(x, 0, z));
                    textures.Add(new Point((double)slice / Slices, Fold + (1 - 2 * Fold) * stack / Stacks));

                    if (stack < Stacks && slice < Slices)
                    {
                        indices.Add(top + slice);
                        indices.Add(bot + slice);
                        indices.Add(top + slice + 1);

                        indices.Add(top + slice + 1);
                        indices.Add(bot + slice);
                        indices.Add(bot + slice + 1);
                    }
                    pointNumber++;
                }
            }

            offset = vertices.Count;

            if (Bottom)
                // Finish with the bottom end. Fill the collections.
                for (int stack = 0; stack <= EndStacks; stack++)
                {
                    double y = 0 - (Length / 2);
                    double radius = (EndStacks - stack) * Radius / EndStacks;
                    int top = offset + (stack + 0) * (Slices + 1);
                    int bot = offset + (stack + 1) * (Slices + 1);

                    for (int slice = 0; slice <= Slices; slice++)
                    {
                        double theta = slice * 2 * Math.PI / Slices;
                        double x = -(radius + 0) * Math.Sin(theta);
                        double z = -(radius + 0) * Math.Cos(theta);

                        vertices.Add(new Point3D(x, y, z));
                        normals.Add(new Vector3D(0, -1, 0));
                        textures.Add(new Point((double)slice / Slices, (1 - Fold) + Fold * stack / EndStacks));

                        if (stack < EndStacks && slice < Slices)
                        {
                            indices.Add(top + slice);
                            indices.Add(bot + slice);
                            indices.Add(top + slice + 1);

                            if (stack != EndStacks - 1)
                            {
                                indices.Add(top + slice + 1);
                                indices.Add(bot + slice);
                                indices.Add(bot + slice + 1);
                            }
                        }
                    }
                }
        }

    }
}
