using Earthblend.Tts.Common.Core.MeshGenerators.Base;
using Earthblend.Tts.Common.Core;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Xml.Serialization;
using Earthblend.DynamicCompile;

namespace Earthblend.Tts.Common.Core.MeshGenerators
{
    public class TubeMeshGenerator : CylindricalMeshBase
    {
        #region Constructor(s)
        public TubeMeshGenerator()
        {
            PropertyChanged(new DependencyPropertyChangedEventArgs());
        }

        #endregion
        private ICommand _decreaseThicknessCommandIc; [XmlIgnore] public ICommand DecreaseThicknessCommandIc => _decreaseThicknessCommandIc ?? (_decreaseThicknessCommandIc = new RelayCommand<Object>(DecreaseThicknessCommandIcExecuted));
        private ICommand _increaseThicknessCommandIc;[XmlIgnore] public ICommand IncreaseThicknessCommandIc => _increaseThicknessCommandIc ?? (_increaseThicknessCommandIc = new RelayCommand<Object>(IncreaseThicknessCommandIcExecuted));
        private ICommand _decreaseFoldCommandIc;[XmlIgnore] public ICommand DecreaseFoldCommandIc => _decreaseFoldCommandIc ?? (_decreaseFoldCommandIc = new RelayCommand<Object>(DecreaseFoldCommandIcExecuted));
        private ICommand _increaseFoldCommandIc;[XmlIgnore] public ICommand IncreaseFoldCommandIc => _increaseFoldCommandIc ?? (_increaseFoldCommandIc = new RelayCommand<Object>(IncreaseFoldCommandIcExecuted));
        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 DecreaseThicknessCommandIcExecuted(Object sender) { Thickness-=MovementValue; }
        public void IncreaseThicknessCommandIcExecuted(Object sender) { Thickness+=MovementValue; }
        public void DecreaseFoldCommandIcExecuted(Object sender) { Fold-=.01; }
        public void IncreaseFoldCommandIcExecuted(Object sender) { Fold+=.01; }
        public void DecreaseEndStacksCommandIcExecuted(Object sender) { EndStacks--; }
        public void IncreaseEndStacksCommandIcExecuted(Object sender) { EndStacks++; }

        public static IList<String> Properties = new List<String>() { "Thickness", "EndStacks", "Fold", "Radius", "Slices", "Stacks" };

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

            meshGenerator.Thickness = Thickness;
            meshGenerator.EndStacks = EndStacks;
            meshGenerator.Fold = Fold;
            meshGenerator.Radius = Radius;
            meshGenerator.Slices = Slices;
            meshGenerator.Stacks = Stacks;
            meshGenerator.Length = Length;

            return meshGenerator;
        }

        public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register("Thickness", typeof(Double), typeof(TubeMeshGenerator),new PropertyMetadata(0.25, PropertyChanged), ValidateThickness);
        public static readonly DependencyProperty EndStacksProperty = DependencyProperty.Register("EndStacks", typeof(int), typeof(TubeMeshGenerator), new PropertyMetadata(1, PropertyChanged), ValidateEndStacks);
        public static readonly DependencyProperty FoldProperty = DependencyProperty.Register("Fold", typeof(Double),typeof(TubeMeshGenerator), new PropertyMetadata(0.125, PropertyChanged), ValidateFold);

        static bool ValidateThickness(object obj) { return (double)obj >= 0; }
        static bool ValidateEndStacks(object obj) { return (int)obj > 0; }
        static bool ValidateFold(object obj) { return (double)obj < 0.5; }

        public double Thickness { set { SetValue(ThicknessProperty, value); } get { return (double)GetValue(ThicknessProperty); } }
        public int EndStacks { set { SetValue(EndStacksProperty, value); } get { return (int)GetValue(EndStacksProperty); } }
        public double Fold { set { SetValue(FoldProperty, value); } get { return (double)GetValue(FoldProperty); } }

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

            // Loop for outside (side = 1) and inside (side = -1).
            for (int side = 1; side >= -1; side -= 2)
            {
                int offset = vertices.Count;

                // Begin at the top end. Fill the collections.
                for (int stack = 0; stack <= EndStacks; stack++)
                {
                    double y = Length;
                    double radius = Radius + side * stack * Thickness / 2 / 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 * Math.Sin(theta);
                        double z = -radius * Math.Cos(theta);

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

                        if (stack < EndStacks && 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);
                        }
                    }
                }

                offset = vertices.Count;

                // Length of the tube: Fill in the collections.
                for (int stack = 0; stack <= Stacks; stack++)
                {
                    double y = Length - stack * Length / Stacks;
                    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 + side * Thickness / 2) * Math.Sin(theta);
                        double z = -(Radius + side * Thickness / 2) * Math.Cos(theta);

                        vertices.Add(new Point3D(x, y, z));
                        normals.Add(new Vector3D(side * x, 0, side * 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);
                        }
                    }
                }

                offset = vertices.Count;

                // Finish with the bottom end. Fill the collections.
                for (int stack = 0; stack <= EndStacks; stack++)
                {
                    double y = 0;
                    double radius = Radius + side * Thickness / 2 * (1 - (double)stack / 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 * Math.Sin(theta);
                        double z = -radius * Math.Cos(theta);

                        vertices.Add(new Point3D(x, y, z));
                        normals.Add(new Vector3D(0, -side, 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);

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